From 77b098c5feb405fafdcdf274ccd188e43e1373db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karsten=20Silkenb=C3=A4umer?= Date: Sat, 2 Mar 2019 15:00:42 +0100 Subject: [PATCH] Add Bacon cipher decoding --- src/core/config/Categories.json | 1 + src/core/lib/Bacon.mjs | 37 ++++ src/core/operations/BaconCipherDecode.mjs | 108 ++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/BaconCipher.mjs | 246 ++++++++++++++++++++++ 5 files changed, 393 insertions(+) create mode 100644 src/core/lib/Bacon.mjs create mode 100644 src/core/operations/BaconCipherDecode.mjs create mode 100644 tests/operations/tests/BaconCipher.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8235ab10..846e3e8e 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -85,6 +85,7 @@ "Vigenère Decode", "To Morse Code", "From Morse Code", + "Bacon Cipher Decode", "Bifid Cipher Encode", "Bifid Cipher Decode", "Affine Cipher Encode", diff --git a/src/core/lib/Bacon.mjs b/src/core/lib/Bacon.mjs new file mode 100644 index 00000000..a2be4b56 --- /dev/null +++ b/src/core/lib/Bacon.mjs @@ -0,0 +1,37 @@ +/** + * Bacon resources. + * + * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + */ + +/** + * Bacon definitions. + */ + +export const BACON_ALPHABET_REDUCED = "ABCDEFGHIKLMNOPQRSTUWXYZ"; +export const BACON_ALPHABET_COMPLETE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +export const BACON_TRANSLATION_01 = "0/1"; +export const BACON_TRANSLATION_AB = "A/B"; +export const BACON_TRANSLATION_CASE = "Case"; +export const BACON_TRANSLATION_AMNZ = "A-M/N-Z first letter"; +export const BACON_TRANSLATIONS = [ + BACON_TRANSLATION_01, + BACON_TRANSLATION_AB, + BACON_TRANSLATION_CASE, + BACON_TRANSLATION_AMNZ, +]; +export const BACON_CLEARER_MAP = { + [BACON_TRANSLATIONS[0]]: /[^01]/g, + [BACON_TRANSLATIONS[1]]: /[^ABab]/g, + [BACON_TRANSLATIONS[2]]: /[^A-Za-z]/g, +}; +export const BACON_NORMALIZE_MAP = { + [BACON_TRANSLATIONS[1]]: { + "A": "0", + "B": "1", + "a": "0", + "b": "1" + }, +}; diff --git a/src/core/operations/BaconCipherDecode.mjs b/src/core/operations/BaconCipherDecode.mjs new file mode 100644 index 00000000..2d767538 --- /dev/null +++ b/src/core/operations/BaconCipherDecode.mjs @@ -0,0 +1,108 @@ +/** + * BaconCipher operation. + * +* @author kassi [kassi@users.noreply.github.com] +* @copyright Karsten Silkenbäumer 2019 +* @license Apache-2.0 +*/ + +import Operation from "../Operation"; +import { + BACON_ALPHABET_REDUCED, BACON_ALPHABET_COMPLETE, + BACON_TRANSLATION_CASE, BACON_TRANSLATION_AMNZ, BACON_TRANSLATIONS, BACON_CLEARER_MAP, BACON_NORMALIZE_MAP +} from "../lib/Bacon"; + +/** +* BaconCipherDecode operation +*/ +class BaconCipherDecode extends Operation { + /** + * BaconCipherDecode constructor + */ + constructor() { + super(); + + this.name = "Bacon Cipher Decode"; + this.module = "Default"; + this.description = "Bacon's cipher or the Baconian cipher is a method of steganography(a method of hiding a secret message as opposed to just a cipher) devised by Francis Bacon in 1605.[1][2][3] A message is concealed in the presentation of text, rather than its content."; + this.infoURL = "https://en.wikipedia.org/wiki/Bacon%27s_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Alphabet", + "type": "option", + "value": [BACON_ALPHABET_REDUCED, BACON_ALPHABET_COMPLETE] + }, + { + "name": "Translation", + "type": "option", + "value": BACON_TRANSLATIONS + }, + { + "name": "Invert Translation", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {String} input + * @param {Object[]} args + * @returns {String} + */ + run(input, args) { + const [alphabet, translation, invert] = args; + // split text into groups of 5 characters + + // remove invalid characters + input = input.replace(BACON_CLEARER_MAP[translation], ""); + // normalize to unique alphabet + if (BACON_NORMALIZE_MAP[translation] !== undefined) { + input = input.replace(/./g, function (c) { + return BACON_NORMALIZE_MAP[translation][c]; + }); + } else if (translation === BACON_TRANSLATION_CASE) { + const codeA = "A".charCodeAt(0); + const codeZ = "Z".charCodeAt(0); + input = input.replace(/./g, function (c) { + const code = c.charCodeAt(0); + if (code >= codeA && code <= codeZ) { + return "1"; + } else { + return "0"; + } + }); + } else if (translation === BACON_TRANSLATION_AMNZ) { + const words = input.split(" "); + const letters = words.map(function (e) { + const code = e[0].toUpperCase().charCodeAt(0); + return code >= "N".charCodeAt(0) ? "1" : "0"; + }); + input = letters.join(""); + } + + if (invert) { + input = input.replace(/./g, function (c) { + return { + "0": "1", + "1": "0" + }[c]; + }); + } + + // group into 5 + const inputArray = input.match(/(.{5})/g) || []; + + let output = ""; + for (let index = 0; index < inputArray.length; index++) { + const code = inputArray[index]; + const number = parseInt(code, 2); + output += number < alphabet.length ? alphabet[number] : "?"; + } + return output; + } +} + +export default BaconCipherDecode; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index fb68ed9c..e61f886a 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -26,6 +26,7 @@ global.ENVIRONMENT_IS_WEB = function() { import TestRegister from "./TestRegister"; import "./tests/BCD"; import "./tests/BSON"; +import "./tests/BaconCipher"; import "./tests/Base58"; import "./tests/Base64"; import "./tests/Base62"; diff --git a/tests/operations/tests/BaconCipher.mjs b/tests/operations/tests/BaconCipher.mjs new file mode 100644 index 00000000..b4b63b8f --- /dev/null +++ b/tests/operations/tests/BaconCipher.mjs @@ -0,0 +1,246 @@ +/** + * BaconCipher tests. + * + * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + */ +import TestRegister from "../TestRegister"; +import { BACON_ALPHABET_REDUCED, BACON_ALPHABET_COMPLETE, BACON_TRANSLATIONS } from "../../../src/core/lib/Bacon"; + +const alphabets = [BACON_ALPHABET_REDUCED, BACON_ALPHABET_COMPLETE]; +const translations = BACON_TRANSLATIONS; + +TestRegister.addTests([ + { + name: "Bacon Decode: no input", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[0], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet 0/1", + input: "00011 00100 00010 01101 00011 01000 01100 00110 00001 00000 00010 01101 01100 10100 01101 10000 01001 10001", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[0], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet 0/1 inverse", + input: "11100 11011 11101 10010 11100 10111 10011 11001 11110 11111 11101 10010 10011 01011 10010 01111 10110 01110", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[0], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B lower case", + input: "aaabb aabaa aaaba abbab aaabb abaaa abbaa aabba aaaab aaaaa aaaba abbab abbaa babaa abbab baaaa abaab baaab", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B lower case inverse", + input: "bbbaa bbabb bbbab baaba bbbaa babbb baabb bbaab bbbba bbbbb bbbab baaba baabb ababb baaba abbbb babba abbba", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B upper case", + input: "AAABB AABAA AAABA ABBAB AAABB ABAAA ABBAA AABBA AAAAB AAAAA AAABA ABBAB ABBAA BABAA ABBAB BAAAA ABAAB BAAAB", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet A/B upper case inverse", + input: "BBBAA BBABB BBBAB BAABA BBBAA BABBB BAABB BBAAB BBBBA BBBBB BBBAB BAABA BAABB ABABB BAABA ABBBB BABBA ABBBA", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code", + input: "thiS IsaN exampLe oF ThE bacON cIpher WIth upPPercasE letters tRanSLaTiNG to OnEs anD LoWErcase To zERoes. KS", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[2], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code inverse", + input: "THIs iS An EXAMPlE Of tHe BACon CiPHER wiTH UPppERCASe LETTERS TrANslAtIng TO oNeS ANd lOweRCASE tO ZerOES. ks", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[2], true] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code", + input: "A little example of the Bacon Cipher to be decoded. It is a working example and shorter than my others, but it anyways works tremendously. And just that's important, correct?", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[3], false] + } + ], + }, + { + name: "Bacon Decode: reduced alphabet case code inverse", + input: "Well, there's now another example which will be not only strange to read but sound weird for everyone not knowing what the thing is about. Nevertheless, works great out of the box.", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[0], translations[3], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet 0/1", + input: "00011 00100 00010 01110 00011 01000 01101 00110 00001 00000 00010 01110 01101 10110 01110 10001 01010 10010", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[0], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet 0/1 inverse", + input: "11100 11011 11101 10001 11100 10111 10010 11001 11110 11111 11101 10001 10010 01001 10001 01110 10101 01101", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[0], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B lower case", + input: "aaabb aabaa aaaba abbba aaabb abaaa abbab aabba aaaab aaaaa aaaba abbba abbab babba abbba baaab ababa baaba", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B lower case inverse", + input: "bbbaa bbabb bbbab baaab bbbaa babbb baaba bbaab bbbba bbbbb bbbab baaab baaba abaab baaab abbba babab abbab", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B upper case", + input: "AAABB AABAA AAABA ABBBA AAABB ABAAA ABBAB AABBA AAAAB AAAAA AAABA ABBBA ABBAB BABBA ABBBA BAAAB ABABA BAABA", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[1], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet A/B upper case inverse", + input: "BBBAA BBABB BBBAB BAAAB BBBAA BABBB BAABA BBAAB BBBBA BBBBB BBBAB BAAAB BAABA ABAAB BAAAB ABBBA BABAB ABBAB", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[1], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code", + input: "thiS IsaN exampLe oF THe bacON cIpher WItH upPPercasE letters tRanSLAtiNG tO OnES anD LOwErcaSe To ZeRoeS. kS", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[2], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code inverse", + input: "THIs iSAn EXAMPlE Of thE BACon CiPHER wiTh UPppERCASe LETTERS TrANslaTIng To zEroES and LoWERcAsE tO oNEs. Ks", + expectedOutput: "DECODINGBACONWORKS", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[2], true] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code", + input: "A little example of the Bacon Cipher to be decoded. It is a working example and shorter than the first, but it anyways works tremendously. And just that's important, correct?", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[3], false] + } + ], + }, + { + name: "Bacon Decode: complete alphabet case code inverse", + input: "Well, there's now another example which will be not only strange to read but sound weird for everyone knowing nothing what the thing is about. Nevertheless, works great out of the box.", + expectedOutput: "DECODE", + recipeConfig: [ + { + op: "Bacon Cipher Decode", + args: [alphabets[1], translations[3], true] + } + ], + }, +]);