{"record":{"id":"51c0110536bbeb8b","repo":"gchq/CyberChef","slug":"rotor-steps-must-be-0-26-unique-uppercase-letters","errorCode":null,"errorMessage":"Rotor steps must be 0-26 unique uppercase letters","messagePattern":"Rotor steps must be 0-26 unique uppercase letters","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/Enigma.mjs","lineNumber":97,"sourceCode":"\n/**\n * A rotor in the Enigma machine.\n */\nexport class Rotor {\n    /**\n     * Rotor constructor.\n     *\n     * @param {string} wiring - A 26 character string of the wiring order.\n     * @param {string} steps - A 0..26 character string of stepping points.\n     * @param {char} ringSetting - The ring setting.\n     * @param {char} initialPosition - The initial position of the rotor.\n     */\n    constructor(wiring, steps, ringSetting, initialPosition) {\n        if (!/^[A-Z]{26}$/.test(wiring)) {\n            throw new OperationError(\"Rotor wiring must be 26 unique uppercase letters\");\n        }\n        if (!/^[A-Z]{0,26}$/.test(steps)) {\n            throw new OperationError(\"Rotor steps must be 0-26 unique uppercase letters\");\n        }\n        if (!/^[A-Z]$/.test(ringSetting)) {\n            throw new OperationError(\"Rotor ring setting must be exactly one uppercase letter\");\n        }\n        if (!/^[A-Z]$/.test(initialPosition)) {\n            throw new OperationError(\"Rotor initial position must be exactly one uppercase letter\");\n        }\n        this.map = new Array(26);\n        this.revMap = new Array(26);\n        const uniq = {};\n        for (let i=0; i<LETTERS.length; i++) {\n            const a = a2i(LETTERS[i]);\n            const b = a2i(wiring[i]);\n            this.map[a] = b;\n            this.revMap[b] = a;\n            uniq[b] = true;\n        }\n        if (Object.keys(uniq).length !== LETTERS.length) {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/Enigma.mjs#L79-L115","documentation":"Thrown by the Rotor constructor when the steps argument fails /^[A-Z]{0,26}$/ — i.e. it must be 0 to 26 uppercase ASCII letters. steps lists the rotor positions at which the adjacent rotor advances (the turnover points).","triggerScenarios":"new Rotor(..., steps, ...) where steps contains lowercase letters, digits, symbols, the '<' marker, whitespace, or more than 26 characters. An empty string is allowed (rotor that never triggers a turnover).","commonSituations":"Passing the full ROTORS value (including wiring) as the steps argument; extracting steps but leaving the '<' prefix; lowercase turnover letters; passing null/undefined.","solutions":["Pass only the step-letters portion from after '<' in the ROTORS value (e.g. value.split('<')[1] || '').","Ensure steps is 0-26 uppercase A-Z characters.","Validate against /^[A-Z]{0,26}$/ before constructing."],"exampleFix":"// before\nnew Rotor('EKMFLGDQVZNTOWYHXUSPAIBRCJ', '<R', 'A', 'A'); // '<' not allowed\n\n// after\nnew Rotor('EKMFLGDQVZNTOWYHXUSPAIBRCJ', 'R', 'A', 'A');","handlingStrategy":"validation","validationCode":"function rotorStepsFromValue(value) {\n  // returns the part after '<', or '' if none\n  const parts = value.split(\"<\");\n  return parts.length > 1 ? parts[1] : \"\";\n}\nconst steps = rotorStepsFromValue(rotorValue);\nif (!/^[A-Z]{0,26}$/.test(steps)) {\n  throw new Error(\"Rotor steps must be 0-26 uppercase letters.\");\n}","typeGuard":"function isRotorStepsFormat(s) {\n  return typeof s === \"string\" && /^[A-Z]{0,26}$/.test(s);\n}","tryCatchPattern":null,"preventionTips":["Derive steps from the part after '<' in a ROTORS value, never the whole string.","Allow empty string for rotors with no turnover points.","Validate the regex before constructing."],"tags":["enigma","cryptography","input-validation","rotor"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}