/** * Colossus - an emulation of the world's first electronic computer * * @author VirtualColossus [martin@virtualcolossus.co.uk] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import {INIT_PATTERNS, ITA2_TABLE, ROTOR_SIZES} from "../lib/Lorenz.mjs"; /** * Colossus simulator class. */ export class ColossusComputer { /** * Construct a Colossus. * * @param {string} ciphertext * @param {string} pattern - named pattern of Chi, Mu and Psi wheels * @param {Object} qbusin - which data inputs are being sent to q bus - each can be null, plain or delta * @param {Object[]} qbusswitches - Q bus calculation switches, multiple rows * @param {Object} control - control switches which specify stepping modes * @param {Object} starts - rotor start positions */ constructor(ciphertext, pattern, qbusin, qbusswitches, control, starts, settotal, limit) { this.ITAlookup = ITA2_TABLE; this.ReverseITAlookup = {}; for (const letter in this.ITAlookup) { const code = this.ITAlookup[letter]; this.ReverseITAlookup[code] = letter; } this.initThyratrons(pattern); this.ciphertext = ciphertext; this.qbusin = qbusin; this.qbusswitches = qbusswitches; this.control = control; this.starts = starts; this.settotal = settotal; this.limitations = limit; this.allCounters = [0, 0, 0, 0, 0]; this.Zbits = [0, 0, 0, 0, 0]; // Z input is the cipher tape this.ZbitsOneBack = [0, 0, 0, 0, 0]; // for delta this.Qbits = [0, 0, 0, 0, 0]; // input generated for placing onto the Q-bus (the logic processor) this.Xbits = [0, 0, 0, 0, 0]; // X is the Chi wheel bits this.Xptr = [0, 0, 0, 0, 0]; // pointers to the current X bits (Chi wheels) this.XbitsOneBack = [0, 0, 0, 0, 0]; // the X bits one back (for delta) this.Sbits = [0, 0, 0, 0, 0]; // S is the Psi wheel bits this.Sptr = [0, 0, 0, 0, 0]; // pointers to the current S bits (Psi wheels) this.SbitsOneBack = [0, 0, 0, 0, 0]; // the S bits one back (for delta) this.Mptr = [0, 0]; this.rotorPtrs = {}; this.totalmotor = 0; this.P5Zbit = [0, 0]; } /** * Begin a run * * @returns {object} */ run() { const result = { printout: "" }; // loop until our start positions are back to the beginning this.rotorPtrs = {X1: this.starts.X1, X2: this.starts.X2, X3: this.starts.X3, X4: this.starts.X4, X5: this.starts.X5, M61: this.starts.M61, M37: this.starts.M37, S1: this.starts.S1, S2: this.starts.S2, S3: this.starts.S3, S4: this.starts.S4, S5: this.starts.S5}; // this.rotorPtrs = this.starts; let runcount = 1; const fast = this.control.fast; const slow = this.control.slow; // Print Headers result.printout += fast + " " + slow + "\n"; do { this.allCounters = [0, 0, 0, 0, 0]; this.ZbitsOneBack = [0, 0, 0, 0, 0]; this.XbitsOneBack = [0, 0, 0, 0, 0]; // Run full tape loop and process counters this.runTape(); // Only print result if larger than set total let fastRef = "00"; let slowRef = "00"; if (fast !== "") fastRef = this.rotorPtrs[fast].toString().padStart(2, "0"); if (slow !== "") slowRef = this.rotorPtrs[slow].toString().padStart(2, "0"); let printline = ""; for (let c=0;c<5;c++) { if (this.allCounters[c] > this.settotal) { printline += String.fromCharCode(c+97) + this.allCounters[c]+" "; } } if (printline !== "") { result.printout += fastRef + " " + slowRef + " : "; result.printout += printline; result.printout += "\n"; } // Step fast rotor if required if (fast !== "") { this.rotorPtrs[fast]++; if (this.rotorPtrs[fast] > ROTOR_SIZES[fast]) this.rotorPtrs[fast] = 1; } // Step slow rotor if fast rotor has returned to initial start position if (slow !== "" && this.rotorPtrs[fast] === this.starts[fast]) { this.rotorPtrs[slow]++; if (this.rotorPtrs[slow] > ROTOR_SIZES[slow]) this.rotorPtrs[slow] = 1; } runcount++; } while (JSON.stringify(this.rotorPtrs) !== JSON.stringify(this.starts)); result.counters = this.allCounters; result.runcount = runcount; return result; } /** * Run tape loop */ runTape() { let charZin = ""; this.Xptr = [this.rotorPtrs.X1, this.rotorPtrs.X2, this.rotorPtrs.X3, this.rotorPtrs.X4, this.rotorPtrs.X5]; this.Mptr = [this.rotorPtrs.M37, this.rotorPtrs.M61]; this.Sptr = [this.rotorPtrs.S1, this.rotorPtrs.S2, this.rotorPtrs.S3, this.rotorPtrs.S4, this.rotorPtrs.S5]; // Run full loop of all character on the input tape (Z) for (let i=0; i ROTOR_SIZES["X"+(r+1)]) this.Xptr[r] = 1; } if (this.totalmotor) { // Step Psi rotors for (let r=0; r<5; r++) { this.Sptr[r]++; if (this.Sptr[r] > ROTOR_SIZES["S"+(r+1)]) this.Sptr[r] = 1; } } // Move M37 rotor if M61 set if (this.rings.M[1][this.Mptr[1]-1]===1) this.Mptr[0]++; if (this.Mptr[0] > ROTOR_SIZES.M37) this.Mptr[0]=1; // Always move M61 rotor this.Mptr[1]++; if (this.Mptr[1] > ROTOR_SIZES.M61) this.Mptr[1]=1; } /** * Get Q bus inputs */ getQbusInputs(charZin) { // Zbits - the bits from the current character from the cipher tape. this.Zbits = this.ITAlookup[charZin].split(""); if (this.qbusin.Z === "Z") { // direct Z this.Qbits = this.Zbits; } else if (this.qbusin.Z === "ΔZ") { // delta Z, the Bitwise XOR of this character Zbits + last character Zbits for (let b=0;b<5;b++) { this.Qbits[b] = this.Zbits[b] ^ this.ZbitsOneBack[b]; } } this.ZbitsOneBack = this.Zbits.slice(); // copy value of object, not reference // Xbits - the current Chi wheel bits for (let b=0;b<5;b++) { this.Xbits[b] = this.rings.X[b+1][this.Xptr[b]-1]; } if (this.qbusin.Chi !== "") { if (this.qbusin.Chi === "Χ") { // direct X added to Qbits for (let b=0;b<5;b++) { this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b]; } } else if (this.qbusin.Chi === "ΔΧ") { // delta X for (let b=0;b<5;b++) { this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b]; this.Qbits[b] = this.Qbits[b] ^ this.XbitsOneBack[b]; } } } this.XbitsOneBack = this.Xbits.slice(); // Sbits - the current Psi wheel bits for (let b=0;b<5;b++) { this.Sbits[b] = this.rings.S[b+1][this.Sptr[b]-1]; } if (this.qbusin.Psi !== "") { if (this.qbusin.Psi === "Ψ") { // direct S added to Qbits for (let b=0;b<5;b++) { this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b]; } } else if (this.qbusin.Psi === "ΔΨ") { // delta S for (let b=0;b<5;b++) { this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b]; this.Qbits[b] = this.Qbits[b] ^ this.SbitsOneBack[b]; } } } this.SbitsOneBack = this.Sbits.slice(); } /** * Conditional impulse Q bus section */ runQbusProcessingConditional() { const cnt = [-1, -1, -1, -1, -1]; const numrows = this.qbusswitches.condition.length; for (let r=0;r= 0 && Qswitch[s] !== parseInt(this.Qbits[s], 10)) result = false; } // Check for NOT switch if (row.Negate) result = !result; // AND each row to get final result if (cnt[cPnt] === -1) { cnt[cPnt] = result; } else if (!result) { cnt[cPnt] = false; } } } // Negate the whole column, this allows A OR B by doing NOT(NOT A AND NOT B) for (let c=0;c<5;c++) { if (this.qbusswitches.condNegateAll && cnt[c] !== -1) cnt[c] = !cnt[c]; } return cnt; } /** * Addition of impulses Q bus section */ runQbusProcessingAddition(cnt) { const row = this.qbusswitches.addition[0]; const Qswitch = row.Qswitches.slice(); // To save making the arguments of this operation any larger, limiting addition counter to first one only // Colossus could actually add into any of the five counters. if (row.C1) { let addition = 0; for (let s=0;s<5;s++) { // XOR addition if (Qswitch[s]) { addition = addition ^ this.Qbits[s]; } } const equals = (row.Equals===""?-1:(row.Equals==="."?0:1)); if (addition === equals) { // AND with conditional rows to get final result if (cnt[0] === -1) cnt[0] = true; } else { cnt[0] = false; } } // Final check, check for addition section negate // then, if any column set, from top to bottom of rack, add to counter. for (let c=0;c<5;c++) { if (this.qbusswitches.addNegateAll && cnt[c] !== -1) cnt[c] = !cnt[c]; if (this.qbusswitches.totalMotor === "" || (this.qbusswitches.totalMotor === "x" && this.totalmotor === 0) || (this.qbusswitches.totalMotor === "." && this.totalmotor === 1)) { if (cnt[c] === true) this.allCounters[c]++; } } } /** * Initialise thyratron rings * These hold the pattern of 1s & 0s for each rotor on banks of thyraton GT1C valves which act as a one-bit store. */ initThyratrons(pattern) { this.rings = { X: { 1: INIT_PATTERNS[pattern].X[1].slice().reverse(), 2: INIT_PATTERNS[pattern].X[2].slice().reverse(), 3: INIT_PATTERNS[pattern].X[3].slice().reverse(), 4: INIT_PATTERNS[pattern].X[4].slice().reverse(), 5: INIT_PATTERNS[pattern].X[5].slice().reverse() }, M: { 1: INIT_PATTERNS[pattern].M[1].slice().reverse(), 2: INIT_PATTERNS[pattern].M[2].slice().reverse(), }, S: { 1: INIT_PATTERNS[pattern].S[1].slice().reverse(), 2: INIT_PATTERNS[pattern].S[2].slice().reverse(), 3: INIT_PATTERNS[pattern].S[3].slice().reverse(), 4: INIT_PATTERNS[pattern].S[4].slice().reverse(), 5: INIT_PATTERNS[pattern].S[5].slice().reverse() } }; } /** * Read argument bus switches X & . and convert to 1 & 0 */ readBusSwitches(row) { const output = [-1, -1, -1, -1, -1]; for (let c=0;c<5;c++) { if (row[c]===".") output[c] = 0; if (row[c]==="x") output[c] = 1; } return output; } }