Colossus.mjs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. /**
  2. * Emulation of Colossus.
  3. *
  4. * @author VirtualColossus [martin@virtualcolossus.co.uk]
  5. * @copyright Crown Copyright 2019
  6. * @license Apache-2.0
  7. */
  8. import Operation from "../Operation.mjs";
  9. import OperationError from "../errors/OperationError.mjs";
  10. import { ColossusComputer } from "../lib/Colossus.mjs";
  11. import { SWITCHES, VALID_ITA2 } from "../lib/Lorenz.mjs";
  12. /**
  13. * Colossus operation
  14. */
  15. class Colossus extends Operation {
  16. /**
  17. * Colossus constructor
  18. */
  19. constructor() {
  20. super();
  21. this.name = "Colossus";
  22. this.module = "Bletchley";
  23. this.description = "Colossus is the name of the world's first electronic computer. Ten Colossi were designed by Tommy Flowers and built at the Post Office Research Labs at Dollis Hill in 1943 during World War 2. They assisted with the breaking of the German Lorenz cipher attachment, a machine created to encipher communications between Hitler and his generals on the front lines.<br><br>To learn more, Virtual Colossus, an online, browser based simulation of a Colossus computer is available at <a href='https://virtualcolossus.co.uk' target='_blank'>virtualcolossus.co.uk</a>.<br><br>A more detailed description of this operation can be found <a href='https://github.com/gchq/CyberChef/wiki/Colossus' target='_blank'>here</a>.";
  24. this.infoURL = "https://wikipedia.org/wiki/Colossus_computer";
  25. this.inputType = "string";
  26. this.outputType = "JSON";
  27. this.presentType = "html";
  28. this.args = [
  29. {
  30. name: "Input",
  31. type: "label"
  32. },
  33. {
  34. name: "Pattern",
  35. type: "option",
  36. value: ["KH Pattern", "ZMUG Pattern", "BREAM Pattern"]
  37. },
  38. {
  39. name: "QBusZ",
  40. type: "option",
  41. value: ["", "Z", "ΔZ"]
  42. },
  43. {
  44. name: "QBusΧ",
  45. type: "option",
  46. value: ["", "Χ", "ΔΧ"]
  47. },
  48. {
  49. name: "QBusΨ",
  50. type: "option",
  51. value: ["", "Ψ", "ΔΨ"]
  52. },
  53. {
  54. name: "Limitation",
  55. type: "option",
  56. value: ["None", "Χ2", "Χ2 + P5", "X2 + Ψ1", "X2 + Ψ1 + P5"]
  57. },
  58. {
  59. name: "K Rack Option",
  60. type: "argSelector",
  61. value: [
  62. {
  63. name: "Select Program",
  64. on: [7],
  65. off: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
  66. },
  67. {
  68. name: "Top Section - Conditional",
  69. on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
  70. off: [7, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
  71. },
  72. {
  73. name: "Bottom Section - Addition",
  74. on: [31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
  75. off: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
  76. },
  77. {
  78. name: "Advanced",
  79. on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
  80. off: [7]
  81. }
  82. ]
  83. },
  84. {
  85. name: "Program to run",
  86. type: "option",
  87. value: ["", "Letter Count", "1+2=. (1+2 Break In, Find X1,X2)", "4=5=/1=2 (Given X1,X2 find X4,X5)", "/,5,U (Count chars to find X3)"]
  88. },
  89. {
  90. name: "K Rack: Conditional",
  91. type: "label"
  92. },
  93. {
  94. name: "R1-Q1",
  95. type: "editableOptionShort",
  96. value: SWITCHES,
  97. defaultIndex: 1
  98. },
  99. {
  100. name: "R1-Q2",
  101. type: "editableOptionShort",
  102. value: SWITCHES,
  103. defaultIndex: 1
  104. },
  105. {
  106. name: "R1-Q3",
  107. type: "editableOptionShort",
  108. value: SWITCHES,
  109. defaultIndex: 1
  110. },
  111. {
  112. name: "R1-Q4",
  113. type: "editableOptionShort",
  114. value: SWITCHES,
  115. defaultIndex: 1
  116. },
  117. {
  118. name: "R1-Q5",
  119. type: "editableOptionShort",
  120. value: SWITCHES,
  121. defaultIndex: 1
  122. },
  123. {
  124. name: "R1-Negate",
  125. type: "boolean"
  126. },
  127. {
  128. name: "R1-Counter",
  129. type: "option",
  130. value: ["", "1", "2", "3", "4", "5"]
  131. },
  132. {
  133. name: "R2-Q1",
  134. type: "editableOptionShort",
  135. value: SWITCHES,
  136. defaultIndex: 1
  137. },
  138. {
  139. name: "R2-Q2",
  140. type: "editableOptionShort",
  141. value: SWITCHES,
  142. defaultIndex: 1
  143. },
  144. {
  145. name: "R2-Q3",
  146. type: "editableOptionShort",
  147. value: SWITCHES,
  148. defaultIndex: 1
  149. },
  150. {
  151. name: "R2-Q4",
  152. type: "editableOptionShort",
  153. value: SWITCHES,
  154. defaultIndex: 1
  155. },
  156. {
  157. name: "R2-Q5",
  158. type: "editableOptionShort",
  159. value: SWITCHES,
  160. defaultIndex: 1
  161. },
  162. {
  163. name: "R2-Negate",
  164. type: "boolean"
  165. },
  166. {
  167. name: "R2-Counter",
  168. type: "option",
  169. value: ["", "1", "2", "3", "4", "5"]
  170. },
  171. {
  172. name: "R3-Q1",
  173. type: "editableOptionShort",
  174. value: SWITCHES,
  175. defaultIndex: 1
  176. },
  177. {
  178. name: "R3-Q2",
  179. type: "editableOptionShort",
  180. value: SWITCHES,
  181. defaultIndex: 1
  182. },
  183. {
  184. name: "R3-Q3",
  185. type: "editableOptionShort",
  186. value: SWITCHES,
  187. defaultIndex: 1
  188. },
  189. {
  190. name: "R3-Q4",
  191. type: "editableOptionShort",
  192. value: SWITCHES,
  193. defaultIndex: 1
  194. },
  195. {
  196. name: "R3-Q5",
  197. type: "editableOptionShort",
  198. value: SWITCHES,
  199. defaultIndex: 1
  200. },
  201. {
  202. name: "R3-Negate",
  203. type: "boolean"
  204. },
  205. {
  206. name: "R3-Counter",
  207. type: "option",
  208. value: ["", "1", "2", "3", "4", "5"]
  209. },
  210. {
  211. name: "Negate All",
  212. type: "boolean"
  213. },
  214. {
  215. name: "K Rack: Addition",
  216. type: "label"
  217. },
  218. {
  219. name: "Add-Q1",
  220. type: "boolean"
  221. },
  222. {
  223. name: "Add-Q2",
  224. type: "boolean"
  225. },
  226. {
  227. name: "Add-Q3",
  228. type: "boolean"
  229. },
  230. {
  231. name: "Add-Q4",
  232. type: "boolean"
  233. },
  234. {
  235. name: "Add-Q5",
  236. type: "boolean"
  237. },
  238. {
  239. name: "Add-Equals",
  240. type: "editableOptionShort",
  241. value: SWITCHES,
  242. defaultIndex: 1
  243. },
  244. {
  245. name: "Add-Counter1",
  246. type: "boolean"
  247. },
  248. {
  249. name: "Add Negate All",
  250. type: "boolean"
  251. },
  252. {
  253. name: "Total Motor",
  254. type: "editableOptionShort",
  255. value: SWITCHES,
  256. defaultIndex: 1
  257. },
  258. {
  259. name: "Master Control Panel",
  260. type: "label"
  261. },
  262. {
  263. name: "Set Total",
  264. type: "number",
  265. value: 0
  266. },
  267. {
  268. name: "Fast Step",
  269. type: "option",
  270. value: ["", "X1", "X2", "X3", "X4", "X5", "M37", "M61", "S1", "S2", "S3", "S4", "S5"]
  271. },
  272. {
  273. name: "Slow Step",
  274. type: "option",
  275. value: ["", "X1", "X2", "X3", "X4", "X5", "M37", "M61", "S1", "S2", "S3", "S4", "S5"]
  276. },
  277. {
  278. name: "Start Χ1",
  279. type: "number",
  280. value: 1
  281. },
  282. {
  283. name: "Start Χ2",
  284. type: "number",
  285. value: 1
  286. },
  287. {
  288. name: "Start Χ3",
  289. type: "number",
  290. value: 1
  291. },
  292. {
  293. name: "Start Χ4",
  294. type: "number",
  295. value: 1
  296. },
  297. {
  298. name: "Start Χ5",
  299. type: "number",
  300. value: 1
  301. },
  302. {
  303. name: "Start M61",
  304. type: "number",
  305. value: 1
  306. },
  307. {
  308. name: "Start M37",
  309. type: "number",
  310. value: 1
  311. },
  312. {
  313. name: "Start Ψ1",
  314. type: "number",
  315. value: 1
  316. },
  317. {
  318. name: "Start Ψ2",
  319. type: "number",
  320. value: 1
  321. },
  322. {
  323. name: "Start Ψ3",
  324. type: "number",
  325. value: 1
  326. },
  327. {
  328. name: "Start Ψ4",
  329. type: "number",
  330. value: 1
  331. },
  332. {
  333. name: "Start Ψ5",
  334. type: "number",
  335. value: 1
  336. }
  337. ];
  338. }
  339. /**
  340. * @param {string} input
  341. * @param {Object[]} args
  342. * @returns {Object}
  343. */
  344. run(input, args) {
  345. input = input.toUpperCase();
  346. for (const character of input) {
  347. if (VALID_ITA2.indexOf(character) === -1) {
  348. let errltr = character;
  349. if (errltr === "\n") errltr = "Carriage Return";
  350. if (errltr === " ") errltr = "Space";
  351. throw new OperationError("Invalid ITA2 character : " + errltr);
  352. }
  353. }
  354. const pattern = args[1];
  355. const qbusin = {
  356. "Z": args[2],
  357. "Chi": args[3],
  358. "Psi": args[4],
  359. };
  360. const limitation = args[5];
  361. const lm = [false, false, false];
  362. if (limitation.includes("Χ2")) lm[0] = true;
  363. if (limitation.includes("Ψ1")) lm[1] = true;
  364. if (limitation.includes("P5")) lm[2] = true;
  365. const limit = {
  366. X2: lm[0], S1: lm[1], P5: lm[2]
  367. };
  368. const KRackOpt = args[6];
  369. const setProgram = args[7];
  370. if (KRackOpt === "Select Program" && setProgram !== "") {
  371. args = this.selectProgram(setProgram, args);
  372. }
  373. const re = new RegExp("^$|^[.x]$");
  374. for (let qr=0;qr<3;qr++) {
  375. for (let a=0;a<5;a++) {
  376. if (!re.test(args[((qr*7)+(a+9))]))
  377. throw new OperationError("Switch R"+(qr+1)+"-Q"+(a+1)+" can only be set to blank, . or x");
  378. }
  379. }
  380. if (!re.test(args[37])) throw new OperationError("Switch Add-Equals can only be set to blank, . or x");
  381. if (!re.test(args[40])) throw new OperationError("Switch Total Motor can only be set to blank, . or x");
  382. // Q1,Q2,Q3,Q4,Q5,negate,counter1
  383. const qbusswitches = {
  384. condition: [
  385. {Qswitches: [args[9], args[10], args[11], args[12], args[13]], Negate: args[14], Counter: args[15]},
  386. {Qswitches: [args[16], args[17], args[18], args[19], args[20]], Negate: args[21], Counter: args[22]},
  387. {Qswitches: [args[23], args[24], args[25], args[26], args[27]], Negate: args[28], Counter: args[29]}
  388. ],
  389. condNegateAll: args[30],
  390. addition: [
  391. {Qswitches: [args[32], args[33], args[34], args[35], args[36]], Equals: args[37], C1: args[38]}
  392. ],
  393. addNegateAll: args[39],
  394. totalMotor: args[40]
  395. };
  396. const settotal = parseInt(args[42], 10);
  397. if (settotal < 0 || settotal > 9999)
  398. throw new OperationError("Set Total must be between 0000 and 9999");
  399. // null|fast|slow for each of S1-5,M1-2,X1-5
  400. const control = {
  401. fast: args[43],
  402. slow: args[44]
  403. };
  404. // Start positions
  405. if (args[52]<1 || args[52]>43) throw new OperationError("Ψ1 start must be between 1 and 43");
  406. if (args[53]<1 || args[53]>47) throw new OperationError("Ψ2 start must be between 1 and 47");
  407. if (args[54]<1 || args[54]>51) throw new OperationError("Ψ3 start must be between 1 and 51");
  408. if (args[55]<1 || args[55]>53) throw new OperationError("Ψ4 start must be between 1 and 53");
  409. if (args[56]<1 || args[57]>59) throw new OperationError("Ψ5 start must be between 1 and 59");
  410. if (args[51]<1 || args[51]>37) throw new OperationError("Μ37 start must be between 1 and 37");
  411. if (args[50]<1 || args[50]>61) throw new OperationError("Μ61 start must be between 1 and 61");
  412. if (args[45]<1 || args[45]>41) throw new OperationError("Χ1 start must be between 1 and 41");
  413. if (args[46]<1 || args[46]>31) throw new OperationError("Χ2 start must be between 1 and 31");
  414. if (args[47]<1 || args[47]>29) throw new OperationError("Χ3 start must be between 1 and 29");
  415. if (args[48]<1 || args[48]>26) throw new OperationError("Χ4 start must be between 1 and 26");
  416. if (args[49]<1 || args[49]>23) throw new OperationError("Χ5 start must be between 1 and 23");
  417. const starts = {
  418. X1: args[45], X2: args[46], X3: args[47], X4: args[48], X5: args[49],
  419. M61: args[50], M37: args[51],
  420. S1: args[52], S2: args[53], S3: args[54], S4: args[55], S5: args[56]
  421. };
  422. const colossus = new ColossusComputer(input, pattern, qbusin, qbusswitches, control, starts, settotal, limit);
  423. const result = colossus.run();
  424. return result;
  425. }
  426. /**
  427. * Select Program
  428. *
  429. * @param {string} progname
  430. * @param {Object[]} args
  431. * @returns {Object[]}
  432. */
  433. selectProgram(progname, args) {
  434. // Basic Letter Count
  435. if (progname === "Letter Count") {
  436. // Set Conditional R1 : count every character into counter 1
  437. args[9] = "";
  438. args[10] = "";
  439. args[11] = "";
  440. args[12] = "";
  441. args[13] = "";
  442. args[14] = false;
  443. args[15] = "1";
  444. // clear Conditional R2 & R3
  445. args[22] = "";
  446. args[29] = "";
  447. // Clear Negate result
  448. args[30] = false;
  449. // Clear Addition row counter
  450. args[38] = false;
  451. }
  452. // Bill Tutte's 1+2 Break In
  453. if (progname === "1+2=. (1+2 Break In, Find X1,X2)") {
  454. // Clear any other counters
  455. args[15] = ""; // Conditional R1
  456. args[22] = ""; // Conditional R2
  457. args[29] = ""; // Conditional R3
  458. // Set Add Q1+Q2=. into Counter 1
  459. args[32] = true;
  460. args[33] = true;
  461. args[34] = false;
  462. args[35] = false;
  463. args[36] = false;
  464. args[37] = ".";
  465. args[38] = true;
  466. }
  467. // 4=3=/1=2 : Find X4 & X5 where X1 & X2 are known
  468. if (progname === "4=5=/1=2 (Given X1,X2 find X4,X5)") {
  469. // Set Conditional R1 : Match NOT ..?.. into counter 1
  470. args[9] = ".";
  471. args[10] = ".";
  472. args[11] = "";
  473. args[12] = ".";
  474. args[13] = ".";
  475. args[14] = true;
  476. args[15] = "1";
  477. // Set Conditional R2 : AND Match NOT xx?xx into counter 1
  478. args[16] = "x";
  479. args[17] = "x";
  480. args[18] = "";
  481. args[19] = "x";
  482. args[20] = "x";
  483. args[21] = true;
  484. args[22] = "1";
  485. // clear Conditional R3
  486. args[29] = "";
  487. // Negate result, giving NOT(NOT Q1 AND NOT Q2) which is equivalent to Q1 OR Q2
  488. args[30] = true;
  489. // Clear Addition row counter
  490. args[38] = false;
  491. }
  492. // /,5,U : Count number of matches of /, 5 & U to find X3
  493. if (progname === "/,5,U (Count chars to find X3)") {
  494. // Set Conditional R1 : Match / char, ITA2 = ..... into counter 1
  495. args[9] = ".";
  496. args[10] = ".";
  497. args[11] = ".";
  498. args[12] = ".";
  499. args[13] = ".";
  500. args[14] = false;
  501. args[15] = "1";
  502. // Set Conditional R2 : Match 5 char, ITA2 = xx.xx into counter 2
  503. args[16] = "x";
  504. args[17] = "x";
  505. args[18] = ".";
  506. args[19] = "x";
  507. args[20] = "x";
  508. args[21] = false;
  509. args[22] = "2";
  510. // Set Conditional R3 : Match U char, ITA2 = xxx.. into counter 3
  511. args[23] = "x";
  512. args[24] = "x";
  513. args[25] = "x";
  514. args[26] = ".";
  515. args[27] = ".";
  516. args[28] = false;
  517. args[29] = "3";
  518. // Clear Negate result
  519. args[30] = false;
  520. // Clear Addition row counter
  521. args[38] = false;
  522. }
  523. return args;
  524. }
  525. /**
  526. * Displays Colossus results in an HTML table
  527. *
  528. * @param {Object} output
  529. * @param {Object[]} output.counters
  530. * @returns {html}
  531. */
  532. present(output) {
  533. let html = "Colossus Printer\n\n";
  534. html += output.printout + "\n\n";
  535. html += "Colossus Counters\n\n";
  536. html += "<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>C1</th> <th>C2</th> <th>C3</th> <th>C4</th> <th>C5</th></tr>\n";
  537. html += "<tr>";
  538. for (const ct of output.counters) {
  539. html += `<td>${ct}</td>\n`;
  540. }
  541. html += "</tr>";
  542. html += "</table>";
  543. return html;
  544. }
  545. }
  546. export default Colossus;