2019-01-10 19:04:02 +01:00
/ * *
* Emulation of the Bombe machine .
* This version carries out multiple Bombe runs to handle unknown rotor configurations .
*
* @ author s2224834
* @ copyright Crown Copyright 2019
* @ license Apache - 2.0
* /
2019-07-09 13:23:59 +02:00
import Operation from "../Operation.mjs" ;
import OperationError from "../errors/OperationError.mjs" ;
import { BombeMachine } from "../lib/Bombe.mjs" ;
import { ROTORS , ROTORS _FOURTH , REFLECTORS , Reflector } from "../lib/Enigma.mjs" ;
import { isWorkerEnvironment } from "../Utils.mjs" ;
2019-07-05 11:17:52 +02:00
2019-01-10 19:04:02 +01:00
/ * *
* Convenience method for flattening the preset ROTORS object into a newline - separated string .
* @ param { Object [ ] } - Preset rotors object
* @ param { number } s - Start index
* @ param { number } n - End index
* @ returns { string }
* /
function rotorsFormat ( rotors , s , n ) {
const res = [ ] ;
for ( const i of rotors . slice ( s , n ) ) {
res . push ( i . value ) ;
}
return res . join ( "\n" ) ;
}
/ * *
* Combinatorics choose function
* @ param { number } n
* @ param { number } k
* @ returns number
* /
function choose ( n , k ) {
let res = 1 ;
for ( let i = 1 ; i <= k ; i ++ ) {
res *= ( n + 1 - i ) / i ;
}
return res ;
}
/ * *
* Bombe operation
* /
class MultipleBombe extends Operation {
/ * *
* Bombe constructor
* /
constructor ( ) {
super ( ) ;
this . name = "Multiple Bombe" ;
this . module = "Default" ;
2019-03-14 13:08:35 +01:00
this . description = "Emulation of the Bombe machine used to attack Enigma. This version carries out multiple Bombe runs to handle unknown rotor configurations.<br><br>You should test your menu on the single Bombe operation before running it here. See the description of the Bombe operation for instructions on choosing a crib.<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>." ;
2019-01-10 19:04:02 +01:00
this . infoURL = "https://wikipedia.org/wiki/Bombe" ;
this . inputType = "string" ;
2019-02-08 16:21:14 +01:00
this . outputType = "JSON" ;
this . presentType = "html" ;
2019-01-10 19:04:02 +01:00
this . args = [
{
"name" : "Standard Enigmas" ,
2019-02-08 01:59:56 +01:00
"type" : "populateMultiOption" ,
2019-01-10 19:04:02 +01:00
"value" : [
{
name : "German Service Enigma (First - 3 rotor)" ,
2019-02-08 01:59:56 +01:00
value : [
rotorsFormat ( ROTORS , 0 , 5 ) ,
"" ,
rotorsFormat ( REFLECTORS , 0 , 1 )
]
2019-01-10 19:04:02 +01:00
} ,
{
name : "German Service Enigma (Second - 3 rotor)" ,
2019-02-08 01:59:56 +01:00
value : [
rotorsFormat ( ROTORS , 0 , 8 ) ,
"" ,
rotorsFormat ( REFLECTORS , 0 , 2 )
]
2019-01-10 19:04:02 +01:00
} ,
{
name : "German Service Enigma (Third - 4 rotor)" ,
2019-02-08 01:59:56 +01:00
value : [
rotorsFormat ( ROTORS , 0 , 8 ) ,
rotorsFormat ( ROTORS _FOURTH , 1 , 2 ) ,
rotorsFormat ( REFLECTORS , 2 , 3 )
]
2019-01-10 19:04:02 +01:00
} ,
{
name : "German Service Enigma (Fourth - 4 rotor)" ,
2019-02-08 01:59:56 +01:00
value : [
rotorsFormat ( ROTORS , 0 , 8 ) ,
rotorsFormat ( ROTORS _FOURTH , 1 , 3 ) ,
rotorsFormat ( REFLECTORS , 2 , 4 )
]
2019-01-10 19:04:02 +01:00
} ,
{
name : "User defined" ,
2019-02-08 01:59:56 +01:00
value : [ "" , "" , "" ]
2019-01-10 19:04:02 +01:00
} ,
] ,
2019-02-08 01:59:56 +01:00
"target" : [ 1 , 2 , 3 ]
2019-01-10 19:04:02 +01:00
} ,
{
name : "Main rotors" ,
type : "text" ,
value : ""
} ,
{
name : "4th rotor" ,
type : "text" ,
value : ""
} ,
{
name : "Reflectors" ,
type : "text" ,
value : ""
} ,
{
name : "Crib" ,
type : "string" ,
value : ""
} ,
{
name : "Crib offset" ,
type : "number" ,
value : 0
2019-01-11 14:18:25 +01:00
} ,
{
name : "Use checking machine" ,
type : "boolean" ,
value : true
2019-01-10 19:04:02 +01:00
}
] ;
}
/ * *
* Format and send a status update message .
* @ param { number } nLoops - Number of loops in the menu
* @ param { number } nStops - How many stops so far
* @ param { number } progress - Progress ( as a float in the range 0. . 1 )
* /
2019-01-12 02:35:24 +01:00
updateStatus ( nLoops , nStops , progress , start ) {
const elapsed = new Date ( ) . getTime ( ) - start ;
const remaining = ( elapsed / progress ) * ( 1 - progress ) / 1000 ;
const hours = Math . floor ( remaining / 3600 ) ;
const minutes = ` 0 ${ Math . floor ( ( remaining % 3600 ) / 60 ) } ` . slice ( - 2 ) ;
const seconds = ` 0 ${ Math . floor ( remaining % 60 ) } ` . slice ( - 2 ) ;
2019-02-08 16:21:14 +01:00
const msg = ` Bombe run with ${ nLoops } loop ${ nLoops === 1 ? "" : "s" } in menu (2+ desirable): ${ nStops } stops, ${ Math . floor ( 100 * progress ) } % done, ${ hours } : ${ minutes } : ${ seconds } remaining ` ;
2019-01-10 19:04:02 +01:00
self . sendStatusMessage ( msg ) ;
}
/ * *
* Early rotor description string validation .
* Drops stepping information .
* @ param { string } rstr - The rotor description string
* @ returns { string } - Rotor description with stepping stripped , if any
* /
validateRotor ( rstr ) {
// The Bombe doesn't take stepping into account so we'll just ignore it here
if ( rstr . includes ( "<" ) ) {
rstr = rstr . split ( "<" , 2 ) [ 0 ] ;
}
// Duplicate the validation of the rotor strings here, otherwise you might get an error
// thrown halfway into a big Bombe run
if ( ! /^[A-Z]{26}$/ . test ( rstr ) ) {
throw new OperationError ( "Rotor wiring must be 26 unique uppercase letters" ) ;
}
if ( new Set ( rstr ) . size !== 26 ) {
throw new OperationError ( "Rotor wiring must be 26 unique uppercase letters" ) ;
}
return rstr ;
}
/ * *
* @ param { string } input
* @ param { Object [ ] } args
* @ returns { string }
* /
run ( input , args ) {
const mainRotorsStr = args [ 1 ] ;
2019-02-08 01:59:56 +01:00
const fourthRotorsStr = args [ 2 ] ;
const reflectorsStr = args [ 3 ] ;
let crib = args [ 4 ] ;
const offset = args [ 5 ] ;
const check = args [ 6 ] ;
2019-01-10 19:04:02 +01:00
const rotors = [ ] ;
const fourthRotors = [ ] ;
const reflectors = [ ] ;
for ( let rstr of mainRotorsStr . split ( "\n" ) ) {
rstr = this . validateRotor ( rstr ) ;
rotors . push ( rstr ) ;
}
if ( rotors . length < 3 ) {
throw new OperationError ( "A minimum of three rotors must be supplied" ) ;
}
if ( fourthRotorsStr !== "" ) {
for ( let rstr of fourthRotorsStr . split ( "\n" ) ) {
rstr = this . validateRotor ( rstr ) ;
fourthRotors . push ( rstr ) ;
}
}
if ( fourthRotors . length === 0 ) {
fourthRotors . push ( "" ) ;
}
for ( const rstr of reflectorsStr . split ( "\n" ) ) {
const reflector = new Reflector ( rstr ) ;
reflectors . push ( reflector ) ;
}
if ( reflectors . length === 0 ) {
throw new OperationError ( "A minimum of one reflector must be supplied" ) ;
}
if ( crib . length === 0 ) {
throw new OperationError ( "Crib cannot be empty" ) ;
}
if ( offset < 0 ) {
throw new OperationError ( "Offset cannot be negative" ) ;
}
// For symmetry with the Enigma op, for the input we'll just remove all invalid characters
input = input . replace ( /[^A-Za-z]/g , "" ) . toUpperCase ( ) ;
crib = crib . replace ( /[^A-Za-z]/g , "" ) . toUpperCase ( ) ;
const ciphertext = input . slice ( offset ) ;
let update ;
2019-07-05 11:17:52 +02:00
if ( isWorkerEnvironment ( ) ) {
2019-01-10 19:04:02 +01:00
update = this . updateStatus ;
} else {
update = undefined ;
}
let bombe = undefined ;
2019-02-08 16:21:14 +01:00
const output = { bombeRuns : [ ] } ;
2019-01-10 19:04:02 +01:00
// I could use a proper combinatorics algorithm here... but it would be more code to
// write one, and we don't seem to have one in our existing libraries, so massively nested
// for loop it is
const totalRuns = choose ( rotors . length , 3 ) * 6 * fourthRotors . length * reflectors . length ;
let nRuns = 0 ;
let nStops = 0 ;
2019-01-12 02:35:24 +01:00
const start = new Date ( ) . getTime ( ) ;
2019-01-10 19:04:02 +01:00
for ( const rotor1 of rotors ) {
for ( const rotor2 of rotors ) {
if ( rotor2 === rotor1 ) {
continue ;
}
for ( const rotor3 of rotors ) {
if ( rotor3 === rotor2 || rotor3 === rotor1 ) {
continue ;
}
for ( const rotor4 of fourthRotors ) {
for ( const reflector of reflectors ) {
nRuns ++ ;
const runRotors = [ rotor1 , rotor2 , rotor3 ] ;
if ( rotor4 !== "" ) {
runRotors . push ( rotor4 ) ;
}
if ( bombe === undefined ) {
2019-01-11 14:18:25 +01:00
bombe = new BombeMachine ( runRotors , reflector , ciphertext , crib , check ) ;
2019-02-08 16:21:14 +01:00
output . nLoops = bombe . nLoops ;
2019-01-10 19:04:02 +01:00
} else {
bombe . changeRotors ( runRotors , reflector ) ;
}
const result = bombe . run ( ) ;
nStops += result . length ;
if ( update !== undefined ) {
2019-01-12 02:35:24 +01:00
update ( bombe . nLoops , nStops , nRuns / totalRuns , start ) ;
2019-01-10 19:04:02 +01:00
}
if ( result . length > 0 ) {
2019-02-08 16:21:14 +01:00
output . bombeRuns . push ( {
rotors : runRotors ,
reflector : reflector . pairs ,
result : result
} ) ;
2019-01-10 19:04:02 +01:00
}
}
}
}
}
}
2019-02-08 16:21:14 +01:00
return output ;
}
/ * *
* Displays the MultiBombe results in an HTML table
*
* @ param { Object } output
* @ param { number } output . nLoops
* @ param { Array [ ] } output . result
* @ returns { html }
* /
present ( output ) {
let html = ` Bombe run on menu with ${ output . nLoops } loop ${ output . nLoops === 1 ? "" : "s" } (2+ desirable). Note: Rotors and rotor positions are listed left to right, ignore stepping and the ring setting, and positions start at the beginning of the crib. Some plugboard settings are determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided. \n ` ;
for ( const run of output . bombeRuns ) {
2019-02-28 19:37:48 +01:00
html += ` \n Rotors: ${ run . rotors . slice ( ) . reverse ( ) . join ( ", " ) } \n Reflector: ${ run . reflector } \n ` ;
2019-03-14 12:43:23 +01:00
html += "<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Rotor stops</th> <th>Partial plugboard</th> <th>Decryption preview</th></tr>\n" ;
2019-02-08 16:21:14 +01:00
for ( const [ setting , stecker , decrypt ] of run . result ) {
2019-03-14 12:43:23 +01:00
html += ` <tr><td> ${ setting } </td> <td> ${ stecker } </td> <td> ${ decrypt } </td></tr> \n ` ;
2019-02-08 16:21:14 +01:00
}
html += "</table>\n" ;
}
return html ;
2019-01-10 19:04:02 +01:00
}
}
export default MultipleBombe ;