Merge branch 'feature/bombe' into feature/typex

This commit is contained in:
s2224834 2019-02-28 17:00:33 +00:00
commit 2be642e4c9
61 changed files with 4621 additions and 635 deletions

View File

@ -1,10 +1,12 @@
{
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 9,
"ecmaFeatures": {
"impliedStrict": true
},
"sourceType": "module"
"sourceType": "module",
"allowImportExportEverywhere": true
},
"env": {
"browser": true,

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ docs/*
!docs/*.ico
.vscode
.*.swp
.DS_Store
src/core/config/modules/*
src/core/config/OperationConfig.json
src/core/operations/index.mjs

View File

@ -2,6 +2,24 @@
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
### [8.24.0] - 2019-02-08
- 'DNS over HTTPS' operation added [@h345983745] | [#489]
### [8.23.1] - 2019-01-18
- 'Convert co-ordinate format' operation added [@j433866] | [#476]
### [8.23.0] - 2019-01-18
- 'YARA Rules' operation added [@artemisbot] | [#468]
### [8.22.0] - 2019-01-10
- 'Subsection' operation added [@j433866] | [#467]
### [8.21.0] - 2019-01-10
- 'To Case Insensitive Regex' and 'From Case Insensitive Regex' operations added [@masq] | [#461]
### [8.20.0] - 2019-01-09
- 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455]
### [8.19.0] - 2018-12-30
- UI test suite added to confirm that the app loads correctly in a reasonable time and that various operations from each module can be run [@n1474335] | [#458]
@ -88,6 +106,12 @@ All major and minor version changes will be documented in this file. Details of
[8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0
[8.23.1]: https://github.com/gchq/CyberChef/releases/tag/v8.23.1
[8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0
[8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0
[8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0
[8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0
[8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0
[8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0
[8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0
@ -117,6 +141,7 @@ All major and minor version changes will be documented in this file. Details of
[@d98762625]: https://github.com/d98762625
[@j433866]: https://github.com/j433866
[@GCHQ77703]: https://github.com/GCHQ77703
[@h345983745]: https://github.com/h345983745
[@artemisbot]: https://github.com/artemisbot
[@picapi]: https://github.com/picapi
[@Dachande663]: https://github.com/Dachande663
@ -130,6 +155,7 @@ All major and minor version changes will be documented in this file. Details of
[@tcode2k16]: https://github.com/tcode2k16
[@Cynser]: https://github.com/Cynser
[@anthony-arnold]: https://github.com/anthony-arnold
[@masq]: https://github.com/masq
[#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173
@ -159,4 +185,10 @@ All major and minor version changes will be documented in this file. Details of
[#446]: https://github.com/gchq/CyberChef/pull/446
[#448]: https://github.com/gchq/CyberChef/pull/448
[#449]: https://github.com/gchq/CyberChef/pull/449
[#455]: https://github.com/gchq/CyberChef/pull/455
[#458]: https://github.com/gchq/CyberChef/pull/458
[#461]: https://github.com/gchq/CyberChef/pull/461
[#467]: https://github.com/gchq/CyberChef/pull/467
[#468]: https://github.com/gchq/CyberChef/pull/468
[#476]: https://github.com/gchq/CyberChef/pull/476
[#489]: https://github.com/gchq/CyberChef/pull/489

View File

@ -194,7 +194,8 @@ module.exports = function (grunt) {
sitemap: "./src/web/static/sitemap.js"
}, moduleEntryPoints),
output: {
path: __dirname + "/build/prod"
path: __dirname + "/build/prod",
globalObject: "this"
},
resolve: {
alias: {
@ -299,6 +300,9 @@ module.exports = function (grunt) {
"./config/modules/OpModules": "./config/modules/Default"
}
},
output: {
globalObject: "this",
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS),
new HtmlWebpackPlugin({
@ -391,9 +395,7 @@ module.exports = function (grunt) {
generateConfig: {
command: [
"echo '\n--- Regenerating config files. ---'",
"mkdir -p src/core/config/modules",
"echo 'export default {};\n' > src/core/config/modules/OpModules.mjs",
"echo '[]\n' > src/core/config/OperationConfig.json",
"echo [] > src/core/config/OperationConfig.json",
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs",
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs",
"echo '--- Config scripts finished. ---\n'"

View File

@ -15,6 +15,7 @@ module.exports = function(api) {
}]
],
"plugins": [
"babel-plugin-syntax-dynamic-import",
["babel-plugin-transform-builtin-extend", {
"globals": ["Error"]
}]

377
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "8.19.5",
"version": "8.24.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -766,6 +766,22 @@
"semver": "^5.3.0"
}
},
"@babel/runtime-corejs2": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.3.1.tgz",
"integrity": "sha512-YpO13776h3e6Wy8dl2J8T9Qwlvopr+b4trCEhHE+yek6yIqV8sx6g3KozdHMbXeBpjosbPi+Ii5Z7X9oXFHUKA==",
"requires": {
"core-js": "^2.5.7",
"regenerator-runtime": "^0.12.0"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
"@babel/template": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz",
@ -1496,9 +1512,9 @@
"dev": true
},
"ansi-escapes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"dev": true
},
"ansi-html": {
@ -1571,7 +1587,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -1615,7 +1631,7 @@
},
"array-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
"resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true
},
@ -1700,7 +1716,7 @@
},
"util": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"dev": true,
"requires": {
@ -1848,7 +1864,7 @@
},
"axios": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"dev": true,
"requires": {
@ -1866,6 +1882,32 @@
"js-tokens": "^3.0.2"
}
},
"babel-eslint": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz",
"integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/traverse": "^7.0.0",
"@babel/types": "^7.0.0",
"eslint-scope": "3.7.1",
"eslint-visitor-keys": "^1.0.0"
},
"dependencies": {
"eslint-scope": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz",
"integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
}
}
},
"babel-loader": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.4.tgz",
@ -1886,6 +1928,12 @@
"babel-runtime": "^6.22.0"
}
},
"babel-plugin-syntax-dynamic-import": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
"integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=",
"dev": true
},
"babel-plugin-transform-builtin-extend": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz",
@ -2087,9 +2135,9 @@
"dev": true
},
"bignumber.js": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.0.1.tgz",
"integrity": "sha512-zAySveTJXkgLYCBi0b14xzfnOs+f3G6x36I8w2a1+PFQpWk/dp0mI0F+ZZK2bu+3ELewDcSyP+Cfq++NcHX7sg=="
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.0.2.tgz",
"integrity": "sha512-EiuvFrnbv0jFixEQ9f58jo7X0qI2lNGIr/MxntmVzQc5JUweDSh8y8hbTCAomFtqwUPIOWcLXP0VEOSZTG7FFw=="
},
"binary-extensions": {
"version": "1.12.0",
@ -2286,7 +2334,7 @@
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"dev": true,
"requires": {
@ -2323,7 +2371,7 @@
},
"browserify-rsa": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
"dev": true,
"requires": {
@ -2388,7 +2436,7 @@
},
"buffer": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"dev": true,
"requires": {
@ -2518,19 +2566,10 @@
"unset-value": "^1.0.0"
}
},
"caller-path": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
"integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
"dev": true,
"requires": {
"callsites": "^0.2.0"
}
},
"callsites": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz",
"integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==",
"dev": true
},
"camel-case": {
@ -2551,7 +2590,7 @@
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true,
"requires": {
@ -2600,7 +2639,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
@ -2779,7 +2818,7 @@
},
"string-width": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@ -3133,7 +3172,7 @@
},
"create-hash": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"dev": true,
"requires": {
@ -3146,7 +3185,7 @@
},
"create-hmac": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"dev": true,
"requires": {
@ -3322,7 +3361,7 @@
},
"regexpu-core": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
"integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
"dev": true,
"requires": {
@ -3339,7 +3378,7 @@
},
"regjsparser": {
"version": "0.1.5",
"resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
"integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
"dev": true,
"requires": {
@ -4062,9 +4101,9 @@
}
},
"eslint": {
"version": "5.11.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.11.1.tgz",
"integrity": "sha512-gOKhM8JwlFOc2acbOrkYR05NW8M6DCMSvfcJiBB5NDxRE1gv8kbvxKaC9u69e6ZGEMWXcswA/7eKR229cEIpvg==",
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.12.1.tgz",
"integrity": "sha512-54NV+JkTpTu0d8+UYSA8mMKAG4XAsaOrozA9rCW7tgneg1mevcL7wIotPC+fZ0SkWwdhNqoXoxnQCTBp7UvTsg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
@ -4084,6 +4123,7 @@
"glob": "^7.1.2",
"globals": "^11.7.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"inquirer": "^6.1.0",
"js-yaml": "^3.12.0",
@ -4098,7 +4138,6 @@
"pluralize": "^7.0.0",
"progress": "^2.0.0",
"regexpp": "^2.0.1",
"require-uncached": "^1.0.3",
"semver": "^5.5.1",
"strip-ansi": "^4.0.0",
"strip-json-comments": "^2.0.1",
@ -4122,9 +4161,9 @@
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
@ -4142,9 +4181,9 @@
}
},
"globals": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz",
"integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==",
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz",
"integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==",
"dev": true
},
"ms": {
@ -4271,7 +4310,7 @@
},
"source-map": {
"version": "0.1.43",
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
"integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
"requires": {
"amdefine": ">=0.0.4"
@ -4461,7 +4500,7 @@
"dependencies": {
"source-map": {
"version": "0.5.0",
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz",
"integrity": "sha1-D+llA6yGpa213mP05BKuSHLNvoY=",
"dev": true
}
@ -4616,18 +4655,6 @@
}
}
},
"extract-text-webpack-plugin": {
"version": "4.0.0-beta.0",
"resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-4.0.0-beta.0.tgz",
"integrity": "sha512-Hypkn9jUTnFr0DpekNam53X47tXn3ucY08BQumv7kdGgeVUBLq3DJHJTi6HNxv4jl9W+Skxjz9+RnK0sJyqqjA==",
"dev": true,
"requires": {
"async": "^2.4.1",
"loader-utils": "^1.1.0",
"schema-utils": "^0.4.5",
"webpack-sources": "^1.1.0"
}
},
"extract-zip": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
@ -4794,7 +4821,7 @@
},
"finalhandler": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
"dev": true,
"requires": {
@ -4829,7 +4856,7 @@
},
"findup-sync": {
"version": "0.3.0",
"resolved": "http://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz",
"integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=",
"dev": true,
"requires": {
@ -4896,7 +4923,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -5019,7 +5046,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -5660,7 +5687,7 @@
},
"string-width": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@ -5680,6 +5707,11 @@
"globule": "^1.0.0"
}
},
"geodesy": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/geodesy/-/geodesy-1.1.3.tgz",
"integrity": "sha512-H/0XSd1KjKZGZ2YGZcOYzRyY/foYAawwTEumNSo+YUwf+u5d4CfvBRg2i2Qimrx9yUEjWR8hLvMnhghuVFN0Zg=="
},
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
@ -5823,7 +5855,7 @@
},
"globby": {
"version": "6.1.0",
"resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true,
"requires": {
@ -5935,7 +5967,7 @@
},
"resolve": {
"version": "1.1.7",
"resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
"dev": true
}
@ -5981,7 +6013,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
}
@ -6125,7 +6157,7 @@
"dependencies": {
"colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
}
@ -6189,7 +6221,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
}
@ -6409,7 +6441,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -6709,6 +6741,16 @@
"import-from": "^2.1.0"
}
},
"import-fresh": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz",
"integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"import-from": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz",
@ -6860,21 +6902,21 @@
}
},
"inquirer": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz",
"integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz",
"integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==",
"dev": true,
"requires": {
"ansi-escapes": "^3.0.0",
"chalk": "^2.0.0",
"ansi-escapes": "^3.2.0",
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.0",
"external-editor": "^3.0.3",
"figures": "^2.0.0",
"lodash": "^4.17.10",
"lodash": "^4.17.11",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.1.0",
"rxjs": "^6.4.0",
"string-width": "^2.1.0",
"strip-ansi": "^5.0.0",
"through": "^2.3.6"
@ -6896,9 +6938,9 @@
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
@ -6970,7 +7012,7 @@
},
"is-accessor-descriptor": {
"version": "0.1.6",
"resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true,
"requires": {
@ -7025,7 +7067,7 @@
},
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true,
"requires": {
@ -7429,7 +7471,7 @@
},
"underscore": {
"version": "1.8.3",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
"dev": true
}
@ -7755,6 +7797,11 @@
"resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz",
"integrity": "sha1-ZMTwJfF/1Tv7RXY/rrFvAVp0dVA="
},
"libyara-wasm": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/libyara-wasm/-/libyara-wasm-0.0.11.tgz",
"integrity": "sha512-rglapPFo0IHPNksWYQXI8oqftXYj5mOGOf4BXtbSySVRX71pro4BehNjJ5qEpjYx+roGvNkcAD9zCsitA08sxw=="
},
"livereload-js": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz",
@ -8222,7 +8269,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -8333,6 +8380,30 @@
"dom-walk": "^0.1.0"
}
},
"mini-css-extract-plugin": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz",
"integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==",
"dev": true,
"requires": {
"loader-utils": "^1.1.0",
"schema-utils": "^1.0.0",
"webpack-sources": "^1.1.0"
},
"dependencies": {
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"dev": true,
"requires": {
"ajv": "^6.1.0",
"ajv-errors": "^1.0.0",
"ajv-keywords": "^3.1.0"
}
}
}
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@ -8471,7 +8542,7 @@
"dependencies": {
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"dev": true,
"optional": true
@ -8591,7 +8662,7 @@
},
"multimatch": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",
"integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=",
"dev": true,
"requires": {
@ -8603,7 +8674,7 @@
},
"mute-stream": {
"version": "0.0.7",
"resolved": "http://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
@ -8922,12 +8993,12 @@
"dependencies": {
"colors": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
"resolved": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
"integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q="
},
"underscore": {
"version": "1.1.7",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz",
"integrity": "sha1-QLq4S60Z0jAJbo1u9ii/8FXYPbA="
}
}
@ -9426,7 +9497,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -9444,6 +9515,15 @@
"no-case": "^2.2.0"
}
},
"parent-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz",
"integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
},
"parse-asn1": {
"version": "5.1.1",
"resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
@ -9514,7 +9594,7 @@
},
"path-browserify": {
"version": "0.0.0",
"resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
"integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
"dev": true
},
@ -9756,7 +9836,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
}
@ -10152,13 +10232,13 @@
"dependencies": {
"async": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
"resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz",
"integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=",
"dev": true
},
"winston": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz",
"resolved": "http://registry.npmjs.org/winston/-/winston-2.1.1.tgz",
"integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=",
"dev": true,
"requires": {
@ -10173,7 +10253,7 @@
"dependencies": {
"colors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
"resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
"dev": true
},
@ -10490,7 +10570,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -10756,16 +10836,6 @@
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"dev": true
},
"require-uncached": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
"integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
"dev": true,
"requires": {
"caller-path": "^0.1.0",
"resolve-from": "^1.0.0"
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@ -10783,7 +10853,7 @@
"dependencies": {
"underscore": {
"version": "1.6.0",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
"dev": true
}
@ -10816,9 +10886,9 @@
}
},
"resolve-from": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
"integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"resolve-pkg": {
@ -10904,9 +10974,9 @@
}
},
"rxjs": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
"integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==",
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
"integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
@ -11102,7 +11172,7 @@
"dependencies": {
"source-map": {
"version": "0.4.4",
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"dev": true,
"requires": {
@ -11311,9 +11381,9 @@
}
},
"slice-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz",
"integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
@ -11710,7 +11780,7 @@
},
"split2": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/split2/-/split2-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/split2/-/split2-1.1.1.tgz",
"integrity": "sha1-Fi2bGIZfAqsvKtlYVSLbm1TEgfk=",
"dev": true,
"requires": {
@ -11719,7 +11789,7 @@
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
@ -11851,7 +11921,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -11868,7 +11938,7 @@
},
"stream-browserify": {
"version": "2.0.1",
"resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
"dev": true,
"requires": {
@ -11954,7 +12024,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -12071,9 +12141,31 @@
},
"supports-color": {
"version": "2.0.0",
"resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
},
"svg-url-loader": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/svg-url-loader/-/svg-url-loader-2.3.2.tgz",
"integrity": "sha1-3YaybBn+O5FPBOoQ7zlZTq3gRGQ=",
"dev": true,
"requires": {
"file-loader": "1.1.11",
"loader-utils": "1.1.0"
},
"dependencies": {
"file-loader": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz",
"integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==",
"dev": true,
"requires": {
"loader-utils": "^1.0.2",
"schema-utils": "^0.4.5"
}
}
}
},
"symbol-tree": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
@ -12081,21 +12173,21 @@
"dev": true
},
"table": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/table/-/table-5.1.1.tgz",
"integrity": "sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/table/-/table-5.2.2.tgz",
"integrity": "sha512-f8mJmuu9beQEDkKHLzOv4VxVYlU68NpdzjbGPl69i4Hx0sTopJuNxuzJd17iV2h24dAfa93u794OnDA5jqXvfQ==",
"dev": true,
"requires": {
"ajv": "^6.6.1",
"lodash": "^4.17.11",
"slice-ansi": "2.0.0",
"slice-ansi": "^2.0.0",
"string-width": "^2.1.1"
},
"dependencies": {
"ajv": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz",
"integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==",
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz",
"integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
@ -12534,7 +12626,7 @@
},
"tty-browserify": {
"version": "0.0.0",
"resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
"dev": true
},
@ -12609,7 +12701,7 @@
},
"underscore": {
"version": "1.7.0",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
"integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk="
},
"underscore-contrib": {
@ -12623,7 +12715,7 @@
"dependencies": {
"underscore": {
"version": "1.6.0",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
"dev": true
}
@ -12938,7 +13030,7 @@
"dependencies": {
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
"dev": true
},
@ -13008,7 +13100,7 @@
},
"vm-browserify": {
"version": "0.0.4",
"resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
"integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
"dev": true,
"requires": {
@ -13666,14 +13758,14 @@
"dependencies": {
"async": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
"resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz",
"integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=",
"dev": true,
"optional": true
},
"colors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
"resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
"dev": true,
"optional": true
@ -13725,7 +13817,7 @@
},
"string-width": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@ -13821,9 +13913,12 @@
"integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ=="
},
"xregexp": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.0.tgz",
"integrity": "sha512-IyMa7SVe9FyT4WbQVW3b95mTLVceHhLEezQ02+QMvmIqDnKTxk0MLWIQPSW2MXAr1zQb+9yvwYhcyQULneh3wA=="
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.4.tgz",
"integrity": "sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA==",
"requires": {
"@babel/runtime-corejs2": "^7.2.0"
}
},
"xtend": {
"version": "4.0.1",
@ -13880,7 +13975,7 @@
},
"string-width": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {

View File

@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "8.19.5",
"version": "8.24.2",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@ -33,14 +33,15 @@
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.2.3",
"autoprefixer": "^9.4.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.4",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"bootstrap": "^4.2.1",
"chromedriver": "^2.45.0",
"colors": "^1.3.3",
"css-loader": "^2.1.0",
"eslint": "^5.11.1",
"eslint": "^5.12.1",
"exports-loader": "^0.7.0",
"extract-text-webpack-plugin": "^4.0.0-alpha0",
"file-loader": "^3.0.1",
"grunt": "^1.0.3",
"grunt-accessibility": "~6.0.0",
@ -58,6 +59,7 @@
"imports-loader": "^0.8.0",
"ink-docstrap": "^1.3.2",
"jsdoc-babel": "^0.5.0",
"mini-css-extract-plugin": "^0.5.0",
"nightwatch": "^1.0.18",
"node-sass": "^4.11.0",
"postcss-css-variables": "^0.11.0",
@ -80,7 +82,7 @@
"babel-plugin-transform-builtin-extend": "1.1.2",
"babel-polyfill": "^6.26.0",
"bcryptjs": "^2.4.3",
"bignumber.js": "^8.0.1",
"bignumber.js": "^8.0.2",
"bootstrap-colorpicker": "^2.5.3",
"bootstrap-material-design": "^4.1.1",
"bson": "^4.0.1",
@ -95,6 +97,7 @@
"esprima": "^4.0.1",
"exif-parser": "^0.1.12",
"file-saver": "^2.0.0",
"geodesy": "^1.1.3",
"highlight.js": "^9.13.1",
"jimp": "^0.6.0",
"jquery": "^3.3.1",
@ -106,6 +109,7 @@
"jsqr": "^1.1.1",
"jsrsasign": "8.0.12",
"kbpgp": "^2.0.82",
"libyara-wasm": "0.0.11",
"lodash": "^4.17.11",
"loglevel": "^1.6.1",
"loglevel-message-prefix": "^3.0.0",
@ -129,7 +133,7 @@
"vkbeautify": "^0.99.3",
"xmldom": "^0.1.27",
"xpath": "0.0.27",
"xregexp": "^4.2.0",
"xregexp": "^4.2.4",
"zlibjs": "^0.3.1"
},
"scripts": {

View File

@ -157,9 +157,9 @@ class Chef {
* @param {number} pos.end - The end offset.
* @returns {Object}
*/
calculateHighlights(recipeConfig, direction, pos) {
async calculateHighlights(recipeConfig, direction, pos) {
const recipe = new Recipe(recipeConfig);
const highlights = recipe.generateHighlightList();
const highlights = await recipe.generateHighlightList();
if (!highlights) return false;

View File

@ -157,8 +157,8 @@ async function getDishAs(data) {
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
function calculateHighlights(recipeConfig, direction, pos) {
pos = self.chef.calculateHighlights(recipeConfig, direction, pos);
async function calculateHighlights(recipeConfig, direction, pos) {
pos = await self.chef.calculateHighlights(recipeConfig, direction, pos);
self.postMessage({
action: "highlightsCalculated",

View File

@ -168,7 +168,7 @@ class Dish {
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
break;
case Dish.BIG_NUMBER:
this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
this.value = BigNumber.isBigNumber(this.value) ? Utils.strToByteArray(this.value.toFixed()) : [];
break;
case Dish.JSON:
this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value, null, 4)) : [];
@ -265,7 +265,7 @@ class Dish {
case Dish.ARRAY_BUFFER:
return this.value instanceof ArrayBuffer;
case Dish.BIG_NUMBER:
return this.value instanceof BigNumber;
return BigNumber.isBigNumber(this.value);
case Dish.JSON:
// All values can be serialised in some manner, so we return true in all cases
return true;

View File

@ -23,6 +23,7 @@ class Ingredient {
this._value = null;
this.disabled = false;
this.hint = "";
this.rows = 0;
this.toggleValues = [];
this.target = null;
this.defaultIndex = 0;
@ -45,6 +46,7 @@ class Ingredient {
this.defaultValue = ingredientConfig.value;
this.disabled = !!ingredientConfig.disabled;
this.hint = ingredientConfig.hint || false;
this.rows = ingredientConfig.rows || false;
this.toggleValues = ingredientConfig.toggleValues;
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;

View File

@ -23,6 +23,7 @@ class Operation {
this._breakpoint = false;
this._disabled = false;
this._flowControl = false;
this._manualBake = false;
this._ingList = [];
// Public fields
@ -179,6 +180,7 @@ class Operation {
if (ing.toggleValues) conf.toggleValues = ing.toggleValues;
if (ing.hint) conf.hint = ing.hint;
if (ing.rows) conf.rows = ing.rows;
if (ing.disabled) conf.disabled = ing.disabled;
if (ing.target) conf.target = ing.target;
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
@ -281,6 +283,7 @@ class Operation {
return this._flowControl;
}
/**
* Set whether this Operation is a flowcontrol op.
*
@ -290,6 +293,26 @@ class Operation {
this._flowControl = !!value;
}
/**
* Returns true if this Operation should not trigger AutoBake.
*
* @returns {boolean}
*/
get manualBake() {
return this._manualBake;
}
/**
* Set whether this Operation should trigger AutoBake.
*
* @param {boolean} value
*/
set manualBake(value) {
this._manualBake = !!value;
}
}
export default Operation;

View File

@ -4,12 +4,15 @@
* @license Apache-2.0
*/
import OpModules from "./config/modules/OpModules";
import OperationConfig from "./config/OperationConfig.json";
import OperationError from "./errors/OperationError";
import Operation from "./Operation";
import DishError from "./errors/DishError";
import log from "loglevel";
// Cache container for modules
let modules = null;
/**
* The Recipe controls a list of Operations and the Dish they operate on.
*/
@ -36,16 +39,43 @@ class Recipe {
* @param {Object} recipeConfig
*/
_parseConfig(recipeConfig) {
for (let c = 0; c < recipeConfig.length; c++) {
const operationName = recipeConfig[c].op;
const opConf = OperationConfig[operationName];
const opObj = OpModules[opConf.module][operationName];
const operation = new opObj();
operation.ingValues = recipeConfig[c].args;
operation.breakpoint = recipeConfig[c].breakpoint;
operation.disabled = recipeConfig[c].disabled;
this.addOperation(operation);
recipeConfig.forEach(c => {
this.opList.push({
name: c.op,
module: OperationConfig[c.op].module,
ingValues: c.args,
breakpoint: c.breakpoint,
disabled: c.disabled,
});
});
}
/**
* Populate elements of opList with operation instances.
* Dynamic import here removes top-level cyclic dependency issue.
*
* @private
*/
async _hydrateOpList() {
if (!modules) {
// Using Webpack Magic Comments to force the dynamic import to be included in the main chunk
// https://webpack.js.org/api/module-methods/
modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules");
modules = modules.default;
}
this.opList = this.opList.map(o => {
if (o instanceof Operation) {
return o;
} else {
const op = new modules[o.module][o.name]();
op.ingValues = o.ingValues;
op.breakpoint = o.breakpoint;
op.disabled = o.disabled;
return op;
}
});
}
@ -55,7 +85,10 @@ class Recipe {
* @returns {Object[]}
*/
get config() {
return this.opList.map(op => op.config);
return this.opList.map(op => ({
op: op.name,
args: op.ingValues,
}));
}
@ -75,7 +108,19 @@ class Recipe {
* @param {Operation[]} operations
*/
addOperations(operations) {
this.opList = this.opList.concat(operations);
operations.forEach(o => {
if (o instanceof Operation) {
this.opList.push(o);
} else {
this.opList.push({
name: o.name,
module: o.module,
ingValues: o.args,
breakpoint: o.breakpoint,
disabled: o.disabled,
});
}
});
}
@ -108,7 +153,7 @@ class Recipe {
/**
* Returns true if there is an Flow Control Operation in this Recipe.
* Returns true if there is a Flow Control Operation in this Recipe.
*
* @returns {boolean}
*/
@ -137,6 +182,8 @@ class Recipe {
if (startFrom === 0) this.lastRunOp = null;
await this._hydrateOpList();
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
for (let i = startFrom; i < this.opList.length; i++) {
@ -254,7 +301,8 @@ class Recipe {
* @returns {function} highlights[].b
* @returns {Object[]} highlights[].args
*/
generateHighlightList() {
async generateHighlightList() {
await this._hydrateOpList();
const highlights = [];
for (let i = 0; i < this.opList.length; i++) {

View File

@ -104,6 +104,8 @@
"Citrix CTX1 Decode",
"Pseudo-Random Number Generator",
"Enigma",
"Bombe",
"Multiple Bombe",
"Typex"
]
},
@ -157,6 +159,7 @@
"name": "Networking",
"ops": [
"HTTP request",
"DNS over HTTPS",
"Strip HTTP headers",
"Dechunk HTTP response",
"Parse User Agent",
@ -191,6 +194,8 @@
"Remove null bytes",
"To Upper case",
"To Lower case",
"To Case Insensitive Regex",
"From Case Insensitive Regex",
"Add line numbers",
"Remove line numbers",
"To Table",
@ -215,6 +220,7 @@
"Convert mass",
"Convert speed",
"Convert data units",
"Convert co-ordinate format",
"Parse UNIX file permissions",
"Swap endianness",
"Parse colour code",
@ -306,9 +312,7 @@
"Adler-32 Checksum",
"CRC-16 Checksum",
"CRC-32 Checksum",
"TCP/IP Checksum",
"To Geohash",
"From Geohash"
"TCP/IP Checksum"
]
},
{
@ -376,6 +380,7 @@
"Generate QR Code",
"Parse QR Code",
"Haversine distance",
"Generate Lorem Ipsum",
"Numberwang",
"XKCD Random Number"
]
@ -385,6 +390,7 @@
"ops": [
"Magic",
"Fork",
"Subsection",
"Merge",
"Register",
"Label",

View File

@ -41,6 +41,7 @@ for (const opObj in Ops) {
inputType: op.inputType,
outputType: op.presentType,
flowControl: op.flowControl,
manualBake: op.manualBake,
args: op.args
};

756
src/core/lib/Bombe.mjs Normal file
View File

@ -0,0 +1,756 @@
/**
* Emulation of the Bombe machine.
*
* @author s2224834
* @author The National Museum of Computing - Bombe Rebuild Project
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import {Rotor, Plugboard, a2i, i2a} from "./Enigma";
/**
* Convenience/optimisation subclass of Rotor
*
* This allows creating multiple Rotors which share backing maps, to avoid repeatedly parsing the
* rotor spec strings and duplicating the maps in memory.
*/
class CopyRotor extends Rotor {
/**
* Return a copy of this Rotor.
* @returns {Object}
*/
copy() {
const clone = {
map: this.map,
revMap: this.revMap,
pos: this.pos,
step: this.step,
transform: this.transform,
revTransform: this.revTransform,
};
return clone;
}
}
/**
* Node in the menu graph
*
* A node represents a cipher/plaintext letter.
*/
class Node {
/**
* Node constructor.
* @param {number} letter - The plain/ciphertext letter this node represents (as a number).
*/
constructor(letter) {
this.letter = letter;
this.edges = new Set();
this.visited = false;
}
}
/**
* Edge in the menu graph
*
* An edge represents an Enigma machine transformation between two letters.
*/
class Edge {
/**
* Edge constructor - an Enigma machine mapping between letters
* @param {number} pos - The rotor position, relative to the beginning of the crib, at this edge
* @param {number} node1 - Letter at one end (as a number)
* @param {number} node2 - Letter at the other end
*/
constructor(pos, node1, node2) {
this.pos = pos;
this.node1 = node1;
this.node2 = node2;
node1.edges.add(this);
node2.edges.add(this);
this.visited = false;
}
/**
* Given the node at one end of this edge, return the other end.
* @param node {number} - The node we have
* @returns {number}
*/
getOther(node) {
return this.node1 === node ? this.node2 : this.node1;
}
}
/**
* As all the Bombe's rotors move in step, at any given point the vast majority of the scramblers
* in the machine share the majority of their state, which is hosted in this class.
*/
class SharedScrambler {
/**
* SharedScrambler constructor.
* @param {Object[]} rotors - List of rotors in the shared state _only_.
* @param {Object} reflector - The reflector in use.
*/
constructor(rotors, reflector) {
this.lowerCache = new Array(26);
this.higherCache = new Array(26);
for (let i=0; i<26; i++) {
this.higherCache[i] = new Array(26);
}
this.changeRotors(rotors, reflector);
}
/**
* Replace the rotors and reflector in this SharedScrambler.
* This takes care of flushing caches as well.
* @param {Object[]} rotors - List of rotors in the shared state _only_.
* @param {Object} reflector - The reflector in use.
*/
changeRotors(rotors, reflector) {
this.reflector = reflector;
this.rotors = rotors;
this.rotorsRev = [].concat(rotors).reverse();
this.cacheGen();
}
/**
* Step the rotors forward.
* @param {number} n - How many rotors to step. This includes the rotors which are not part of
* the shared state, so should be 2 or more.
*/
step(n) {
for (let i=0; i<n-1; i++) {
this.rotors[i].step();
}
this.cacheGen();
}
/**
* Optimisation: We pregenerate all routes through the machine with the top rotor removed,
* as these rarely change. This saves a lot of lookups. This function generates this route
* table.
* We also just-in-time cache the full routes through the scramblers, because after stepping
* the fast rotor some scramblers will be in states occupied by other scrambles on previous
* iterations.
*/
cacheGen() {
for (let i=0; i<26; i++) {
this.lowerCache[i] = undefined;
for (let j=0; j<26; j++) {
this.higherCache[i][j] = undefined;
}
}
for (let i=0; i<26; i++) {
if (this.lowerCache[i] !== undefined) {
continue;
}
let letter = i;
for (const rotor of this.rotors) {
letter = rotor.transform(letter);
}
letter = this.reflector.transform(letter);
for (const rotor of this.rotorsRev) {
letter = rotor.revTransform(letter);
}
// By symmetry
this.lowerCache[i] = letter;
this.lowerCache[letter] = i;
}
}
/**
* Map a letter through this (partial) scrambler.
* @param {number} i - The letter
* @returns {number}
*/
transform(i) {
return this.lowerCache[i];
}
}
/**
* Scrambler.
*
* This is effectively just an Enigma machine, but it only operates on one character at a time and
* the stepping mechanism is different.
*/
class Scrambler {
/** Scrambler constructor.
* @param {Object} base - The SharedScrambler whose state this scrambler uses
* @param {Object} rotor - The non-shared fast rotor in this scrambler
* @param {number} pos - Position offset from start of crib
* @param {number} end1 - Letter in menu this scrambler is attached to
* @param {number} end2 - Other letter in menu this scrambler is attached to
*/
constructor(base, rotor, pos, end1, end2) {
this.baseScrambler = base;
this.initialPos = pos;
this.changeRotor(rotor);
this.end1 = end1;
this.end2 = end2;
// For efficiency reasons, we pull the relevant shared cache from the baseScrambler into
// this object - this saves us a few pointer dereferences
this.cache = this.baseScrambler.higherCache[pos];
}
/**
* Replace the rotor in this scrambler.
* The position is reset automatically.
* @param {Object} rotor - New rotor
*/
changeRotor(rotor) {
this.rotor = rotor;
this.rotor.pos += this.initialPos;
}
/**
* Step the rotor forward.
*
* The base SharedScrambler needs to be instructed to step separately.
*/
step() {
// The Bombe steps the slowest rotor on an actual Enigma fastest, for reasons.
// ...but for optimisation reasons I'm going to cheat and not do that, as this vastly
// simplifies caching the state of the majority of the scramblers. The results are the
// same, just in a slightly different order.
this.rotor.step();
this.cache = this.baseScrambler.higherCache[this.rotor.pos];
}
/**
* Run a letter through the scrambler.
* @param {number} i - The letter to transform (as a number)
* @returns {number}
*/
transform(i) {
let letter = i;
const cached = this.cache[i];
if (cached !== undefined) {
return cached;
}
letter = this.rotor.transform(letter);
letter = this.baseScrambler.transform(letter);
letter = this.rotor.revTransform(letter);
this.cache[i] = letter;
this.cache[letter] = i;
return letter;
}
/**
* Given one letter in the menu this scrambler maps to, return the other.
* @param end {number} - The node we have
* @returns {number}
*/
getOtherEnd(end) {
return this.end1 === end ? this.end2 : this.end1;
}
/**
* Read the position this scrambler is set to.
* Note that because of Enigma's stepping, you need to set an actual Enigma to the previous
* position in order to get it to make a certain set of electrical connections when a button
* is pressed - this function *does* take this into account.
* However, as with the rest of the Bombe, it does not take stepping into account - the middle
* and slow rotors are treated as static.
* @return {string}
*/
getPos() {
let result = "";
// Roll back the fast rotor by one step
let pos = Utils.mod(this.rotor.pos - 1, 26);
result += i2a(pos);
for (let i=0; i<this.baseScrambler.rotors.length; i++) {
pos = this.baseScrambler.rotors[i].pos;
result += i2a(pos);
}
return result.split("").reverse().join("");
}
}
/**
* Bombe simulator class.
*/
export class BombeMachine {
/**
* Construct a Bombe.
*
* Note that there is no handling of offsets here: the crib specified must exactly match the
* ciphertext. It will check that the crib is sane (length is vaguely sensible and there's no
* matching characters between crib and ciphertext) but cannot check further - if it's wrong
* your results will be wrong!
*
* There is also no handling of rotor stepping - if the target Enigma stepped in the middle of
* your crib, you're out of luck. TODO: Allow specifying a step point - this is fairly easy to
* configure on a real Bombe, but we're not clear on whether it was ever actually done for
* real (there would almost certainly have been better ways of attacking in most situations
* than attempting to exhaust options for the stepping point, but in some circumstances, e.g.
* via Banburismus, the stepping point might have been known).
*
* @param {string[]} rotors - list of rotor spec strings (without step points!)
* @param {Object} reflector - Reflector object
* @param {string} ciphertext - The ciphertext to attack
* @param {string} crib - Known plaintext for this ciphertext
* @param {boolean} check - Whether to use the checking machine
* @param {function} update - Function to call to send status updates (optional)
*/
constructor(rotors, reflector, ciphertext, crib, check, update=undefined) {
if (ciphertext.length < crib.length) {
throw new OperationError("Crib overruns supplied ciphertext");
}
if (crib.length < 2) {
// This is the absolute bare minimum to be sane, and even then it's likely too short to
// be useful
throw new OperationError("Crib is too short");
}
if (crib.length > 25) {
// A crib longer than this will definitely cause the middle rotor to step somewhere
// A shorter crib is preferable to reduce this chance, of course
throw new OperationError("Crib is too long");
}
for (let i=0; i<crib.length; i++) {
if (ciphertext[i] === crib[i]) {
throw new OperationError(`Invalid crib: character ${ciphertext[i]} at pos ${i} in both ciphertext and crib`);
}
}
this.ciphertext = ciphertext;
this.crib = crib;
this.initRotors(rotors);
this.check = check;
this.updateFn = update;
const [mostConnected, edges] = this.makeMenu();
// This is the bundle of wires corresponding to the 26 letters within each of the 26
// possible nodes in the menu
this.wires = new Array(26*26);
// These are the pseudo-Engima devices corresponding to each edge in the menu, and the
// nodes in the menu they each connect to
this.scramblers = new Array();
for (let i=0; i<26; i++) {
this.scramblers.push(new Array());
}
this.sharedScrambler = new SharedScrambler(this.baseRotors.slice(1), reflector);
this.allScramblers = new Array();
this.indicator = undefined;
for (const edge of edges) {
const cRotor = this.baseRotors[0].copy();
const end1 = a2i(edge.node1.letter);
const end2 = a2i(edge.node2.letter);
const scrambler = new Scrambler(this.sharedScrambler, cRotor, edge.pos, end1, end2);
if (edge.pos === 0) {
this.indicator = scrambler;
}
this.scramblers[end1].push(scrambler);
this.scramblers[end2].push(scrambler);
this.allScramblers.push(scrambler);
}
// The Bombe uses a set of rotors to keep track of what settings it's testing. We cheat and
// use one of the actual scramblers if there's one in the right position, but if not we'll
// just create one.
if (this.indicator === undefined) {
this.indicator = new Scrambler(this.sharedScrambler, this.baseRotors[0].copy(), 0, undefined, undefined);
this.allScramblers.push(this.indicator);
}
this.testRegister = a2i(mostConnected.letter);
// This is an arbitrary letter other than the most connected letter
for (const edge of mostConnected.edges) {
this.testInput = [this.testRegister, a2i(edge.getOther(mostConnected).letter)];
break;
}
}
/**
* Build Rotor objects from list of rotor wiring strings.
* @param {string[]} rotors - List of rotor wiring strings
*/
initRotors(rotors) {
// This is ordered from the Enigma fast rotor to the slow, so bottom to top for the Bombe
this.baseRotors = [];
for (const rstr of rotors) {
const rotor = new CopyRotor(rstr, "", "A", "A");
this.baseRotors.push(rotor);
}
}
/**
* Replace the rotors and reflector in all components of this Bombe.
* @param {string[]} rotors - List of rotor wiring strings
* @param {Object} reflector - Reflector object
*/
changeRotors(rotors, reflector) {
// At the end of the run, the rotors are all back in the same position they started
this.initRotors(rotors);
this.sharedScrambler.changeRotors(this.baseRotors.slice(1), reflector);
for (const scrambler of this.allScramblers) {
scrambler.changeRotor(this.baseRotors[0].copy());
}
}
/**
* If we have a way of sending status messages, do so.
* @param {...*} msg - Message to send.
*/
update(...msg) {
if (this.updateFn !== undefined) {
this.updateFn(...msg);
}
}
/**
* Recursive depth-first search on the menu graph.
* This is used to a) isolate unconnected sub-graphs, and b) count the number of loops in each
* of those graphs.
* @param {Object} node - Node object to start the search from
* @returns {[number, number, Object, number, Object[]} - loop count, node count, most connected
* node, order of most connected node, list of edges in this sub-graph
*/
dfs(node) {
let loops = 0;
let nNodes = 1;
let mostConnected = node;
let nConnections = mostConnected.edges.size;
let edges = new Set();
node.visited = true;
for (const edge of node.edges) {
if (edge.visited) {
// Already been here from the other end.
continue;
}
edge.visited = true;
edges.add(edge);
const other = edge.getOther(node);
if (other.visited) {
// We have a loop, record that and continue
loops += 1;
continue;
}
// This is a newly visited node
const [oLoops, oNNodes, oMostConnected, oNConnections, oEdges] = this.dfs(other);
loops += oLoops;
nNodes += oNNodes;
edges = new Set([...edges, ...oEdges]);
if (oNConnections > nConnections) {
mostConnected = oMostConnected;
nConnections = oNConnections;
}
}
return [loops, nNodes, mostConnected, nConnections, edges];
}
/**
* Build a menu from the ciphertext and crib.
* A menu is just a graph where letters in either the ciphertext or crib (Enigma is symmetric,
* so there's no difference mathematically) are nodes and states of the Enigma machine itself
* are the edges.
* Additionally, we want a single connected graph, and of the subgraphs available, we want the
* one with the most loops (since these generate feedback cycles which efficiently close off
* disallowed states).
* Finally, we want to identify the most connected node in that graph (as it's the best choice
* of measurement point).
* @returns [Object, Object[]] - the most connected node, and the list of edges in the subgraph
*/
makeMenu() {
// First, we make a graph of all of the mappings given by the crib
// Make all nodes first
const nodes = new Map();
for (const c of this.ciphertext + this.crib) {
if (!nodes.has(c)) {
const node = new Node(c);
nodes.set(c, node);
}
}
// Then all edges
for (let i=0; i<this.crib.length; i++) {
const a = this.crib[i];
const b = this.ciphertext[i];
new Edge(i, nodes.get(a), nodes.get(b));
}
// list of [loop_count, node_count, most_connected_node, connections_on_most_connected, edges]
const graphs = [];
// Then, for each unconnected subgraph, we count the number of loops and nodes
for (const start of nodes.keys()) {
if (nodes.get(start).visited) {
continue;
}
const subgraph = this.dfs(nodes.get(start));
graphs.push(subgraph);
}
// Return the subgraph with the most loops (ties broken by node count)
graphs.sort((a, b) => {
let result = b[0] - a[0];
if (result === 0) {
result = b[1] - a[1];
}
return result;
});
this.nLoops = graphs[0][0];
return [graphs[0][2], graphs[0][4]];
}
/**
* Bombe electrical simulation. Energise a wire. For all connected wires (both via the diagonal
* board and via the scramblers), energise them too, recursively.
* @param {number} i - Bombe wire bundle
* @param {number} j - Bombe stecker hypothesis wire within bundle
*/
energise(i, j) {
const idx = 26*i + j;
if (this.wires[idx]) {
return;
}
this.wires[idx] = true;
// Welchman's diagonal board: if A steckers to B, that implies B steckers to A. Handle
// both.
const idxPair = 26*j + i;
this.wires[idxPair] = true;
if (i === this.testRegister || j === this.testRegister) {
this.energiseCount++;
if (this.energiseCount === 26) {
// no point continuing, bail out
return;
}
}
for (let k=0; k<this.scramblers[i].length; k++) {
const scrambler = this.scramblers[i][k];
const out = scrambler.transform(j);
const other = scrambler.getOtherEnd(i);
// Lift the pre-check before the call, to save some function call overhead
const otherIdx = 26*other + out;
if (!this.wires[otherIdx]) {
this.energise(other, out);
if (this.energiseCount === 26) {
return;
}
}
}
if (i === j) {
return;
}
for (let k=0; k<this.scramblers[j].length; k++) {
const scrambler = this.scramblers[j][k];
const out = scrambler.transform(i);
const other = scrambler.getOtherEnd(j);
const otherIdx = 26*other + out;
if (!this.wires[otherIdx]) {
this.energise(other, out);
if (this.energiseCount === 26) {
return;
}
}
}
}
/**
* Trial decryption at the current setting.
* Used after we get a stop.
* This applies the detected stecker pair if we have one. It does not handle the other
* steckering or stepping (which is why we limit it to 26 characters, since it's guaranteed to
* be wrong after that anyway).
* @param {string} stecker - Known stecker spec string.
* @returns {string}
*/
tryDecrypt(stecker) {
const fastRotor = this.indicator.rotor;
const initialPos = fastRotor.pos;
const res = [];
const plugboard = new Plugboard(stecker);
// The indicator scrambler starts in the right place for the beginning of the ciphertext.
for (let i=0; i<Math.min(26, this.ciphertext.length); i++) {
const t = this.indicator.transform(plugboard.transform(a2i(this.ciphertext[i])));
res.push(i2a(plugboard.transform(t)));
this.indicator.step(1);
}
fastRotor.pos = initialPos;
return res.join("");
}
/**
* Format a steckered pair, in sorted order to allow uniquing.
* @param {number} a - A letter
* @param {number} b - Its stecker pair
* @returns {string}
*/
formatPair(a, b) {
if (a < b) {
return `${i2a(a)}${i2a(b)}`;
}
return `${i2a(b)}${i2a(a)}`;
}
/**
* The checking machine was used to manually verify Bombe stops. Using a device which was
* effectively a non-stepping Enigma, the user would walk through each of the links in the
* menu at the rotor positions determined by the Bombe. By starting with the stecker pair the
* Bombe gives us, we find the stecker pair of each connected letter in the graph, and so on.
* If a contradiction is reached, the stop is invalid. If not, we have most (but not
* necessarily all) of the plugboard connections.
* You will notice that this procedure is exactly the same as what the Bombe itself does, only
* we start with an assumed good hypothesis and read out the stecker pair for every letter.
* On the real hardware that wasn't practical, but fortunately we're not the real hardware, so
* we don't need to implement the manual checking machine procedure.
* @param {number} pair - The stecker pair of the test register.
* @returns {string} - The empty string for invalid stops, or a plugboard configuration string
* containing all known pairs.
*/
checkingMachine(pair) {
if (pair !== this.testInput[1]) {
// We have a new hypothesis for this stop - apply the new one.
// De-energise the board
for (let i=0; i<this.wires.length; i++) {
this.wires[i] = false;
}
this.energiseCount = 0;
// Re-energise with the corrected hypothesis
this.energise(this.testRegister, pair);
}
const results = new Set();
results.add(this.formatPair(this.testRegister, pair));
for (let i=0; i<26; i++) {
let count = 0;
let other;
for (let j=0; j<26; j++) {
if (this.wires[i*26 + j]) {
count++;
other = j;
}
}
if (count > 1) {
// This is an invalid stop.
return "";
} else if (count === 0) {
// No information about steckering from this wire
continue;
}
results.add(this.formatPair(i, other));
}
return [...results].join(" ");
}
/**
* Check to see if the Bombe has stopped. If so, process the stop.
* @returns {(undefined|string[3])} - Undefined for no stop, or [rotor settings, plugboard settings, decryption preview]
*/
checkStop() {
// Count the energised outputs
const count = this.energiseCount;
if (count === 26) {
return undefined;
}
// If it's not all of them, we have a stop
let steckerPair;
// The Bombe tells us one stecker pair as well. The input wire and test register we
// started with are hypothesised to be a stecker pair.
if (count === 25) {
// Our steckering hypothesis is wrong. Correct value is the un-energised wire.
for (let j=0; j<26; j++) {
if (!this.wires[26*this.testRegister + j]) {
steckerPair = j;
break;
}
}
} else if (count === 1) {
// This means our hypothesis for the steckering is correct.
steckerPair = this.testInput[1];
} else {
// This was known as a "boxing stop" - we have a stop but not a single hypothesis.
// If this happens a lot it implies the menu isn't good enough.
// If we have the checking machine enabled, we're going to just check each wire in
// turn. If we get 0 or 1 hit, great.
// If we get multiple hits, or the checking machine is off, the user will just have to
// deal with it.
if (!this.check) {
// We can't draw any conclusions about the steckering (one could maybe suggest
// options in some cases, but too hard to present clearly).
return [this.indicator.getPos(), "??", this.tryDecrypt("")];
}
let stecker = undefined;
for (let i = 0; i < 26; i++) {
const newStecker = this.checkingMachine(i);
if (newStecker !== "") {
if (stecker !== undefined) {
// Multiple hypotheses can't be ruled out.
return [this.indicator.getPos(), "??", this.tryDecrypt("")];
}
stecker = newStecker;
}
}
if (stecker === undefined) {
// Checking machine ruled all possibilities out.
return undefined;
}
// If we got here, there was just one possibility allowed by the checking machine. Success.
return [this.indicator.getPos(), stecker, this.tryDecrypt(stecker)];
}
let stecker;
if (this.check) {
stecker = this.checkingMachine(steckerPair);
if (stecker === "") {
// Invalid stop - don't count it, don't return it
return undefined;
}
} else {
stecker = `${i2a(this.testRegister)}${i2a(steckerPair)}`;
}
const testDecrypt = this.tryDecrypt(stecker);
return [this.indicator.getPos(), stecker, testDecrypt];
}
/**
* Having set up the Bombe, do the actual attack run. This tries every possible rotor setting
* and attempts to logically invalidate them. If it can't, it's added to the list of candidate
* solutions.
* @returns {string[][3]} - list of 3-tuples of candidate rotor setting, plugboard settings, and decryption preview
*/
run() {
let stops = 0;
const result = [];
// For each possible rotor setting
const nChecks = Math.pow(26, this.baseRotors.length);
for (let i=1; i<=nChecks; i++) {
// Benchmarking suggests this is faster than using .fill()
for (let i=0; i<this.wires.length; i++) {
this.wires[i] = false;
}
this.energiseCount = 0;
// Energise the test input, follow the current through each scrambler
// (and the diagonal board)
this.energise(...this.testInput);
const stop = this.checkStop();
if (stop !== undefined) {
stops++;
result.push(stop);
}
// Step all the scramblers
// This loop counts how many rotors have reached their starting position (meaning the
// next one needs to step as well)
let n = 1;
for (let j=1; j<this.baseRotors.length; j++) {
if ((i % Math.pow(26, j)) === 0) {
n++;
} else {
break;
}
}
if (n > 1) {
this.sharedScrambler.step(n);
}
for (const scrambler of this.allScramblers) {
scrambler.step();
}
// Send status messages at what seems to be a reasonably sensible frequency
// (note this won't be triggered on 3-rotor runs - they run fast enough it doesn't seem necessary)
if (n > 3) {
this.update(this.nLoops, stops, i/nChecks);
}
}
return result;
}
}

View File

@ -0,0 +1,655 @@
/**
* Co-ordinate conversion resources.
*
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import geohash from "ngeohash";
import geodesy from "geodesy";
import OperationError from "../errors/OperationError";
/**
* Co-ordinate formats
*/
export const FORMATS = [
"Degrees Minutes Seconds",
"Degrees Decimal Minutes",
"Decimal Degrees",
"Geohash",
"Military Grid Reference System",
"Ordnance Survey National Grid",
"Universal Transverse Mercator"
];
/**
* Formats that should be passed to the conversion module as-is
*/
const NO_CHANGE = [
"Geohash",
"Military Grid Reference System",
"Ordnance Survey National Grid",
"Universal Transverse Mercator",
];
/**
* Convert a given latitude and longitude into a different format.
*
* @param {string} input - Input string to be converted
* @param {string} inFormat - Format of the input coordinates
* @param {string} inDelim - The delimiter splitting the lat/long of the input
* @param {string} outFormat - Format to convert to
* @param {string} outDelim - The delimiter to separate the output with
* @param {string} includeDir - Whether or not to include the compass direction in the output
* @param {number} precision - Precision of the result
* @returns {string} A formatted string of the converted co-ordinates
*/
export function convertCoordinates (input, inFormat, inDelim, outFormat, outDelim, includeDir, precision) {
let isPair = false,
split,
latlon,
convLat,
convLon,
conv,
hash,
utm,
mgrs,
osng,
splitLat,
splitLong,
lat,
lon;
// Can't have a precision less than 0!
if (precision < 0) {
precision = 0;
}
if (inDelim === "Auto") {
// Try to detect a delimiter in the input.
inDelim = findDelim(input);
if (inDelim === null) {
throw new OperationError("Unable to detect the input delimiter automatically.");
}
} else if (!inDelim.includes("Direction")) {
// Convert the delimiter argument value to the actual character
inDelim = realDelim(inDelim);
}
if (inFormat === "Auto") {
// Try to detect the format of the input data
inFormat = findFormat(input, inDelim);
if (inFormat === null) {
throw new OperationError("Unable to detect the input format automatically.");
}
}
// Convert the output delimiter argument to the real character
outDelim = realDelim(outDelim);
if (!NO_CHANGE.includes(inFormat)) {
if (inDelim.includes("Direction")) {
// Split on directions
split = input.split(/[NnEeSsWw]/g);
if (split[0] === "") {
// Remove first element if direction preceding
split = split.slice(1);
}
} else {
split = input.split(inDelim);
}
// Replace any co-ordinate symbols with spaces so we can split on them later
for (let i = 0; i < split.length; i++) {
split[i] = split[i].replace(/[°˝´'"]/g, " ");
}
if (split.length > 1) {
isPair = true;
}
} else {
// Remove any delimiters from the input
input = input.replace(inDelim, "");
isPair = true;
}
// Conversions from the input format into a geodesy latlon object
switch (inFormat) {
case "Geohash":
hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, ""));
latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude);
break;
case "Military Grid Reference System":
utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
latlon = utm.toLatLonE();
break;
case "Ordnance Survey National Grid":
osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
latlon = geodesy.OsGridRef.osGridToLatLon(osng);
break;
case "Universal Transverse Mercator":
// Geodesy needs a space between the first 2 digits and the next letter
if (/^[\d]{2}[A-Za-z]/.test(input)) {
input = input.slice(0, 2) + " " + input.slice(2);
}
utm = geodesy.Utm.parse(input);
latlon = utm.toLatLonE();
break;
case "Degrees Minutes Seconds":
if (isPair) {
// Split up the lat/long into degrees / minutes / seconds values
splitLat = splitInput(split[0]);
splitLong = splitInput(split[1]);
if (splitLat.length >= 3 && splitLong.length >= 3) {
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10);
lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
} else {
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
}
} else {
// Not a pair, so only try to convert one set of co-ordinates
splitLat = splitInput(split[0]);
if (splitLat.length >= 3) {
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
} else {
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
}
}
break;
case "Degrees Decimal Minutes":
if (isPair) {
splitLat = splitInput(split[0]);
splitLong = splitInput(split[1]);
if (splitLat.length !== 2 || splitLong.length !== 2) {
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
}
// Convert to decimal degrees, and then convert to a geodesy object
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
lon = convDDMToDD(splitLong[0], splitLong[1], 10);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
} else {
// Not a pair, so only try to convert one set of co-ordinates
splitLat = splitInput(input);
if (splitLat.length !== 2) {
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
}
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
}
break;
case "Decimal Degrees":
if (isPair) {
splitLat = splitInput(split[0]);
splitLong = splitInput(split[1]);
if (splitLat.length !== 1 || splitLong.length !== 1) {
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
}
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]);
} else {
// Not a pair, so only try to convert one set of co-ordinates
splitLat = splitInput(split[0]);
if (splitLat.length !== 1) {
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
}
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]);
}
break;
default:
throw new OperationError(`Unknown input format '${inFormat}'`);
}
// Everything is now a geodesy latlon object
// These store the latitude and longitude as decimal
if (inFormat.includes("Degrees")) {
// If the input string contains directions, we need to check if they're S or W.
// If either of the directions are, we should make the decimal value negative
const dirs = input.toUpperCase().match(/[NESW]/g);
if (dirs && dirs.length >= 1) {
// Make positive lat/lon values with S/W directions into negative values
if (dirs[0] === "S" || dirs[0] === "W" && latlon.lat > 0) {
latlon.lat = -latlon.lat;
}
if (dirs.length >= 2) {
if (dirs[1] === "S" || dirs[1] === "W" && latlon.lon > 0) {
latlon.lon = -latlon.lon;
}
}
}
}
// Try to find the compass directions of the lat and long
const [latDir, longDir] = findDirs(latlon.lat + "," + latlon.lon, ",");
// Output conversions for each output format
switch (outFormat) {
case "Decimal Degrees":
// We could use the built in latlon.toString(),
// but this makes adjusting the output harder
lat = convDDToDD(latlon.lat, precision);
lon = convDDToDD(latlon.lon, precision);
convLat = lat.string;
convLon = lon.string;
break;
case "Degrees Decimal Minutes":
lat = convDDToDDM(latlon.lat, precision);
lon = convDDToDDM(latlon.lon, precision);
convLat = lat.string;
convLon = lon.string;
break;
case "Degrees Minutes Seconds":
lat = convDDToDMS(latlon.lat, precision);
lon = convDDToDMS(latlon.lon, precision);
convLat = lat.string;
convLon = lon.string;
break;
case "Geohash":
convLat = geohash.encode(latlon.lat, latlon.lon, precision);
break;
case "Military Grid Reference System":
utm = latlon.toUtm();
mgrs = utm.toMgrs();
// MGRS wants a precision that's an even number between 2 and 10
if (precision % 2 !== 0) {
precision = precision + 1;
}
if (precision > 10) {
precision = 10;
}
convLat = mgrs.toString(precision);
break;
case "Ordnance Survey National Grid":
osng = geodesy.OsGridRef.latLonToOsGrid(latlon);
if (osng.toString() === "") {
throw new OperationError("Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?");
}
// OSNG wants a precision that's an even number between 2 and 10
if (precision % 2 !== 0) {
precision = precision + 1;
}
if (precision > 10) {
precision = 10;
}
convLat = osng.toString(precision);
break;
case "Universal Transverse Mercator":
utm = latlon.toUtm();
convLat = utm.toString(precision);
break;
}
if (convLat === undefined) {
throw new OperationError("Error converting co-ordinates.");
}
if (outFormat.includes("Degrees")) {
// Format DD/DDM/DMS for output
// If we're outputting a compass direction, remove the negative sign
if (latDir === "S" && includeDir !== "None") {
convLat = convLat.replace("-", "");
}
if (longDir === "W" && includeDir !== "None") {
convLon = convLon.replace("-", "");
}
let outConv = "";
if (includeDir === "Before") {
outConv += latDir + " ";
}
outConv += convLat;
if (includeDir === "After") {
outConv += " " + latDir;
}
outConv += outDelim;
if (isPair) {
if (includeDir === "Before") {
outConv += longDir + " ";
}
outConv += convLon;
if (includeDir === "After") {
outConv += " " + longDir;
}
outConv += outDelim;
}
conv = outConv;
} else {
conv = convLat + outDelim;
}
return conv;
}
/**
* Split up the input using a space or degrees signs, and sanitise the result
*
* @param {string} input - The input data to be split
* @returns {number[]} An array of the different items in the string, stored as floats
*/
function splitInput (input){
const split = [];
input.split(/\s+/).forEach(item => {
// Remove any character that isn't a digit, decimal point or negative sign
item = item.replace(/[^0-9.-]/g, "");
if (item.length > 0){
// Turn the item into a float
split.push(parseFloat(item));
}
});
return split;
}
/**
* Convert Degrees Minutes Seconds to Decimal Degrees
*
* @param {number} degrees - The degrees of the input co-ordinates
* @param {number} minutes - The minutes of the input co-ordinates
* @param {number} seconds - The seconds of the input co-ordinates
* @param {number} precision - The precision the result should be rounded to
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
*/
function convDMSToDD (degrees, minutes, seconds, precision){
const absDegrees = Math.abs(degrees);
let conv = absDegrees + (minutes / 60) + (seconds / 3600);
let outString = round(conv, precision) + "°";
if (isNegativeZero(degrees) || degrees < 0) {
conv = -conv;
outString = "-" + outString;
}
return {
"degrees": conv,
"string": outString
};
}
/**
* Convert Decimal Degrees Minutes to Decimal Degrees
*
* @param {number} degrees - The input degrees to be converted
* @param {number} minutes - The input minutes to be converted
* @param {number} precision - The precision which the result should be rounded to
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
*/
function convDDMToDD (degrees, minutes, precision) {
const absDegrees = Math.abs(degrees);
let conv = absDegrees + minutes / 60;
let outString = round(conv, precision) + "°";
if (isNegativeZero(degrees) || degrees < 0) {
conv = -conv;
outString = "-" + outString;
}
return {
"degrees": conv,
"string": outString
};
}
/**
* Convert Decimal Degrees to Decimal Degrees
*
* Doesn't affect the input, just puts it into an object
* @param {number} degrees - The input degrees to be converted
* @param {number} precision - The precision which the result should be rounded to
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
*/
function convDDToDD (degrees, precision) {
return {
"degrees": degrees,
"string": round(degrees, precision) + "°"
};
}
/**
* Convert Decimal Degrees to Degrees Minutes Seconds
*
* @param {number} decDegrees - The input data to be converted
* @param {number} precision - The precision which the result should be rounded to
* @returns {{string: string, degrees: number, minutes: number, seconds: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes, .seconds), and a formatted string version (obj.string)
*/
function convDDToDMS (decDegrees, precision) {
const absDegrees = Math.abs(decDegrees);
let degrees = Math.floor(absDegrees);
const minutes = Math.floor(60 * (absDegrees - degrees)),
seconds = round(3600 * (absDegrees - degrees) - 60 * minutes, precision);
let outString = degrees + "° " + minutes + "' " + seconds + "\"";
if (isNegativeZero(decDegrees) || decDegrees < 0) {
degrees = -degrees;
outString = "-" + outString;
}
return {
"degrees": degrees,
"minutes": minutes,
"seconds": seconds,
"string": outString
};
}
/**
* Convert Decimal Degrees to Degrees Decimal Minutes
*
* @param {number} decDegrees - The input degrees to be converted
* @param {number} precision - The precision the input data should be rounded to
* @returns {{string: string, degrees: number, minutes: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes), and a formatted string version (obj.string)
*/
function convDDToDDM (decDegrees, precision) {
const absDegrees = Math.abs(decDegrees);
let degrees = Math.floor(absDegrees);
const minutes = absDegrees - degrees,
decMinutes = round(minutes * 60, precision);
let outString = degrees + "° " + decMinutes + "'";
if (decDegrees < 0 || isNegativeZero(decDegrees)) {
degrees = -degrees;
outString = "-" + outString;
}
return {
"degrees": degrees,
"minutes": decMinutes,
"string": outString,
};
}
/**
* Finds and returns the compass directions in an input string
*
* @param {string} input - The input co-ordinates containing the direction
* @param {string} delim - The delimiter separating latitide and longitude
* @returns {string[]} String array containing the latitude and longitude directions
*/
export function findDirs(input, delim) {
const upperInput = input.toUpperCase();
const dirExp = new RegExp(/[NESW]/g);
const dirs = upperInput.match(dirExp);
if (dirs) {
// If there's actually compass directions
// in the input, use these to work out the direction
if (dirs.length <= 2 && dirs.length >= 1) {
return dirs.length === 2 ? [dirs[0], dirs[1]] : [dirs[0], ""];
}
}
// Nothing was returned, so guess the directions
let lat = upperInput,
long,
latDir = "",
longDir = "";
if (!delim.includes("Direction")) {
if (upperInput.includes(delim)) {
const split = upperInput.split(delim);
if (split.length >= 1) {
if (split[0] !== "") {
lat = split[0];
}
if (split.length >= 2 && split[1] !== "") {
long = split[1];
}
}
}
} else {
const split = upperInput.split(dirExp);
if (split.length > 1) {
lat = split[0] === "" ? split[1] : split[0];
if (split.length > 2 && split[2] !== "") {
long = split[2];
}
}
}
if (lat) {
lat = parseFloat(lat);
latDir = lat < 0 ? "S" : "N";
}
if (long) {
long = parseFloat(long);
longDir = long < 0 ? "W" : "E";
}
return [latDir, longDir];
}
/**
* Detects the co-ordinate format of the input data
*
* @param {string} input - The input data whose format we need to detect
* @param {string} delim - The delimiter separating the data in input
* @returns {string} The input format
*/
export function findFormat (input, delim) {
let testData;
const mgrsPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]{1}\s?[A-HJ-NP-Z][A-HJ-NP-V]\s?[0-9\s]+/),
osngPattern = new RegExp(/^[A-HJ-Z]{2}\s+[0-9\s]+$/),
geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/),
utmPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]\s[0-9.]+\s?[0-9.]+$/),
degPattern = new RegExp(/[°'"]/g);
input = input.trim();
if (delim !== null && delim.includes("Direction")) {
const split = input.split(/[NnEeSsWw]/);
if (split.length > 1) {
testData = split[0] === "" ? split[1] : split[0];
}
} else if (delim !== null && delim !== "") {
if (input.includes(delim)) {
const split = input.split(delim);
if (split.length > 1) {
testData = split[0] === "" ? split[1] : split[0];
}
} else {
testData = input;
}
}
// Test non-degrees formats
if (!degPattern.test(input)) {
const filteredInput = input.toUpperCase().replace(delim, "");
if (utmPattern.test(filteredInput)) {
return "Universal Transverse Mercator";
}
if (mgrsPattern.test(filteredInput)) {
return "Military Grid Reference System";
}
if (osngPattern.test(filteredInput)) {
return "Ordnance Survey National Grid";
}
if (geohashPattern.test(filteredInput)) {
return "Geohash";
}
}
// Test DMS/DDM/DD formats
if (testData !== undefined) {
const split = splitInput(testData);
switch (split.length){
case 3:
return "Degrees Minutes Seconds";
case 2:
return "Degrees Decimal Minutes";
case 1:
return "Decimal Degrees";
}
}
return null;
}
/**
* Automatically find the delimeter type from the given input
*
* @param {string} input
* @returns {string} Delimiter type
*/
export function findDelim (input) {
input = input.trim();
const delims = [",", ";", ":"];
const testDir = input.match(/[NnEeSsWw]/g);
if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
// Possibly contains a direction
const splitInput = input.split(/[NnEeSsWw]/);
if (splitInput.length <= 3 && splitInput.length > 0) {
// If there's 3 splits (one should be empty), then assume we have directions
if (splitInput[0] === "") {
return "Direction Preceding";
} else if (splitInput[splitInput.length - 1] === "") {
return "Direction Following";
}
}
}
// Loop through the standard delimiters, and try to find them in the input
for (let i = 0; i < delims.length; i++) {
const delim = delims[i];
if (input.includes(delim)) {
const splitInput = input.split(delim);
if (splitInput.length <= 3 && splitInput.length > 0) {
// Don't want to try and convert more than 2 co-ordinates
return delim;
}
}
}
return null;
}
/**
* Gets the real string for a delimiter name.
*
* @param {string} delim The delimiter to be matched
* @returns {string}
*/
export function realDelim (delim) {
return {
"Auto": "Auto",
"Space": " ",
"\\n": "\n",
"Comma": ",",
"Semi-colon": ";",
"Colon": ":"
}[delim];
}
/**
* Returns true if a zero is negative
*
* @param {number} zero
* @returns {boolean}
*/
function isNegativeZero(zero) {
return zero === 0 && (1/zero < 0);
}
/**
* Rounds a number to a specified number of decimal places
*
* @param {number} input - The number to be rounded
* @param {precision} precision - The number of decimal places the number should be rounded to
* @returns {number}
*/
function round(input, precision) {
precision = Math.pow(10, precision);
return Math.round(input * precision) / precision;
}

View File

@ -22,14 +22,13 @@ export const ROTORS = [
{name: "VI", value: "JPGVOUMFYQBENHZRDKASXLICTW<AN"},
{name: "VII", value: "NZJHGRCXMYSWBOUFAIVLPEKQDT<AN"},
{name: "VIII", value: "FKQHTLXOCBJSPDZRAMEWNIUYGV<AN"},
];
export const ROTORS_FOURTH = [
{name: "Beta", value: "LEYJVCNIXWPBQMDRTAKZGFUHOS"},
{name: "Gamma", value: "FSOKANUERHMBTIYCWLQPZXVGJD"},
];
export const ROTORS_OPTIONAL = [].concat(ROTORS).concat([
{name: "None", value: ""},
]);
/**
* Provided default Enigma reflector set.
* These are specified as 13 space-separated transposed pairs covering every letter.
@ -103,15 +102,17 @@ export class Rotor {
if (!/^[A-Z]$/.test(initialPosition)) {
throw new OperationError("Rotor initial position must be exactly one uppercase letter");
}
this.map = {};
this.revMap = {};
this.map = new Array(26);
this.revMap = new Array(26);
const uniq = {};
for (let i=0; i<LETTERS.length; i++) {
const a = a2i(LETTERS[i]);
const b = a2i(wiring[i]);
this.map[a] = b;
this.revMap[b] = a;
uniq[b] = true;
}
if (Object.keys(this.revMap).length !== LETTERS.length) {
if (Object.keys(uniq).length !== LETTERS.length) {
throw new OperationError("Rotor wiring must have each letter exactly once");
}
const rs = a2i(ringSetting);
@ -169,6 +170,7 @@ class PairMapBase {
constructor(pairs, name="PairMapBase") {
// I've chosen to make whitespace significant here to make a) code and
// b) inputs easier to read
this.pairs = pairs;
this.map = {};
if (pairs === "") {
return;
@ -179,7 +181,8 @@ class PairMapBase {
}
const a = a2i(pair[0]), b = a2i(pair[1]);
if (a === b) {
throw new OperationError(`${name}: cannot connect ${pair[0]} to itself`);
// self-stecker
return;
}
if (this.map.hasOwnProperty(a)) {
throw new OperationError(`${name} connects ${pair[0]} more than once`);
@ -219,6 +222,8 @@ class PairMapBase {
/**
* Reflector. PairMapBase but requires that all characters are accounted for.
*
* Includes a couple of optimisations on that basis.
*/
export class Reflector extends PairMapBase {
/**
@ -231,6 +236,21 @@ export class Reflector extends PairMapBase {
if (s !== 26) {
throw new OperationError("Reflector must have exactly 13 pairs covering every letter");
}
const optMap = new Array(26);
for (const x of Object.keys(this.map)) {
optMap[x] = this.map[x];
}
this.map = optMap;
}
/**
* Transform a character through this object.
*
* @param {number} c - The character.
* @returns {number}
*/
transform(c) {
return this.map[c];
}
}

230
src/core/lib/LoremIpsum.mjs Normal file
View File

@ -0,0 +1,230 @@
/**
* Lorem Ipsum generator.
*
* @author Klaxon [klaxon@veyr.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
/**
* Generate lorem ipsum paragraphs.
*
* @param {number} length
* @returns {string}
*/
export function GenerateParagraphs(length=3) {
const paragraphs = [];
while (paragraphs.length < length) {
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
const sentences = [];
while (sentences.length < paragraphLength) {
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
const sentence = getWords(sentenceLength);
sentences.push(formatSentence(sentence));
}
paragraphs.push(formatParagraph(sentences));
}
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -2);
paragraphs[0] = replaceStart(paragraphs[0]);
return paragraphs.join("");
}
/**
* Generate lorem ipsum sentences.
*
* @param {number} length
* @returns {string}
*/
export function GenerateSentences(length=3) {
const sentences = [];
while (sentences.length < length) {
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
const sentence = getWords(sentenceLength);
sentences.push(formatSentence(sentence));
}
const paragraphs = sentencesToParagraphs(sentences);
return paragraphs.join("");
}
/**
* Generate lorem ipsum words.
*
* @param {number} length
* @returns {string}
*/
export function GenerateWords(length=3) {
const words = getWords(length);
const sentences = wordsToSentences(words);
const paragraphs = sentencesToParagraphs(sentences);
return paragraphs.join("");
}
/**
* Generate lorem ipsum bytes.
*
* @param {number} length
* @returns {string}
*/
export function GenerateBytes(length=3) {
const str = GenerateWords(length/3);
return str.slice(0, length);
}
/**
* Get array of randomly selected words from the lorem ipsum wordList.
*
* @param {number} length
* @returns {string[]}
* @private
*/
function getWords(length=3) {
const words = [];
let word;
let previousWord;
while (words.length < length){
do {
word = wordList[Math.floor(Math.random() * wordList.length)];
} while (previousWord === word);
words.push(word);
previousWord = word;
}
return words;
}
/**
* Convert an array of words into an array of sentences
*
* @param {string[]} words
* @returns {string[]}
* @private
*/
function wordsToSentences(words) {
const sentences = [];
while (words.length > 0) {
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
if (sentenceLength <= words.length) {
sentences.push(formatSentence(words.splice(0, sentenceLength)));
} else {
sentences.push(formatSentence(words.splice(0, words.length)));
}
}
return sentences;
}
/**
* Convert an array of sentences into an array of paragraphs
*
* @param {string[]} sentences
* @returns {string[]}
* @private
*/
function sentencesToParagraphs(sentences) {
const paragraphs = [];
while (sentences.length > 0) {
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
paragraphs.push(formatParagraph(sentences.splice(0, paragraphLength)));
}
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -1);
paragraphs[0] = replaceStart(paragraphs[0]);
return paragraphs;
}
/**
* Format an array of words into a sentence.
*
* @param {string[]} words
* @returns {string}
* @private
*/
function formatSentence(words) {
// 0.35 chance of a comma being added randomly to the sentence.
if (Math.random() < PROBABILITY_OF_A_COMMA) {
const pos = Math.round(Math.random()*(words.length-1));
words[pos] +=",";
}
let sentence = words.join(" ");
sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1);
sentence += ".";
return sentence;
}
/**
* Format an array of sentences into a paragraph.
*
* @param {string[]} sentences
* @returns {string}
* @private
*/
function formatParagraph(sentences) {
let paragraph = sentences.join(" ");
paragraph += "\n\n";
return paragraph;
}
/**
* Get a random number based on a mean and standard deviation.
*
* @param {number} mean
* @param {number} stdDev
* @returns {number}
* @private
*/
function getRandomLength(mean, stdDev) {
let length;
do {
length = Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1)*stdDev+mean);
} while (length <= 0);
return length;
}
/**
* Replace first 5 words with "Lorem ipsum dolor sit amet"
*
* @param {string[]} str
* @returns {string[]}
* @private
*/
function replaceStart(str) {
let words = str.split(" ");
if (words.length > 5) {
words.splice(0, 5, "Lorem", "ipsum", "dolor", "sit", "amet");
return words.join(" ");
} else {
const lorem = ["Lorem", "ipsum", "dolor", "sit", "amet"];
words = lorem.slice(0, words.length);
str = words.join(" ");
str += ".";
return str;
}
}
const SENTENCE_LENGTH_MEAN = 15;
const SENTENCE_LENGTH_STD_DEV = 9;
const PARAGRAPH_LENGTH_MEAN = 5;
const PARAGRAPH_LENGTH_STD_DEV = 2;
const PROBABILITY_OF_A_COMMA = 0.35;
const wordList = [
"ad", "adipisicing", "aliqua", "aliquip", "amet", "anim",
"aute", "cillum", "commodo", "consectetur", "consequat", "culpa",
"cupidatat", "deserunt", "do", "dolor", "dolore", "duis",
"ea", "eiusmod", "elit", "enim", "esse", "est",
"et", "eu", "ex", "excepteur", "exercitation", "fugiat",
"id", "in", "incididunt", "ipsum", "irure", "labore",
"laboris", "laborum", "Lorem", "magna", "minim", "mollit",
"nisi", "non", "nostrud", "nulla", "occaecat", "officia",
"pariatur", "proident", "qui", "quis", "reprehenderit", "sint",
"sit", "sunt", "tempor", "ullamco", "ut", "velit",
"veniam", "voluptate",
];

View File

@ -0,0 +1,175 @@
/**
* Emulation of the Bombe machine.
*
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {BombeMachine} from "../lib/Bombe";
import {ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector} from "../lib/Enigma";
/**
* Bombe operation
*/
class Bombe extends Operation {
/**
* Bombe constructor
*/
constructor() {
super();
this.name = "Bombe";
this.module = "Default";
this.description = "Emulation of the Bombe machine used to attack Enigma.<br><br>To run this you need to have a 'crib', which is some known plaintext for a chunk of the target ciphertext, and know the rotors used. (See the 'Bombe (multiple runs)' operation if you don't know the rotors.) The machine will suggest possible configurations of the Enigma. Each suggestion has the rotor start positions (left to right) and known plugboard pairs.<br><br>Choosing a crib: First, note that Enigma cannot encrypt a letter to itself, which allows you to rule out some positions for possible cribs. Secondly, the Bombe does not simulate the Enigma's middle rotor stepping. The longer your crib, the more likely a step happened within it, which will prevent the attack working. However, other than that, longer cribs are generally better. The attack produces a 'menu' which maps ciphertext letters to plaintext, and the goal is to produce 'loops': for example, with ciphertext ABC and crib CAB, we have the mappings A&lt;-&gt;C, B&lt;-&gt;A, and C&lt;-&gt;B, which produces a loop A-B-C-A. The more loops, the better the crib. The operation will output this: if your menu has too few loops, a large number of incorrect outputs will be produced. Try a different crib. If the menu seems good but the right answer isn't produced, your crib may be wrong, or you may have overlapped the middle rotor stepping - try a different crib.<br><br>Output is not sufficient to fully decrypt the data. You will have to recover the rest of the plugboard settings by inspection. And the ring position is not taken into account: this affects when the middle rotor steps. If your output is correct for a bit, and then goes wrong, adjust the ring and start position on the right-hand rotor together until the output improves. If necessary, repeat for the middle rotor.<br><br>By default this operation runs the checking machine, a manual process to verify the quality of Bombe stops, on each stop, discarding stops which fail. If you want to see how many times the hardware actually stops for a given input, disable the checking machine.";
this.infoURL = "https://wikipedia.org/wiki/Bombe";
this.inputType = "string";
this.outputType = "JSON";
this.presentType = "html";
this.args = [
{
name: "Model",
type: "argSelector",
value: [
{
name: "3-rotor",
off: [1]
},
{
name: "4-rotor",
on: [1]
}
]
},
{
name: "Left-most (4th) rotor",
type: "editableOption",
value: ROTORS_FOURTH,
defaultIndex: 0
},
{
name: "Left-hand rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 0
},
{
name: "Middle rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 1
},
{
name: "Right-hand rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 2
},
{
name: "Reflector",
type: "editableOption",
value: REFLECTORS
},
{
name: "Crib",
type: "string",
value: ""
},
{
name: "Crib offset",
type: "number",
value: 0
},
{
name: "Use checking machine",
type: "boolean",
value: true
}
];
}
/**
* 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)
*/
updateStatus(nLoops, nStops, progress) {
const msg = `Bombe run with ${nLoops} loop${nLoops === 1 ? "" : "s"} in menu (2+ desirable): ${nStops} stops, ${Math.floor(100 * progress)}% done`;
self.sendStatusMessage(msg);
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const model = args[0];
const reflectorstr = args[5];
let crib = args[6];
const offset = args[7];
const check = args[8];
const rotors = [];
for (let i=0; i<4; i++) {
if (i === 0 && model === "3-rotor") {
// No fourth rotor
continue;
}
let rstr = args[i + 1];
// The Bombe doesn't take stepping into account so we'll just ignore it here
if (rstr.includes("<")) {
rstr = rstr.split("<", 2)[0];
}
rotors.push(rstr);
}
// Rotors are handled in reverse
rotors.reverse();
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);
const reflector = new Reflector(reflectorstr);
let update;
if (ENVIRONMENT_IS_WORKER()) {
update = this.updateStatus;
} else {
update = undefined;
}
const bombe = new BombeMachine(rotors, reflector, ciphertext, crib, check, update);
const result = bombe.run();
return {
nLoops: bombe.nLoops,
result: result
};
}
/**
* Displays the Bombe 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: Rotor positions are listed left to right and start at the beginning of the crib, and ignore stepping and the ring setting. Some plugboard settings are determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided.\n\n`;
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>";
for (const [setting, stecker, decrypt] of output.result) {
html += `<tr><td>${setting}</td><td>${stecker}</td><td>${decrypt}</td></tr>\n`;
}
html += "</table>";
return html;
}
}
export default Bombe;

View File

@ -0,0 +1,95 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates";
/**
* Convert co-ordinate format operation
*/
class ConvertCoordinateFormat extends Operation {
/**
* ConvertCoordinateFormat constructor
*/
constructor() {
super();
this.name = "Convert co-ordinate format";
this.module = "Hashing";
this.description = "Converts geographical coordinates between different formats.<br><br>Supported formats:<ul><li>Degrees Minutes Seconds (DMS)</li><li>Degrees Decimal Minutes (DDM)</li><li>Decimal Degrees (DD)</li><li>Geohash</li><li>Military Grid Reference System (MGRS)</li><li>Ordnance Survey National Grid (OSNG)</li><li>Universal Transverse Mercator (UTM)</li></ul><br>The operation can try to detect the input co-ordinate format and delimiter automatically, but this may not always work correctly.";
this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Input Format",
"type": "option",
"value": ["Auto"].concat(FORMATS)
},
{
"name": "Input Delimiter",
"type": "option",
"value": [
"Auto",
"Direction Preceding",
"Direction Following",
"\\n",
"Comma",
"Semi-colon",
"Colon"
]
},
{
"name": "Output Format",
"type": "option",
"value": FORMATS
},
{
"name": "Output Delimiter",
"type": "option",
"value": [
"Space",
"\\n",
"Comma",
"Semi-colon",
"Colon"
]
},
{
"name": "Include Compass Directions",
"type": "option",
"value": [
"None",
"Before",
"After"
]
},
{
"name": "Precision",
"type": "number",
"value": 3
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (input.replace(/[\s+]/g, "") !== "") {
const [inFormat, inDelim, outFormat, outDelim, incDirection, precision] = args;
const result = convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision);
return result;
} else {
return input;
}
}
}
export default ConvertCoordinateFormat;

View File

@ -0,0 +1,125 @@
/**
* @author h345983745
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
/**
* DNS over HTTPS operation
*/
class DNSOverHTTPS extends Operation {
/**
* DNSOverHTTPS constructor
*/
constructor() {
super();
this.name = "DNS over HTTPS";
this.module = "Default";
this.description = [
"Takes a single domain name and performs a DNS lookup using DNS over HTTPS.",
"<br><br>",
"By default, <a href='https://developers.cloudflare.com/1.1.1.1/dns-over-https/'>Cloudflare</a> and <a href='https://developers.google.com/speed/public-dns/docs/dns-over-https'>Google</a> DNS over HTTPS services are supported.",
"<br><br>",
"Can be used with any service that supports the GET parameters <code>name</code> and <code>type</code>."
].join("\n");
this.infoURL = "https://wikipedia.org/wiki/DNS_over_HTTPS";
this.inputType = "string";
this.outputType = "JSON";
this.manualBake = true;
this.args = [
{
name: "Resolver",
type: "editableOption",
value: [
{
name: "Google",
value: "https://dns.google.com/resolve"
},
{
name: "Cloudflare",
value: "https://cloudflare-dns.com/dns-query"
}
]
},
{
name: "Request Type",
type: "option",
value: [
"A",
"AAAA",
"TXT",
"MX",
"DNSKEY",
"NS"
]
},
{
name: "Answer Data Only",
type: "boolean",
value: false
},
{
name: "Validate DNSSEC",
type: "boolean",
value: true
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {JSON}
*/
run(input, args) {
const [resolver, requestType, justAnswer, DNSSEC] = args;
let url = URL;
try {
url = new URL(resolver);
} catch (error) {
throw new OperationError(error.toString() +
"\n\nThis error could be caused by one of the following:\n" +
" - An invalid Resolver URL\n");
}
const params = {name: input, type: requestType, cd: DNSSEC};
url.search = new URLSearchParams(params);
return fetch(url, {headers: {"accept": "application/dns-json"}}).then(response => {
return response.json();
}).then(data => {
if (justAnswer) {
return extractData(data.Answer);
}
return data;
}).catch(e => {
throw new OperationError(`Error making request to ${url}\n${e.toString()}`);
});
}
}
/**
* Construct an array of just data from a DNS Answer section
*
* @private
* @param {JSON} data
* @returns {JSON}
*/
function extractData(data) {
if (typeof(data) == "undefined"){
return [];
} else {
const dataValues = [];
data.forEach(element => {
dataValues.push(element.data);
});
return dataValues;
}
}
export default DNSOverHTTPS;

View File

@ -43,7 +43,7 @@ class Divide extends Operation {
*/
run(input, args) {
const val = div(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
}
}

View File

@ -8,12 +8,12 @@
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import * as Enigma from "../lib/Enigma";
import {ROTORS, LETTERS, ROTORS_FOURTH, REFLECTORS, Rotor, Reflector, Plugboard, EnigmaMachine} from "../lib/Enigma";
/**
* Enigma operation
*/
class EnigmaOp extends Operation {
class Enigma extends Operation {
/**
* Enigma constructor
*/
@ -28,69 +28,88 @@ class EnigmaOp extends Operation {
this.outputType = "string";
this.args = [
{
name: "1st (right-hand) rotor",
name: "Model",
type: "argSelector",
value: [
{
name: "3-rotor",
off: [1, 2, 3]
},
{
name: "4-rotor",
on: [1, 2, 3]
}
]
},
{
name: "Left-most (4th) rotor",
type: "editableOption",
value: Enigma.ROTORS,
value: ROTORS_FOURTH,
defaultIndex: 0
},
{
name: "Left-most rotor ring setting",
type: "option",
value: LETTERS
},
{
name: "Left-most rotor initial value",
type: "option",
value: LETTERS
},
{
name: "Left-hand rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 0
},
{
name: "Left-hand rotor ring setting",
type: "option",
value: LETTERS
},
{
name: "Left-hand rotor initial value",
type: "option",
value: LETTERS
},
{
name: "Middle rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 1
},
{
name: "Middle rotor ring setting",
type: "option",
value: LETTERS
},
{
name: "Middle rotor initial value",
type: "option",
value: LETTERS
},
{
name: "Right-hand rotor",
type: "editableOption",
value: ROTORS,
// Default config is the rotors I-III *left to right*
defaultIndex: 2
},
{
name: "1st rotor ring setting",
name: "Right-hand rotor ring setting",
type: "option",
value: Enigma.LETTERS
value: LETTERS
},
{
name: "1st rotor initial value",
name: "Right-hand rotor initial value",
type: "option",
value: Enigma.LETTERS
},
{
name: "2nd rotor",
type: "editableOption",
value: Enigma.ROTORS,
defaultIndex: 1
},
{
name: "2nd rotor ring setting",
type: "option",
value: Enigma.LETTERS
},
{
name: "2nd rotor initial value",
type: "option",
value: Enigma.LETTERS
},
{
name: "3rd rotor",
type: "editableOption",
value: Enigma.ROTORS,
defaultIndex: 0
},
{
name: "3rd rotor ring setting",
type: "option",
value: Enigma.LETTERS
},
{
name: "3rd rotor initial value",
type: "option",
value: Enigma.LETTERS
},
{
name: "4th rotor",
type: "editableOption",
value: Enigma.ROTORS_OPTIONAL,
defaultIndex: 10
},
{
name: "4th rotor initial value",
type: "option",
value: Enigma.LETTERS
value: LETTERS
},
{
name: "Reflector",
type: "editableOption",
value: Enigma.REFLECTORS
value: REFLECTORS
},
{
name: "Plugboard",
@ -130,32 +149,27 @@ class EnigmaOp extends Operation {
* @returns {string}
*/
run(input, args) {
const [
rotor1str, rotor1ring, rotor1pos,
rotor2str, rotor2ring, rotor2pos,
rotor3str, rotor3ring, rotor3pos,
rotor4str, rotor4pos,
reflectorstr, plugboardstr,
removeOther
] = args;
const model = args[0];
const reflectorstr = args[13];
const plugboardstr = args[14];
const removeOther = args[15];
const rotors = [];
const [rotor1wiring, rotor1steps] = this.parseRotorStr(rotor1str, 1);
rotors.push(new Enigma.Rotor(rotor1wiring, rotor1steps, rotor1ring, rotor1pos));
const [rotor2wiring, rotor2steps] = this.parseRotorStr(rotor2str, 2);
rotors.push(new Enigma.Rotor(rotor2wiring, rotor2steps, rotor2ring, rotor2pos));
const [rotor3wiring, rotor3steps] = this.parseRotorStr(rotor3str, 3);
rotors.push(new Enigma.Rotor(rotor3wiring, rotor3steps, rotor3ring, rotor3pos));
if (rotor4str !== "") {
// Fourth rotor doesn't have a ring setting - A is equivalent to no setting
const [rotor4wiring, rotor4steps] = this.parseRotorStr(rotor4str, 4);
rotors.push(new Enigma.Rotor(rotor4wiring, rotor4steps, "A", rotor4pos));
for (let i=0; i<4; i++) {
if (i === 0 && model === "3-rotor") {
// Skip the 4th rotor settings
continue;
}
const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*3 + 1], 1);
rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*3 + 2], args[i*3 + 3]));
}
const reflector = new Enigma.Reflector(reflectorstr);
const plugboard = new Enigma.Plugboard(plugboardstr);
// Rotors are handled in reverse
rotors.reverse();
const reflector = new Reflector(reflectorstr);
const plugboard = new Plugboard(plugboardstr);
if (removeOther) {
input = input.replace(/[^A-Za-z]/g, "");
}
const enigma = new Enigma.EnigmaMachine(rotors, reflector, plugboard);
const enigma = new EnigmaMachine(rotors, reflector, plugboard);
let result = enigma.crypt(input);
if (removeOther) {
// Five character cipher groups is traditional
@ -197,4 +211,4 @@ class EnigmaOp extends Operation {
}
export default EnigmaOp;
export default Enigma;

View File

@ -0,0 +1,39 @@
/**
* @author masq [github.cyberchef@masq.cc]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* From Case Insensitive Regex operation
*/
class FromCaseInsensitiveRegex extends Operation {
/**
* FromCaseInsensitiveRegex constructor
*/
constructor() {
super();
this.name = "From Case Insensitive Regex";
this.module = "Default";
this.description = "Converts a case-insensitive regex string to a case sensitive regex string (no guarantee on it being the proper original casing) in case the i flag wasn't available at the time but now is, or you need it to be case-sensitive again.<br><br>e.g. <code>[mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*</code> becomes <code>Mozilla/[0-9].[0-9] .*</code>";
this.infoURL = "https://wikipedia.org/wiki/Regular_expression";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return input.replace(/\[[a-z]{2}\]/ig, m => m[1].toUpperCase() === m[2].toUpperCase() ? m[1] : m);
}
}
export default FromCaseInsensitiveRegex;

View File

@ -1,44 +0,0 @@
/**
* @author gchq77703 []
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import geohash from "ngeohash";
/**
* From Geohash operation
*/
class FromGeohash extends Operation {
/**
* FromGeohash constructor
*/
constructor() {
super();
this.name = "From Geohash";
this.module = "Crypto";
this.description = "Converts Geohash strings into Lat/Long coordinates. For example, <code>ww8p1r4t8</code> becomes <code>37.8324,112.5584</code>.";
this.infoURL = "https://wikipedia.org/wiki/Geohash";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return input.split("\n").map(line => {
const coords = geohash.decode(line);
return [coords.latitude, coords.longitude].join(",");
}).join("\n");
}
}
export default FromGeohash;

View File

@ -0,0 +1,70 @@
/**
* @author klaxon [klaxon@veyr.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum";
/**
* Generate Lorem Ipsum operation
*/
class GenerateLoremIpsum extends Operation {
/**
* GenerateLoremIpsum constructor
*/
constructor() {
super();
this.name = "Generate Lorem Ipsum";
this.module = "Default";
this.description = "Generate varying length lorem ipsum placeholder text.";
this.infoURL = "https://wikipedia.org/wiki/Lorem_ipsum";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Length",
"type": "number",
"value": "3"
},
{
"name": "Length in",
"type": "option",
"value": ["Paragraphs", "Sentences", "Words", "Bytes"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [length, lengthType] = args;
if (length < 1){
throw new OperationError("Length must be greater than 0");
}
switch (lengthType) {
case "Paragraphs":
return GenerateParagraphs(length);
case "Sentences":
return GenerateSentences(length);
case "Words":
return GenerateWords(length);
case "Bytes":
return GenerateBytes(length);
default:
throw new OperationError("Invalid length type");
}
}
}
export default GenerateLoremIpsum;

View File

@ -43,7 +43,7 @@ class Mean extends Operation {
*/
run(input, args) {
const val = mean(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
}
}

View File

@ -43,7 +43,7 @@ class Median extends Operation {
*/
run(input, args) {
const val = median(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
}
}

View File

@ -0,0 +1,305 @@
/**
* 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
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {BombeMachine} from "../lib/Bombe";
import {ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector} from "../lib/Enigma";
/**
* 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";
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.";
this.infoURL = "https://wikipedia.org/wiki/Bombe";
this.inputType = "string";
this.outputType = "JSON";
this.presentType = "html";
this.args = [
{
"name": "Standard Enigmas",
"type": "populateMultiOption",
"value": [
{
name: "German Service Enigma (First - 3 rotor)",
value: [
rotorsFormat(ROTORS, 0, 5),
"",
rotorsFormat(REFLECTORS, 0, 1)
]
},
{
name: "German Service Enigma (Second - 3 rotor)",
value: [
rotorsFormat(ROTORS, 0, 8),
"",
rotorsFormat(REFLECTORS, 0, 2)
]
},
{
name: "German Service Enigma (Third - 4 rotor)",
value: [
rotorsFormat(ROTORS, 0, 8),
rotorsFormat(ROTORS_FOURTH, 1, 2),
rotorsFormat(REFLECTORS, 2, 3)
]
},
{
name: "German Service Enigma (Fourth - 4 rotor)",
value: [
rotorsFormat(ROTORS, 0, 8),
rotorsFormat(ROTORS_FOURTH, 1, 3),
rotorsFormat(REFLECTORS, 2, 4)
]
},
{
name: "User defined",
value: ["", "", ""]
},
],
"target": [1, 2, 3]
},
{
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
},
{
name: "Use checking machine",
type: "boolean",
value: true
}
];
}
/**
* 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)
*/
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);
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`;
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];
const fourthRotorsStr = args[2];
const reflectorsStr = args[3];
let crib = args[4];
const offset = args[5];
const check = args[6];
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;
if (ENVIRONMENT_IS_WORKER()) {
update = this.updateStatus;
} else {
update = undefined;
}
let bombe = undefined;
const output = {bombeRuns: []};
// 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;
const start = new Date().getTime();
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) {
bombe = new BombeMachine(runRotors, reflector, ciphertext, crib, check);
output.nLoops = bombe.nLoops;
} else {
bombe.changeRotors(runRotors, reflector);
}
const result = bombe.run();
nStops += result.length;
if (update !== undefined) {
update(bombe.nLoops, nStops, nRuns / totalRuns, start);
}
if (result.length > 0) {
output.bombeRuns.push({
rotors: runRotors,
reflector: reflector.pairs,
result: result
});
}
}
}
}
}
}
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) {
html += `\nRotors: ${run.rotors.join(", ")}\nReflector: ${run.reflector}\n`;
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>";
for (const [setting, stecker, decrypt] of run.result) {
html += `<tr><td>${setting}</td><td>${stecker}</td><td>${decrypt}</td></tr>\n`;
}
html += "</table>\n";
}
return html;
}
}
export default MultipleBombe;

View File

@ -44,7 +44,7 @@ class Multiply extends Operation {
*/
run(input, args) {
const val = multi(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
}
}

View File

@ -228,40 +228,29 @@ function regexList (input, regex, displayTotal, matches, captureGroups) {
function regexHighlight (input, regex, displayTotal) {
let output = "",
title = "",
m,
hl = 1,
i = 0,
total = 0;
while ((m = regex.exec(input))) {
// Moves pointer when an empty string is matched (prevents infinite loop)
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
output = input.replace(regex, (match, ...args) => {
args.pop(); // Throw away full string
const offset = args.pop(),
groups = args;
// Add up to match
output += Utils.escapeHtml(input.slice(i, m.index));
title = `Offset: ${m.index}\n`;
if (m.length > 1) {
title = `Offset: ${offset}\n`;
if (groups.length) {
title += "Groups:\n";
for (let n = 1; n < m.length; ++n) {
title += `\t${n}: ${m[n]}\n`;
for (let i = 0; i < groups.length; i++) {
title += `\t${i+1}: ${Utils.escapeHtml(groups[i] || "")}\n`;
}
}
// Add match with highlighting
output += "<span class='hl"+hl+"' title='"+title+"'>" + Utils.escapeHtml(m[0]) + "</span>";
// Switch highlight
hl = hl === 1 ? 2 : 1;
i = regex.lastIndex;
total++;
}
// Add all after final match
output += Utils.escapeHtml(input.slice(i, input.length));
return `<span class='hl${hl}' title='${title}'>${Utils.escapeHtml(match)}</span>`;
});
if (displayTotal)
output = "Total found: " + total + "\n\n" + output;

View File

@ -44,7 +44,7 @@ class StandardDeviation extends Operation {
*/
run(input, args) {
const val = stdDev(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
}

View File

@ -0,0 +1,153 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import XRegExp from "xregexp";
import Operation from "../Operation";
import Recipe from "../Recipe";
import Dish from "../Dish";
/**
* Subsection operation
*/
class Subsection extends Operation {
/**
* Subsection constructor
*/
constructor() {
super();
this.name = "Subsection";
this.flowControl = true;
this.module = "Regex";
this.description = "Select a part of the input data using a regular expression (regex), and run all subsequent operations on each match separately.<br><br>You can use up to one capture group, where the recipe will only be run on the data in the capture group. If there's more than one capture group, only the first one will be operated on.";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Section (regex)",
"type": "string",
"value": ""
},
{
"name": "Case sensitive matching",
"type": "boolean",
"value": true
},
{
"name": "Global matching",
"type": "boolean",
"value": true
},
{
"name": "Ignore errors",
"type": "boolean",
"value": false
}
];
}
/**
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on
* @param {Operation[]} state.opList - The list of operations in the recipe
* @returns {Object} - The updated state of the recipe
*/
async run(state) {
const opList = state.opList,
inputType = opList[state.progress].inputType,
outputType = opList[state.progress].outputType,
input = await state.dish.get(inputType),
ings = opList[state.progress].ingValues,
[section, caseSensitive, global, ignoreErrors] = ings,
subOpList = [];
if (input && section !== "") {
// Create subOpList for each tranche to operate on
// all remaining operations unless we encounter a Merge
for (let i = state.progress + 1; i < opList.length; i++) {
if (opList[i].name === "Merge" && !opList[i].disabled) {
break;
} else {
subOpList.push(opList[i]);
}
}
let flags = "",
inOffset = 0,
output = "",
m,
progress = 0;
if (!caseSensitive) flags += "i";
if (global) flags += "g";
const regex = new XRegExp(section, flags),
recipe = new Recipe();
recipe.addOperations(subOpList);
state.forkOffset += state.progress + 1;
// Take a deep(ish) copy of the ingredient values
const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));
let matched = false;
// Run recipe over each match
while ((m = regex.exec(input))) {
matched = true;
// Add up to match
let matchStr = m[0];
if (m.length === 1) { // No capture groups
output += input.slice(inOffset, m.index);
inOffset = m.index + m[0].length;
} else if (m.length >= 2) {
matchStr = m[1];
// Need to add some of the matched string that isn't in the capture group
output += input.slice(inOffset, m.index + m[0].indexOf(m[1]));
// Set i to be after the end of the first capture group
inOffset = m.index + m[0].indexOf(m[1]) + m[1].length;
}
// Baseline ing values for each tranche so that registers are reset
subOpList.forEach((op, i) => {
op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
});
const dish = new Dish();
dish.set(matchStr, inputType);
try {
progress = await recipe.execute(dish, 0, state);
} catch (err) {
if (!ignoreErrors) {
throw err;
}
progress = err.progress + 1;
}
output += await dish.get(outputType);
if (!regex.global) break;
}
// If no matches were found, advance progress to after a Merge op
// Otherwise, the operations below Subsection will be run on all the input data
if (!matched) {
state.progress += subOpList.length + 1;
}
output += input.slice(inOffset);
state.progress += progress;
state.dish.set(output, outputType);
}
return state;
}
}
export default Subsection;

View File

@ -44,7 +44,7 @@ class Subtract extends Operation {
*/
run(input, args) {
const val = sub(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
}
}

View File

@ -44,7 +44,7 @@ class Sum extends Operation {
*/
run(input, args) {
const val = sum(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
}
}

View File

@ -20,7 +20,7 @@ class ToBase64 extends Operation {
this.name = "To Base64";
this.module = "Default";
this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation decodes data from an ASCII Base64 string back into its raw format.<br><br>e.g. <code>aGVsbG8=</code> becomes <code>hello</code>";
this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation encodes raw data into an ASCII Base64 string.<br><br>e.g. <code>hello</code> becomes <code>aGVsbG8=</code>";
this.infoURL = "https://wikipedia.org/wiki/Base64";
this.inputType = "ArrayBuffer";
this.outputType = "string";

View File

@ -0,0 +1,39 @@
/**
* @author masq [github.cyberchef@masq.cc]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* To Case Insensitive Regex operation
*/
class ToCaseInsensitiveRegex extends Operation {
/**
* ToCaseInsensitiveRegex constructor
*/
constructor() {
super();
this.name = "To Case Insensitive Regex";
this.module = "Default";
this.description = "Converts a case-sensitive regex string into a case-insensitive regex string in case the i flag is unavailable to you.<br><br>e.g. <code>Mozilla/[0-9].[0-9] .*</code> becomes <code>[mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*</code>";
this.infoURL = "https://wikipedia.org/wiki/Regular_expression";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return input.replace(/[a-z]/ig, m => `[${m.toLowerCase()}${m.toUpperCase()}]`);
}
}
export default ToCaseInsensitiveRegex;

View File

@ -1,53 +0,0 @@
/**
* @author gchq77703 []
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import geohash from "ngeohash";
/**
* To Geohash operation
*/
class ToGeohash extends Operation {
/**
* ToGeohash constructor
*/
constructor() {
super();
this.name = "To Geohash";
this.module = "Crypto";
this.description = "Converts Lat/Long coordinates into a Geohash string. For example, <code>37.8324,112.5584</code> becomes <code>ww8p1r4t8</code>.";
this.infoURL = "https://wikipedia.org/wiki/Geohash";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Precision",
type: "number",
value: 9
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [precision] = args;
return input.split("\n").map(line => {
line = line.replace(/ /g, "");
if (line === "") return "";
return geohash.encode(...line.split(",").map(num => parseFloat(num)), precision);
}).join("\n");
}
}
export default ToGeohash;

View File

@ -57,7 +57,7 @@ class ToTable extends Operation {
const [cellDelims, rowDelims, firstRowHeader, format] = args;
// Process the input into a nested array of elements.
const tableData = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
const tableData = Utils.parseCSV(Utils.escapeHtml(input), cellDelims.split(""), rowDelims.split(""));
if (!tableData.length) return "";

View File

@ -0,0 +1,122 @@
/**
* @author Matt C [matt@artemisbot.uk]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Yara from "libyara-wasm";
/**
* YARA Rules operation
*/
class YARARules extends Operation {
/**
* YARARules constructor
*/
constructor() {
super();
this.name = "YARA Rules";
this.module = "Yara";
this.description = "YARA is a tool developed at VirusTotal, primarily aimed at helping malware researchers to identify and classify malware samples. It matches based on rules specified by the user containing textual or binary patterns and a boolean expression. For help on writing rules, see the <a href='https://yara.readthedocs.io/en/latest/writingrules.html'>YARA documentation.</a>";
this.infoURL = "https://wikipedia.org/wiki/YARA";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
name: "Rules",
type: "text",
value: "",
rows: 5
},
{
name: "Show strings",
type: "boolean",
value: false
},
{
name: "Show string lengths",
type: "boolean",
value: false
},
{
name: "Show metadata",
type: "boolean",
value: false
},
{
name: "Show counts",
type: "boolean",
value: true
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Instantiating YARA...");
const [rules, showStrings, showLengths, showMeta, showCounts] = args;
return new Promise((resolve, reject) => {
Yara().then(yara => {
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Converting data for YARA.");
let matchString = "";
const inpArr = new Uint8Array(input); // Turns out embind knows that JS uint8array <==> C++ std::string
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Running YARA matching.");
const resp = yara.run(inpArr, rules);
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Processing data.");
if (resp.compileErrors.size() > 0) {
for (let i = 0; i < resp.compileErrors.size(); i++) {
const compileError = resp.compileErrors.get(i);
if (!compileError.warning) {
reject(new OperationError(`Error on line ${compileError.lineNumber}: ${compileError.message}`));
} else {
matchString += `Warning on line ${compileError.lineNumber}: ${compileError.message}`;
}
}
}
const matchedRules = resp.matchedRules;
for (let i = 0; i < matchedRules.size(); i++) {
const rule = matchedRules.get(i);
const matches = rule.resolvedMatches;
let meta = "";
if (showMeta && rule.metadata.size() > 0) {
meta += " [";
for (let j = 0; j < rule.metadata.size(); j++) {
meta += `${rule.metadata.get(j).identifier}: ${rule.metadata.get(j).data}, `;
}
meta = meta.slice(0, -2) + "]";
}
const countString = showCounts ? `${matches.size()} time${matches.size() > 1 ? "s" : ""}` : "";
if (matches.size() === 0 || !(showStrings || showLengths)) {
matchString += `Input matches rule "${rule.ruleName}"${meta}${countString.length > 0 ? ` ${countString}`: ""}.\n`;
} else {
matchString += `Rule "${rule.ruleName}"${meta} matches (${countString}):\n`;
for (let j = 0; j < matches.size(); j++) {
const match = matches.get(j);
if (showStrings || showLengths) {
matchString += `Pos ${match.location}, ${showLengths ? `length ${match.matchLength}, ` : ""}identifier ${match.stringIdentifier}${showStrings ? `, data: "${match.data}"` : ""}\n`;
}
}
}
}
resolve(matchString);
});
});
}
}
export default YARARules;

View File

@ -51,10 +51,12 @@ class App {
*/
setup() {
document.dispatchEvent(this.manager.appstart);
this.initialiseSplitter();
this.loadLocalStorage();
this.populateOperationsList();
this.manager.setup();
this.manager.output.saveBombe();
this.resetLayout();
this.setCompileMessage();
@ -122,6 +124,9 @@ class App {
// Reset attemptHighlight flag
this.options.attemptHighlight = true;
// Remove all current indicators
this.manager.recipe.updateBreakpointIndicator(false);
this.manager.worker.bake(
this.getInput(), // The user's input
this.getRecipeConfig(), // The configuration of the recipe
@ -238,12 +243,18 @@ class App {
/**
* Sets up the adjustable splitter to allow the user to resize areas of the page.
*
* @param {boolean} [minimise=false] - Set this flag if attempting to minimuse frames to 0 width
*/
initialiseSplitter() {
initialiseSplitter(minimise=false) {
if (this.columnSplitter) this.columnSplitter.destroy();
if (this.ioSplitter) this.ioSplitter.destroy();
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
sizes: [20, 30, 50],
minSize: [240, 370, 450],
minSize: minimise ? [0, 0, 0] : [240, 370, 450],
gutterSize: 4,
expandToMin: false,
onDrag: function() {
this.manager.recipe.adjustWidth();
}.bind(this)
@ -251,7 +262,8 @@ class App {
this.ioSplitter = Split(["#input", "#output"], {
direction: "vertical",
gutterSize: 4
gutterSize: 4,
minSize: minimise ? [0, 0] : [100, 100]
});
this.resetLayout();
@ -467,6 +479,7 @@ class App {
const item = this.manager.recipe.addOperation(recipeConfig[i].op);
// Populate arguments
log.debug(`Populating arguments for ${recipeConfig[i].op}`);
const args = item.querySelectorAll(".arg");
for (let j = 0; j < args.length; j++) {
if (recipeConfig[i].args[j] === undefined) continue;
@ -492,6 +505,8 @@ class App {
item.querySelector(".breakpoint").click();
}
this.manager.recipe.triggerArgEvents(item);
this.progress = 0;
}

View File

@ -4,6 +4,8 @@
* @license Apache-2.0
*/
import Utils from "../core/Utils";
/**
* Object to handle the creation of operation ingredients.
*/
@ -25,6 +27,7 @@ class HTMLIngredient {
this.value = config.value;
this.disabled = config.disabled || false;
this.hint = config.hint || false;
this.rows = config.rows || false;
this.target = config.target;
this.defaultIndex = config.defaultIndex || 0;
this.toggleValues = config.toggleValues;
@ -39,7 +42,7 @@ class HTMLIngredient {
*/
toHtml() {
let html = "",
i, m;
i, m, eventFn;
switch (this.type) {
case "string":
@ -142,10 +145,11 @@ class HTMLIngredient {
</div>`;
break;
case "populateOption":
case "populateMultiOption":
html += `<div class="form-group">
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
<select
class="form-control arg"
class="form-control arg no-state-change populate-option"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>`;
@ -155,14 +159,20 @@ class HTMLIngredient {
} else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) {
html += "</optgroup>";
} else {
html += `<option populate-value="${this.value[i].value}">${this.value[i].name}</option>`;
const val = this.type === "populateMultiOption" ?
JSON.stringify(this.value[i].value) :
this.value[i].value;
html += `<option populate-value='${Utils.escapeHtml(val)}'>${this.value[i].name}</option>`;
}
}
html += `</select>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this);
eventFn = this.type === "populateMultiOption" ?
this.populateMultiOptionChange :
this.populateOptionChange;
this.manager.addDynamicListener("#" + this.id, "change", eventFn, this);
break;
case "editableOption":
html += `<div class="form-group input-group">
@ -229,10 +239,32 @@ class HTMLIngredient {
class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
rows="${this.rows ? this.rows : 3}"
${this.disabled ? "disabled" : ""}>${this.value}</textarea>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break;
case "argSelector":
html += `<div class="form-group inline">
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
<select
class="form-control arg inline arg-selector"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>`;
for (i = 0; i < this.value.length; i++) {
html += `<option ${this.defaultIndex === i ? "selected" : ""}
turnon="${JSON.stringify(this.value[i].on || [])}"
turnoff="${JSON.stringify(this.value[i].off || [])}">
${this.value[i].name}
</option>`;
}
html += `</select>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this);
break;
default:
break;
}
@ -248,6 +280,9 @@ class HTMLIngredient {
* @param {event} e
*/
populateOptionChange(e) {
e.preventDefault();
e.stopPropagation();
const el = e.target;
const op = el.parentNode.parentNode;
const target = op.querySelectorAll(".arg")[this.target];
@ -260,6 +295,37 @@ class HTMLIngredient {
}
/**
* Handler for populate multi option changes.
* Populates the relevant arguments with the specified values.
*
* @param {event} e
*/
populateMultiOptionChange(e) {
e.preventDefault();
e.stopPropagation();
const el = e.target;
const op = el.parentNode.parentNode;
const args = op.querySelectorAll(".arg");
const targets = this.target.map(i => args[i]);
const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute("populate-value"));
const evt = new Event("change");
for (let i = 0; i < targets.length; i++) {
targets[i].value = vals[i];
}
// Fire change event after all targets have been assigned
this.manager.recipe.ingChange();
// Send change event for each target once all have been assigned, to update the label placement.
for (const target of targets) {
target.dispatchEvent(evt);
}
}
/**
* Handler for editable option clicks.
* Populates the input box with the selected value.
@ -280,6 +346,33 @@ class HTMLIngredient {
this.manager.recipe.ingChange();
}
/**
* Handler for argument selector changes.
* Shows or hides the relevant arguments for this operation.
*
* @param {event} e
*/
argSelectorChange(e) {
e.preventDefault();
e.stopPropagation();
const option = e.target.options[e.target.selectedIndex];
const op = e.target.closest(".operation");
const args = op.querySelectorAll(".ingredients .form-group");
const turnon = JSON.parse(option.getAttribute("turnon"));
const turnoff = JSON.parse(option.getAttribute("turnoff"));
args.forEach((arg, i) => {
if (turnon.includes(i)) {
arg.classList.remove("d-none");
}
if (turnoff.includes(i)) {
arg.classList.add("d-none");
}
});
}
}
export default HTMLIngredient;

View File

@ -243,14 +243,22 @@ class InputWaiter {
}
if (file) {
this.closeFile();
this.loaderWorker = new LoaderWorker();
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.loaderWorker.postMessage({"file": file});
this.set(file);
this.loadFile(file);
}
}
/**
* Handler for open input button events
* Loads the opened data into the input textarea
*
* @param {event} e
*/
inputOpen(e) {
e.preventDefault();
const file = e.srcElement.files[0];
this.loadFile(file);
}
/**
* Handler for messages sent back by the LoaderWorker.
@ -306,6 +314,22 @@ class InputWaiter {
}
/**
* Loads a file into the input.
*
* @param {File} file
*/
loadFile(file) {
if (file) {
this.closeFile();
this.loaderWorker = new LoaderWorker();
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.loaderWorker.postMessage({"file": file});
this.set(file);
}
}
/**
* Handler for clear IO events.
* Resets the input, output and info areas.

View File

@ -137,12 +137,16 @@ class Manager {
this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe);
this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe);
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
this.addDynamicListener("textarea.arg", "dragover", this.recipe.textArgDragover, this.recipe);
this.addDynamicListener("textarea.arg", "dragleave", this.recipe.textArgDragLeave, this.recipe);
this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe);
// Input
this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input);
this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input);
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
this.addListeners("#open-file", "change", this.input.inputOpen, this.input);
this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);
this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input);
this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input);

View File

@ -319,6 +319,7 @@ class OutputWaiter {
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
this.app.initialiseSplitter(true);
this.app.columnSplitter.collapse(0);
this.app.columnSplitter.collapse(1);
this.app.ioSplitter.collapse(0);
@ -328,30 +329,61 @@ class OutputWaiter {
} else {
$(el).attr("data-original-title", "Maximise output pane");
el.querySelector("i").innerHTML = "fullscreen";
this.app.initialiseSplitter(false);
this.app.resetLayout();
}
}
/**
* Shows or hides the loading icon.
* Save bombe object then remove it from the DOM so that it does not cause performance issues.
*/
saveBombe() {
this.bombeEl = document.getElementById("bombe");
this.bombeEl.parentNode.removeChild(this.bombeEl);
}
/**
* Shows or hides the output loading screen.
* The animated Bombe SVG, whilst quite aesthetically pleasing, is reasonably CPU
* intensive, so we remove it from the DOM when not in use. We only show it if the
* recipe is taking longer than 200ms. We add it to the DOM just before that so that
* it is ready to fade in without stuttering.
*
* @param {boolean} value
* @param {boolean} value - true == show loader
*/
toggleLoader(value) {
clearTimeout(this.appendBombeTimeout);
clearTimeout(this.outputLoaderTimeout);
const outputLoader = document.getElementById("output-loader"),
outputElement = document.getElementById("output-text");
outputElement = document.getElementById("output-text"),
animation = document.getElementById("output-loader-animation");
if (value) {
this.manager.controls.hideStaleIndicator();
this.bakingStatusTimeout = setTimeout(function() {
// Start a timer to add the Bombe to the DOM just before we make it
// visible so that there is no stuttering
this.appendBombeTimeout = setTimeout(function() {
animation.appendChild(this.bombeEl);
}.bind(this), 150);
// Show the loading screen
this.outputLoaderTimeout = setTimeout(function() {
outputElement.disabled = true;
outputLoader.style.visibility = "visible";
outputLoader.style.opacity = 1;
this.manager.controls.toggleBakeButtonFunction(true);
}.bind(this), 200);
} else {
clearTimeout(this.bakingStatusTimeout);
// Remove the Bombe from the DOM to save resources
this.outputLoaderTimeout = setTimeout(function () {
try {
animation.removeChild(this.bombeEl);
} catch (err) {}
}.bind(this), 500);
outputElement.disabled = false;
outputLoader.style.opacity = 0;
outputLoader.style.visibility = "hidden";
@ -476,7 +508,7 @@ class OutputWaiter {
*/
showMagicButton(opSequence, result, recipeConfig) {
const magicButton = document.getElementById("magic");
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.truncate(result, 30)}"</span>`);
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.escapeHtml(Utils.truncate(result), 30)}"</span>`);
magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, "");
magicButton.classList.remove("hidden");
}

View File

@ -205,6 +205,7 @@ class RecipeWaiter {
* @fires Manager#statechange
*/
ingChange(e) {
if (e && e.target && e.target.classList.contains("no-state-change")) return;
window.dispatchEvent(this.manager.statechange);
}
@ -340,10 +341,11 @@ class RecipeWaiter {
/**
* Moves or removes the breakpoint indicator in the recipe based on the position.
*
* @param {number} position
* @param {number|boolean} position - If boolean, turn off all indicators
*/
updateBreakpointIndicator(position) {
const operations = document.querySelectorAll("#rec-list li.operation");
if (typeof position === "boolean") position = operations.length;
for (let i = 0; i < operations.length; i++) {
if (i === position) {
operations[i].classList.add("break");
@ -376,6 +378,7 @@ class RecipeWaiter {
}
}
/**
* Adds the specified operation to the recipe.
*
@ -428,6 +431,23 @@ class RecipeWaiter {
}
/**
* Triggers various change events for operation arguments that have just been initialised.
*
* @param {HTMLElement} op
*/
triggerArgEvents(op) {
// Trigger populateOption and argSelector events
const triggerableOptions = op.querySelectorAll(".populate-option, .arg-selector");
const evt = new Event("change", {bubbles: true});
if (triggerableOptions.length) {
for (const el of triggerableOptions) {
el.dispatchEvent(evt);
}
}
}
/**
* Handler for operationadd events.
*
@ -437,6 +457,8 @@ class RecipeWaiter {
*/
opAdd(e) {
log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`);
this.triggerArgEvents(e.target);
window.dispatchEvent(this.manager.statechange);
}
@ -454,6 +476,75 @@ class RecipeWaiter {
}
/**
* Handler for text argument dragover events.
* Gives the user a visual cue to show that items can be dropped here.
*
* @param {event} e
*/
textArgDragover (e) {
// This will be set if we're dragging an operation
if (e.dataTransfer.effectAllowed === "move")
return false;
e.stopPropagation();
e.preventDefault();
e.target.closest("textarea.arg").classList.add("dropping-file");
}
/**
* Handler for text argument dragleave events.
* Removes the visual cue.
*
* @param {event} e
*/
textArgDragLeave (e) {
e.stopPropagation();
e.preventDefault();
e.target.classList.remove("dropping-file");
}
/**
* Handler for text argument drop events.
* Loads the dragged data into the argument textarea.
*
* @param {event} e
*/
textArgDrop(e) {
// This will be set if we're dragging an operation
if (e.dataTransfer.effectAllowed === "move")
return false;
e.stopPropagation();
e.preventDefault();
const targ = e.target;
const file = e.dataTransfer.files[0];
const text = e.dataTransfer.getData("Text");
targ.classList.remove("dropping-file");
if (text) {
targ.value = text;
return;
}
if (file) {
const reader = new FileReader();
const self = this;
reader.onload = function (e) {
targ.value = e.target.result;
// Trigger floating label move
const changeEvent = new Event("change");
targ.dispatchEvent(changeEvent);
window.dispatchEvent(self.manager.statechange);
};
reader.readAsText(file);
}
}
/**
* Sets register values.
*
@ -479,6 +570,7 @@ class RecipeWaiter {
op.insertAdjacentHTML("beforeend", registerListEl);
}
/**
* Adjusts the number of ingredient columns as the width of the recipe changes.
*/
@ -490,20 +582,25 @@ class RecipeWaiter {
this.ingredientChildRuleID = null;
// Find relevant rules in the stylesheet
for (const i in document.styleSheets[0].cssRules) {
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
this.ingredientRuleID = i;
}
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
this.ingredientChildRuleID = i;
// try/catch for chrome 64+ CORS error on cssRules.
try {
for (const i in document.styleSheets[0].cssRules) {
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
this.ingredientRuleID = i;
}
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
this.ingredientChildRuleID = i;
}
}
} catch (e) {
// Do nothing.
}
}
if (!this.ingredientRuleID || !this.ingredientChildRuleID) return;
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID],
ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID];
const ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
if (recList.clientWidth < 450) {
ingredientRule.style.gridTemplateColumns = "auto auto";

View File

@ -81,7 +81,11 @@
if (!el.classList.contains("loading"))
el.classList.add("loading"); // Causes CSS transition on first message
el.innerHTML = msg;
} catch (err) {} // Ignore errors if DOM not yet ready
} catch (err) {
// This error was likely caused by the DOM not being ready yet,
// so we wait another second and then try again.
setTimeout(changeLoadingMsg, 1000);
}
}
changeLoadingMsg();
@ -225,6 +229,10 @@
<div class="title no-select">
<label for="input-text">Input</label>
<span class="float-right">
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-open-file" data-toggle="tooltip" title="Open file as input" onclick="document.getElementById('open-file').click();">
<i class="material-icons">input</i>
<input type="file" id="open-file" style="display: none">
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output">
<i class="material-icons">delete</i>
</button>
@ -315,7 +323,9 @@
</div>
</div>
<div id="output-loader">
<div class="loader"></div>
<div id="output-loader-animation">
<object id="bombe" data="<%- require('../static/images/bombe.svg') %>" width="100%" height="100%"></object>
</div>
<div class="loading-msg"></div>
</div>
</div>

View File

@ -0,0 +1,261 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Turing-Welchman Bombe SVG animation
@author n1474335 [n1474335@gmail.com]
@copyright Crown Copyright 2019
@license Apache-2.0
-->
<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" width="550px" height="350px" viewBox="0 0 550 350" xml:space="preserve" onload="setup(evt)">
<script type="text/ecmascript">
// <![CDATA[
function setup(evt) {
const allRotors = evt.target.ownerDocument.querySelectorAll('.rotor');
const rotors = [];
const initTime = Date.now();
const tick = 360/26;
const speed = 1000; // Time for one full rotation of the fast rotor
for (const rotor of allRotors) {
const row = parseInt(rotor.classList.value.match(/row(\d)/)[1], 10);
const startPos = row === 2 ? tick * Math.floor(Math.random()*26) : 0;
const bbox = rotor.getBBox();
const x = bbox.width/2 + bbox.x;
const y = bbox.height/2 + bbox.y;
const wait = row === 0 ? speed/26/1.5 : row === 1 ? speed : speed*26;
rotor.setAttribute("transform", "rotate(" + startPos + ", " + x + ", " + y + ")");
rotors.push({
el: rotor,
pos: startPos,
x: x,
y: y,
last: initTime,
wait: wait
});
}
setInterval(function() {
const now = Date.now();
for (const rotor of rotors) {
if (now > (rotor.last + rotor.wait)) {
const numTicks = Math.floor((now - rotor.last) / rotor.wait);
rotor.pos = (rotor.pos + tick * numTicks) % 360;
rotor.last = rotor.last + rotor.wait * numTicks;
rotor.el.setAttribute("transform", "rotate(" + rotor.pos + ", " + rotor.x + ", " + rotor.y + ")");
} else {
// Don't bother looking at the rest
break;
}
}
}, speed/26/1.5 - 5);
}
// ]]>
</script>
<style>
.row0 {--primary-color: #e5d41b;}
.row1 {--primary-color: #be1e2d;}
.row2 {--primary-color: #337f24;}
</style>
<symbol id="rotor">
<g transform="scale(0.1)">
<circle id="casing" class="ring-color" style="fill: var(--primary-color, #be1e2d)" cx="692" cy="674" r="505"/>
<circle id="alphabet-ring" fill="#7a5340" cx="692" cy="674" r="477"/>
<circle id="face" fill="#412612" cx="692" cy="674" r="412"/>
<circle id="plate" fill="#F1F2F2" cx="692" cy="674" r="185"/>
<g id="alphabet" fill="#ffffff" font-family="sans-serif" font-size="36">
<text transform="matrix(0.9731 0.2303 -0.2303 0.9731 779.8848 256.5488)">Z</text>
<text transform="matrix(0.8903 0.4554 -0.4554 0.8903 875.2021 288.6948)">Y</text>
<text transform="matrix(0.7561 0.6545 -0.6545 0.7561 961.8311 343.6372)">X</text>
<text transform="matrix(0.5696 0.8219 -0.8219 0.5696 1033.0146 417.4619)">W</text>
<text transform="matrix(0.3454 0.9385 -0.9385 0.3454 1088.1104 515.4634)">V</text>
<text transform="matrix(0.1078 0.9942 -0.9942 0.1078 1114.4678 614.5894)">U</text>
<text transform="matrix(-0.1302 0.9915 -0.9915 -0.1302 1116.1533 719.1523)">T</text>
<text transform="matrix(-0.3623 0.9321 -0.9321 -0.3623 1093.8984 817.2373)">S</text>
<text transform="matrix(-0.5767 0.817 -0.817 -0.5767 1048.0635 908.9912)">R</text>
<text transform="matrix(-0.7588 0.6514 -0.6514 -0.7588 980.2002 988.5342)">Q</text>
<text transform="matrix(-0.8942 0.4476 -0.4476 -0.8942 893.3154 1050.1416)">P</text>
<text transform="matrix(-0.9766 0.215 -0.215 -0.9766 797.7471 1087.3965)">O</text>
<text transform="matrix(-0.9996 -0.0298 0.0298 -0.9996 692.0405 1100.5684)">N</text>
<text transform="matrix(-0.961 -0.2765 0.2765 -0.961 588.2832 1087.9443)">M</text>
<text transform="matrix(-0.8654 -0.5011 0.5011 -0.8654 487.3003 1048.2471)">L</text>
<text transform="matrix(-0.7244 -0.6894 0.6894 -0.7244 406.814 991.1895)">K</text>
<text transform="matrix(-0.5456 -0.838 0.838 -0.5456 339.3418 913.8809)">J</text>
<text transform="matrix(-0.3508 -0.9364 0.9364 -0.3508 294.3491 828.2446)">I</text>
<text transform="matrix(-0.1295 -0.9916 0.9916 -0.1295 270.9233 742.6519)">H</text>
<text transform="matrix(0.1153 -0.9933 0.9933 0.1153 266.8784 638.1958)">G</text>
<text transform="matrix(0.3526 -0.9358 0.9358 0.3526 288.9976 533.9849)">F</text>
<text transform="matrix(0.5645 -0.8255 0.8255 0.5645 333.0195 443.5317)">E</text>
<text transform="matrix(0.7459 -0.666 0.666 0.7459 398.4409 364.5073)">D</text>
<text transform="matrix(0.8853 -0.4651 0.4651 0.8853 482.4824 302.3418)">C</text>
<text transform="matrix(0.9716 -0.2365 0.2365 0.9716 579.1396 262.5479)">B</text>
<text transform="matrix(0.9999 0.0162 -0.0162 0.9999 680.5581 247.4321)">A</text>
</g>
<g id="holes">
<circle stroke="#C49A6C" cx="692" cy="438.782" r="40.816"/>
<circle stroke="#C49A6C" cx="927.219" cy="674" r="40.816"/>
<circle stroke="#C49A6C" cx="692" cy="909.219" r="40.816"/>
<circle stroke="#C49A6C" cx="456.781" cy="674" r="40.816"/>
<circle stroke="#C49A6C" cx="574.391" cy="470.295" r="40.816"/>
<circle stroke="#C49A6C" cx="895.706" cy="556.39" r="40.816"/>
<circle stroke="#C49A6C" cx="809.609" cy="877.706" r="40.816"/>
<circle stroke="#C49A6C" cx="488.295" cy="791.609" r="40.816"/>
<circle stroke="#C49A6C" cx="488.295" cy="556.39" r="40.816"/>
<circle stroke="#C49A6C" cx="809.609" cy="470.293" r="40.816"/>
<circle stroke="#C49A6C" cx="895.706" cy="791.609" r="40.816"/>
<circle stroke="#C49A6C" cx="574.391" cy="877.705" r="40.816"/>
</g>
<g id="plate-screws">
<g>
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="693.223" cy="543.521" r="25.342"/>
<line fill="#939598" stroke="#808285" stroke-width="7" x1="693.446" y1="519.729" x2="693" y2="567.311"/>
</g>
<g>
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="822.479" cy="675.221" r="25.342"/>
<line fill="#939598" stroke="#808285" stroke-width="7" x1="798.689" y1="674.999" x2="846.271" y2="675.445"/>
</g>
<g>
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="562.605" cy="673.886" r="25.341"/>
<line fill="#939598" stroke="#808285" stroke-width="7" x1="538.814" y1="673.663" x2="586.396" y2="674.108"/>
</g>
<g>
<circle fill="#BCBEC0" stroke="#808285" stroke-width="2" cx="691.863" cy="805.587" r="25.341"/>
<line fill="#939598" stroke="#808285" stroke-width="7" x1="692.086" y1="781.798" x2="691.64" y2="829.379"/>
</g>
</g>
<path id="pin" fill-rule="evenodd" fill="#D1D3D4" stroke="#939598"
d="M956.275,448.71c-0.969,0.389-1.924,0.836-2.848,1.302
c-5.875,2.962-10.965,7.197-16.168,11.152c-5.885,4.475-11.93,8.739-17.834,13.187c-10.688,8.049-21.533,15.888-32.24,23.907
c-2.199,1.643-4.436,3.238-6.609,4.912c-14.525,11.139-28.867,22.534-43.559,33.452c-9.428,7.004-19.436,13.346-28.354,21.005
c-12.459,10.694-24.723,22.592-35.869,34.65c-5.281,5.711-10.656,11.297-16.243,16.711c-3.063,2.967-5.874,5.382-8.114,8.997
c-2.256,3.646-4.589,7.558-6.059,11.586c-2.757,7.565,0.999,14.189,3.413,21.241c5.533,16.161-0.56,32.288-11.42,44.675
c-6.989,7.974-15.39,15.932-25.247,20.16c-5.45,2.337-12.057,3.965-18.012,4.105c-6.159,0.148-11.914-1.53-17.568-3.802
c-5.215-2.094-14.936-7.879-20.029-3.758c-4.529,3.667-8.937,7.59-13.502,11.251c-1.359,1.088-2.961,2.043-4.15,3.33
c0.001,0,16.224-17.545,16.596-17.948c2.86-3.092,0.168-9.246-1.066-12.486c-2.088-5.471-3.199-10.951-4.633-16.611
c-1.02-4.023-1.841-8.044-1.548-12.215c0.637-9.093,3.98-19.698,8.918-27.356c6.4-9.925,16.834-18.061,27.527-22.879
c14.831-6.684,29.543-3.252,44.133,2.23c5.441,2.044,12.285-2.206,16.829-4.831c6.116-3.534,11.542-8.171,16.117-13.547
c9.109-10.707,19.505-20.119,29.089-30.368c4.945-5.288,10.229-10.295,15.316-15.45l25.586-29.884l31.963-43.979
c0,0,29.025-38.288,29.113-38.409c9.037-11.917,24.822-22.94,25.588-39.161c0.617-13.024-14.27-17.184-24.727-16.841
c-7.41,0.242-16.311,0.894-23.117,4.161c-15.1,7.248-28.342,15.616-34.676,31.979c-2.504,6.464-4.865,12.671-6.76,19.319
c-2.051,7.208-5.539,11.212-9.826,17.088c-10.779,14.778-24.389,24.73-40.998,32.1c-4.74,2.104-9.229,4.293-14.08,6.129
c-3.961,1.5-9.706,3.104-12.91,5.747c-5.948,4.907-10.334,14.214-13.357,21.205c-1.911,4.418-3.278,9.046-5.009,13.535
c-2.069,5.37-2.532,11.326-4.88,16.507c-1.33,2.935-1.91,5.994-4.104,8.414c-2.609,2.877-4.623,4.939-8.159,6.693
c-3.45,1.713-6.487,3.997-10.305,4.736c-2.717,0.528-5.277,1.418-8.023,1.794c-8.203,1.127-16.54,1.73-24.695,3.159
c-3.994,0.7-7.947,2.283-11.792,3.534c-5.167,1.681-10.116,5.972-14.846,8.78c-10.3,6.119-20.007,15.004-27.479,24.277
c-5.337,6.625-8.976,14.32-11.926,22.251c-2.169,5.833-4.357,11.754-5.061,17.977c-0.564,5.001-0.074,10.062-0.502,15.077
c-0.706,8.26-3.203,17.47-9.294,23.414c-5.363,5.234-14.174,10.834-21.666,12.043c-7.607,1.226-15.016,0.118-20.697-5.407
c-5.092-4.954-9.277-11.304-15.816-14.539c-3.873-1.917-8.116-2.357-12.351-1.588c-10.82,1.965-17.767,7.374-18.428,18.637
c-0.545,9.325,1.999,15.171,6.731,22.947c4.323,7.103,5.315,15.456,9.255,22.756c4.052,7.503,7.825,15.248,12.169,22.583
c3.05,5.156,6.832,9.664,10.749,14.176c1.717,1.978,3.554,4.901,5.732,6.378c5.639,3.827,10.784,3.305,17.032,1.951
c2.175-0.473,3.233,0.047,4.694,1.679c1.557,1.74,1.399,1.609,0.505,3.68c-2.732,6.329-4.573,12.085-0.1,18.199
c3.421,4.675,8.728,9.01,13.531,12.271c7.165,4.865,14.799,8.835,22.414,12.933c8.94,4.808,18.489,8.188,27.963,11.765
c6.597,2.491,11.068,7.997,17.229,11.186c6.945,3.595,13.775,1.032,19.691-3.353c5.688-4.216,9.634-9.578,10.066-16.804
c0.415-6.938-1.239-14.501-5.51-20.082c-4.163-5.439-10.751-8.996-13.229-15.664c-2.506-6.741-0.296-14.597,1.313-21.3
c1.606-6.687,3.798-12.642,9.227-17.17c5.458-4.554,12.49-7.653,19.583-8.294c7.954-0.721,15.985-0.105,23.912-1.162
c7.9-1.052,15.855-4.074,22.918-7.696c5.104-2.616,9.105-6.979,13.309-10.789c8.875-8.052,18.1-16.759,23.735-27.459
c4.125-7.834,8.521-15.675,11.016-24.222c1.154-3.962,2.098-8.083,2.316-12.204c0.424-7.886-1.686-16.176,2.564-23.391
c5.645-9.582,14.869-17.408,25.563-20.561c8.727-2.571,17.697-4.624,25.963-8.522c7.234-3.413,16-7.686,20.182-14.833
c1.822-3.116,3.109-6.775,4.361-10.158c1.752-4.719,3.648-9.389,5.4-14.108c2.082-5.625,4.016-10.898,6.887-16.146
c2.551-4.656,6.072-7.849,9.471-11.864c2.504-2.956,4.539-5.815,7.773-8.031c3.229-2.208,6.805-3.835,10.088-5.952
c3.469-2.237,6.955-4.47,10.578-6.445c4.242-2.312,8.557-3.716,13.207-4.92c10.176-2.643,19.592-6.376,26.959-14.134
c6.977-7.349,13.82-15.747,16.816-25.566c2.938-9.634,3.967-20.147,2.086-30.07c-0.973-5.124-2.291-11.331-5.824-15.367
C964.873,446.457,960.432,447.042,956.275,448.71z"/>
<circle id="center-nut" fill="#d1a26a" stroke="#a88e75" stroke-width="25" cx="692" cy="674" r="60"/>
<g id="pin-screws">
<circle fill="#BCBEC0" stroke="#58595B" cx="768.174" cy="545.468" r="18.485"/>
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="750.079" y1="545.298" x2="786.273" y2="545.635"/>
<path fill="#BCBEC0" stroke="#58595B" d="M819.834,579.439c-10.211-0.094-18.564,8.103-18.66,18.313
c-0.094,10.208,8.102,18.562,18.313,18.657c10.205,0.095,18.563-8.102,18.656-18.312
C838.24,587.889,830.041,579.535,819.834,579.439z"/>
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="819.49" y1="616.02" x2="819.826" y2="579.826"/>
<circle fill="#BCBEC0" stroke="#58595B" cx="626.351" cy="736.463" r="18.486"/>
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="639.026" y1="749.378" x2="613.672" y2="723.543"/>
<circle fill="#BCBEC0" stroke="#58595B" cx="526.668" cy="709.157" r="18.485"/>
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="526.498" y1="727.252" x2="526.837" y2="691.059"/>
<circle fill="#BCBEC0" stroke="#58595B" cx="654.839" cy="839.752" r="18.486"/>
<line fill="#BCBEC0" stroke="#939598" stroke-width="5" x1="636.744" y1="839.583" x2="672.937" y2="839.922"/>
</g>
<g id="plate-mini-screws">
<circle fill="#E6E7E8" stroke="#A7A9AC" cx="786.206" cy="769.987" r="15.332"/>
<line fill="#E6E7E8" stroke="#A7A9AC" stroke-width="5" x1="775.494" y1="780.5" x2="796.92" y2="759.472"/>
<circle fill="#E6E7E8" stroke="#A7A9AC" cx="599.966" cy="580.227" r="15.333"/>
<line fill="#E6E7E8" stroke="#A7A9AC" stroke-width="5" x1="589.254" y1="590.74" x2="610.682" y2="569.712"/>
</g>
<g id="spring">
<line fill="none" stroke="#808285" stroke-width="2" x1="561.307" y1="722.169" x2="534.592" y2="739.515"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="565.744" y1="726.689" x2="539.028" y2="744.034"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="570.179" y1="731.21" x2="543.465" y2="748.555"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="574.616" y1="735.73" x2="547.901" y2="753.074"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="579.052" y1="740.25" x2="552.336" y2="757.596"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="583.722" y1="745.008" x2="557.007" y2="762.354"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="588.158" y1="749.529" x2="561.443" y2="766.873"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="592.595" y1="754.047" x2="565.879" y2="771.393"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="597.03" y1="758.568" x2="570.315" y2="775.913"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="601.466" y1="763.088" x2="574.751" y2="780.434"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="605.902" y1="767.608" x2="579.188" y2="784.953"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="610.338" y1="772.128" x2="583.623" y2="789.474"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="614.775" y1="776.647" x2="588.06" y2="793.994"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="619.211" y1="781.169" x2="592.496" y2="798.513"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="623.648" y1="785.688" x2="596.933" y2="803.034"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="628.084" y1="790.209" x2="601.369" y2="807.553"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="632.52" y1="794.728" x2="605.806" y2="812.074"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="636.956" y1="799.249" x2="610.241" y2="816.593"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="641.393" y1="803.77" x2="614.678" y2="821.113"/>
<line fill="none" stroke="#808285" stroke-width="2" x1="645.83" y1="808.288" x2="619.114" y2="825.634"/>
</g>
<g id="face-nuts">
<g>
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="340.617,715.657 300.423,704.888
289.653,664.693 319.077,635.27 359.271,646.04 370.041,686.233 "/>
<path fill="#BCBEC0" stroke="#58595B" stroke-width="3" d="M306.759,698.144
c-12.516-12.755-12.326-33.236,0.428-45.752c12.752-12.516,33.234-12.324,45.75,0.431c12.516,12.75,12.324,33.233-0.428,45.748
C339.755,711.087,319.275,710.895,306.759,698.144z"/>
<line fill="#939598" stroke="#808285" stroke-width="7" x1="351.527" y1="654.208" x2="308.171" y2="696.757"/>
</g>
<g>
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="702.77,354.86 662.576,344.091
651.806,303.896 681.23,274.473 721.424,285.243 732.194,325.437 "/>
<path fill="#603913" stroke="#3C2415" stroke-width="3" d="M668.912,337.347c-12.516-12.755-12.325-33.236,0.428-45.752c12.752-12.516,33.235-12.324,45.75,0.431
c12.516,12.75,12.324,33.233-0.428,45.748C701.909,350.29,681.428,350.098,668.912,337.347z"/>
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="713.68" y1="293.411" x2="670.324" y2="335.96"/>
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="670.324" y1="293.392" x2="713.68" y2="335.941"/>
</g>
<g>
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="702.77,1072.723 662.576,1061.953
651.806,1021.759 681.23,992.335 721.424,1003.105 732.193,1043.299 "/>
<path fill="#603913" stroke="#3C2415" stroke-width="3" d="M668.912,1055.21c-12.516-12.756-12.325-33.236,0.428-45.752c12.752-12.516,33.235-12.324,45.75,0.431
c12.516,12.75,12.324,33.233-0.428,45.747C701.909,1068.152,681.428,1067.96,668.912,1055.21z"/>
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="713.68" y1="1011.272" x2="670.324" y2="1053.822"/>
<line fill="#939598" stroke="#3C2415" stroke-width="7" x1="670.324" y1="1011.254" x2="713.68" y2="1053.804"/>
</g>
<g>
<polygon fill-rule="evenodd" fill="#E6E7E8" stroke="#939598" points="1038.556,715.658 1078.749,704.888
1089.521,664.694 1060.097,635.27 1019.901,646.041 1009.132,686.234 "/>
<path fill="#BCBEC0" stroke="#58595B" stroke-width="3" d="M1072.413,698.145
c12.516-12.755,12.326-33.236-0.428-45.752c-12.752-12.516-33.234-12.324-45.75,0.431c-12.516,12.75-12.324,33.233,0.428,45.748
C1039.417,711.087,1059.897,710.896,1072.413,698.145z"/>
<line fill="#939598" stroke="#808285" stroke-width="7" x1="1027.646" y1="654.208" x2="1071.001" y2="696.757"/>
</g>
</g>
</g>
</symbol>
<g class="rotor row0"><use xlink:href="#rotor" x="0" y="0" /></g>
<g class="rotor row0"><use xlink:href="#rotor" x="105" y="0" /></g>
<g class="rotor row0"><use xlink:href="#rotor" x="210" y="0" /></g>
<g class="rotor row0"><use xlink:href="#rotor" x="315" y="0" /></g>
<g class="rotor row0"><use xlink:href="#rotor" x="420" y="0" /></g>
<g class="rotor row1"><use xlink:href="#rotor" x="0" y="105" /></g>
<g class="rotor row1"><use xlink:href="#rotor" x="105" y="105" /></g>
<g class="rotor row1"><use xlink:href="#rotor" x="210" y="105" /></g>
<g class="rotor row1"><use xlink:href="#rotor" x="315" y="105" /></g>
<g class="rotor row1"><use xlink:href="#rotor" x="420" y="105" /></g>
<g class="rotor row2"><use xlink:href="#rotor" x="0" y="210" /></g>
<g class="rotor row2"><use xlink:href="#rotor" x="105" y="210" /></g>
<g class="rotor row2"><use xlink:href="#rotor" x="210" y="210" /></g>
<g class="rotor row2"><use xlink:href="#rotor" x="315" y="210" /></g>
<g class="rotor row2"><use xlink:href="#rotor" x="420" y="210" /></g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -73,6 +73,30 @@
background-color: var(--primary-background-colour);
visibility: hidden;
opacity: 0;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.5s ease;
}
#output-loader-animation {
display: block;
position: absolute;
width: 60%;
height: 60%;
top: 10%;
transition: all 0.5s ease;
}
#output-loader .loading-msg {
opacity: 1;
font-family: var(--primary-font-family);
line-height: var(--primary-line-height);
color: var(--primary-font-colour);
left: unset;
top: 30%;
position: relative;
transition: all 0.5s ease;
}
@ -123,6 +147,7 @@
.dropping-file {
border: 5px dashed var(--drop-file-border-colour) !important;
margin: -5px;
}
#stale-indicator {
@ -138,16 +163,6 @@
margin-bottom: 5px;
}
#output-loader .loading-msg {
opacity: 1;
font-family: var(--primary-font-family);
line-height: var(--primary-line-height);
color: var(--primary-font-colour);
top: 50%;
transition: all 0.5s ease;
}
#magic {
opacity: 1;
visibility: visibile;

View File

@ -65,8 +65,8 @@
left: calc(50% - 200px);
top: calc(50% + 50px);
text-align: center;
margin-top: 50px;
opacity: 0;
font-size: 18px;
}
.loading-msg.loading {
@ -160,3 +160,12 @@
transform: translate3d(0, 200px, 0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View File

@ -45,7 +45,6 @@ import "./tests/DateTime";
import "./tests/ExtractEmailAddresses";
import "./tests/Fork";
import "./tests/FromDecimal";
import "./tests/FromGeohash";
import "./tests/Hash";
import "./tests/HaversineDistance";
import "./tests/Hexdump";
@ -77,12 +76,16 @@ import "./tests/SetUnion";
import "./tests/StrUtils";
import "./tests/SymmetricDifference";
import "./tests/TextEncodingBruteForce";
import "./tests/ToGeohash";
import "./tests/TranslateDateTimeFormat";
import "./tests/Magic";
import "./tests/ParseTLV";
import "./tests/Media";
import "./tests/ToFromInsensitiveRegex";
import "./tests/YARA.mjs";
import "./tests/ConvertCoordinateFormat";
import "./tests/Enigma";
import "./tests/Bombe";
import "./tests/MultipleBombe";
// Cannot test operations that use the File type yet
//import "./tests/SplitColourChannels";

View File

@ -0,0 +1,242 @@
/**
* Bombe machine tests.
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
// Plugboard for this test is BO LC KE GA
name: "Bombe: 3 rotor (self-stecker)",
input: "BBYFLTHHYIJQAYBBYS",
expectedMatch: /<td>LGA<\/td><td>SS<\/td><td>VFISUSGTKSTMPSUNAK<\/td>/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"THISISATESTMESSAGE", 0, false
]
}
]
},
{
// This test produces a menu that doesn't use the first letter, which is also a good test
name: "Bombe: 3 rotor (other stecker)",
input: "JBYALIHDYNUAAVKBYM",
expectedMatch: /<td>LGA<\/td><td>AG<\/td><td>QFIMUMAFKMQSKMYNGW<\/td>/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"THISISATESTMESSAGE", 0, false
]
}
]
},
{
name: "Bombe: crib offset",
input: "AAABBYFLTHHYIJQAYBBYS", // first three chars here are faked
expectedMatch: /<td>LGA<\/td><td>SS<\/td><td>VFISUSGTKSTMPSUNAK<\/td>/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"THISISATESTMESSAGE", 3, false
]
}
]
},
{
name: "Bombe: multiple stops",
input: "BBYFLTHHYIJQAYBBYS",
expectedMatch: /<td>LGA<\/td><td>TT<\/td><td>VFISUSGTKSTMPSUNAK<\/td>/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"THISISATESTM", 0, false
]
}
]
},
{
name: "Bombe: checking machine",
input: "BBYFLTHHYIJQAYBBYS",
expectedMatch: /<td>LGA<\/td><td>TT AG BO CL EK FF HH II JJ SS YY<\/td><td>THISISATESTMESSAGE<\/td>/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"THISISATESTM", 0, true
]
}
]
},
// This test is a bit slow - it takes about 12s on my test hardware
{
name: "Bombe: 4 rotor",
input: "LUOXGJSHGEDSRDOQQX",
expectedMatch: /<td>LHSC<\/td><td>SS<\/td><td>HHHSSSGQUUQPKSEKWK<\/td>/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"4-rotor",
"LEYJVCNIXWPBQMDRTAKZGFUHOS", // Beta
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
"THISISATESTMESSAGE", 0, false
]
}
]
},
{
name: "Bombe: no crib",
input: "JBYALIHDYNUAAVKBYM",
expectedMatch: /Crib cannot be empty/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"", 0, false
]
}
]
},
{
name: "Bombe: short crib",
input: "JBYALIHDYNUAAVKBYM",
expectedMatch: /Crib is too short/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"A", 0, false
]
}
]
},
{
name: "Bombe: invalid crib",
input: "JBYALIHDYNUAAVKBYM",
expectedMatch: /Invalid crib: .* in both ciphertext and crib/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"AAAAAAAA", 0, false
]
}
]
},
{
name: "Bombe: long crib",
input: "JBYALIHDYNUAAVKBYM",
expectedMatch: /Crib overruns supplied ciphertext/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"CCCCCCCCCCCCCCCCCCCCCC", 0, false
]
}
]
},
{
name: "Bombe: really long crib",
input: "BBBBBBBBBBBBBBBBBBBBBBBBBB",
expectedMatch: /Crib is too long/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"AAAAAAAAAAAAAAAAAAAAAAAAAA", 0, false
]
}
]
},
{
name: "Bombe: negative offset",
input: "AAAAA",
expectedMatch: /Offset cannot be negative/,
recipeConfig: [
{
"op": "Bombe",
"args": [
"3-rotor",
"",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"BBBBB", -1, false
]
}
]
},
// Enigma tests cover validation of rotors and reflector
]);

View File

@ -0,0 +1,211 @@
/**
* Convert co-ordinate format tests
*
* @author j433866
*
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
/**
* TEST CO-ORDINATES
* DD: 51.504°,-0.126°,
* DDM: 51° 30.24',-0° 7.56',
* DMS: 51° 30' 14.4",-0° 7' 33.6",
* Geohash: gcpvj0h0x,
* MGRS: 30U XC 99455 09790,
* OSNG: TQ 30163 80005,
* UTM: 30N 699456 5709791,
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
name: "Co-ordinates: From Decimal Degrees to Degrees Minutes Seconds",
input: "51.504°,-0.126°,",
expectedOutput: "51° 30' 14.4\",-0° 7' 33.6\",",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Degrees Minutes Seconds", "Comma", "None", 1]
},
],
},
{
name: "Co-ordinates: From Degrees Minutes Seconds to Decimal Degrees",
input: "51° 30' 14.4\",-0° 7' 33.6\",",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Degrees Minutes Seconds", "Comma", "Decimal Degrees", "Comma", "None", 3]
},
],
},
{
name: "Co-ordinates: From Decimal Degrees to Degrees Decimal Minutes",
input: "51.504°,-0.126°,",
expectedOutput: "51° 30.24',-0° 7.56',",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Degrees Decimal Minutes", "Comma", "None", 2]
}
]
},
{
name: "Co-ordinates: From Degrees Decimal Minutes to Decimal Degrees",
input: "51° 30.24',-0° 7.56',",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Degrees Decimal Minutes", "Comma", "Decimal Degrees", "Comma", "None", 3]
}
]
},
{
name: "Co-ordinates: From Decimal Degrees to Decimal Degrees",
input: "51.504°,-0.126°,",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "None", 3]
}
]
},
{
name: "Co-ordinates: From Decimal Degrees to Geohash",
input: "51.504°,-0.126°,",
expectedOutput: "gcpvj0h0x,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Geohash", "Comma", "None", 9]
},
],
},
{
name: "Co-ordinates: From Geohash to Decimal Degrees",
input: "gcpvj0h0x,",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Geohash", "Comma", "Decimal Degrees", "Comma", "None", 3]
},
],
},
{
name: "Co-ordinates: From Decimal Degrees to MGRS",
input: "51.504°,-0.126°,",
expectedOutput: "30U XC 99455 09790,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Military Grid Reference System", "Comma", "None", 10]
},
],
},
{
name: "Co-ordinates: From MGRS to Decimal Degrees",
input: "30U XC 99455 09790,",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Military Grid Reference System", "Comma", "Decimal Degrees", "Comma", "None", 3]
}
]
},
{
name: "Co-ordinates: From Decimal Degrees to OSNG",
input: "51.504°,-0.126°,",
expectedOutput: "TQ 30163 80005,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Ordnance Survey National Grid", "Comma", "None", 10]
},
],
},
{
name: "Co-ordinates: From OSNG to Decimal Degrees",
input: "TQ 30163 80005,",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Ordnance Survey National Grid", "Comma", "Decimal Degrees", "Comma", "None", 3]
},
],
},
{
name: "Co-ordinates: From Decimal Degrees to UTM",
input: "51.504°,-0.126°,",
expectedOutput: "30 N 699456 5709791,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Universal Transverse Mercator", "Comma", "None", 0]
},
],
},
{
name: "Co-ordinates: From UTM to Decimal Degrees",
input: "30 N 699456 5709791,",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Universal Transverse Mercator", "Comma", "Decimal Degrees", "Comma", "None", 3]
},
],
},
{
name: "Co-ordinates: Directions in input, not output",
input: "N51.504°,W0.126°,",
expectedOutput: "51.504°,-0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "None", 3]
},
],
},
{
name: "Co-ordinates: Directions in input and output",
input: "N51.504°,W0.126°,",
expectedOutput: "N 51.504°,W 0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "Before", 3]
},
],
},
{
name: "Co-ordinates: Directions not in input, in output",
input: "51.504°,-0.126°,",
expectedOutput: "N 51.504°,W 0.126°,",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "Before", 3]
},
],
},
{
name: "Co-ordinates: Directions not in input, in converted output",
input: "51.504°,-0.126°,",
expectedOutput: "N 51° 30' 14.4\",W 0° 7' 33.6\",",
recipeConfig: [
{
op: "Convert co-ordinate format",
args: ["Decimal Degrees", "Comma", "Degrees Minutes Seconds", "Comma", "Before", 3]
},
],
}
]);

View File

@ -17,11 +17,12 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"3-rotor",
"", "A", "A",
// Note: start on Z because it steps when the key is pressed
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
""
]
@ -38,10 +39,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "W",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "F",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "N",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "F",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "W",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -59,10 +61,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "B", "Z",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "B", "Z",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -79,10 +82,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "W", "Z",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "F", "A",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "N", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "F", "A",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "W", "Z",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -99,10 +103,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -118,10 +123,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -129,10 +135,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "Z",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -149,10 +156,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "U",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "D",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "D",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "U",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -169,10 +177,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "U",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "E",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "E",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "U",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -189,10 +198,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "S",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "D",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "D",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "S",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -208,10 +218,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "H", "Z",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "H", "Z",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -227,10 +238,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "H", "F",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "C", "D",
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "Q", "A",
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "C", "D",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "H", "F",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW",
""
]
@ -246,10 +258,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "D", "Q",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "P", "F",
"4-rotor",
"LEYJVCNIXWPBQMDRTAKZGFUHOS", "A", "X", // Beta
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "O", "E",
"LEYJVCNIXWPBQMDRTAKZGFUHOS", "X", // Beta
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "P", "F",
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "D", "Q",
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
""
]
@ -265,10 +278,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "W", "U", // IV
"VZBRGITYUPSDNHLXAWMJQOFECK<A", "M", "G", // V
"4-rotor",
"FSOKANUERHMBTIYCWLQPZXVGJD", "A", "L", // Gamma
"JPGVOUMFYQBENHZRDKASXLICTW<AN", "A", "J", // VI
"FSOKANUERHMBTIYCWLQPZXVGJD", "L", // Gamma
"VZBRGITYUPSDNHLXAWMJQOFECK<A", "M", "G", // V
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "W", "U", // IV
"AR BD CO EJ FN GT HK IV LM PW QZ SX UY", // C thin
""
]
@ -284,10 +298,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN", "U", "Z", // VIII
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "O", "O", // IV
"4-rotor",
"FSOKANUERHMBTIYCWLQPZXVGJD", "A", "I", // Gamma
"NZJHGRCXMYSWBOUFAIVLPEKQDT<AN", "I", "V", // VII
"FSOKANUERHMBTIYCWLQPZXVGJD", "I", // Gamma
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "O", "O", // IV
"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN", "U", "Z", // VIII
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
"WN MJ LX YB FP QD US IH CE GR"
]
@ -303,16 +318,37 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN", "U", "Z", // VIII
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "O", "O", // IV
"4-rotor",
"FSOKANUERHMBTIYCWLQPZXVGJD", "A", "I", // Gamma
"NZJHGRCXMYSWBOUFAIVLPEKQDT<AN", "I", "V", // VII
"FSOKANUERHMBTIYCWLQPZXVGJD", "I", // Gamma
"ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "O", "O", // IV
"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN", "U", "Z", // VIII
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
"WN MJ LX YB FP QD US IH CE GR"
]
}
]
},
{
// Decryption test on real message
name: "Enigma: decryption 2",
input: "LANOTCTOUARBBFPMHPHGCZXTDYGAHGUFXGEWKBLKGJWLQXXTGPJJAVTOCKZFSLPPQIHZFXOEBWIIEKFZLCLOAQJULJOYHSSMBBGWHZANVOIIPYRBRTDJQDJJOQKCXWDNBBTYVXLYTAPGVEATXSONPNYNQFUDBBHHVWEPYEYDOHNLXKZDNWRHDUWUJUMWWVIIWZXIVIUQDRHYMNCYEFUAPNHOTKHKGDNPSAKNUAGHJZSMJBMHVTREQEDGXHLZWIFUSKDQVELNMIMITHBHDBWVHDFYHJOQIHORTDJDBWXEMEAYXGYQXOHFDMYUXXNOJAZRSGHPLWMLRECWWUTLRTTVLBHYOORGLGOWUXNXHMHYFAACQEKTHSJW",
expectedOutput: "KRKRALLEXXFOLGENDESISTSOFORTBEKANNTZUGEBENXXICHHABEFOLGELNBEBEFEHLERHALTENXXJANSTERLEDESBISHERIGXNREICHSMARSCHALLSJGOERINGJSETZTDERFUEHRERSIEYHVRRGRZSSADMIRALYALSSEINENNACHFOLGEREINXSCHRIFTLSCHEVOLLMACHTUNTERWEGSXABSOFORTSOLLENSIESAEMTLICHEMASSNAHMENVERFUEGENYDIESICHAUSDERGEGENWAERTIGENLAGEERGEBENXGEZXREICHSLEITEIKKTULPEKKJBORMANNJXXOBXDXMMMDURNHFKSTXKOMXADMXUUUBOOIEXKP",
recipeConfig: [
{
"op": "Enigma",
"args": [
"4-rotor",
"LEYJVCNIXWPBQMDRTAKZGFUHOS", "E", "C", // Beta
"VZBRGITYUPSDNHLXAWMJQOFECK<A", "P", "D", // V
"JPGVOUMFYQBENHZRDKASXLICTW<AN", "E", "S", // VI
"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN", "L", "Z", // VIII
"AR BD CO EJ FN GT HK IV LM PW QZ SX UY", // C thin
"AE BF CM DQ HU JN LX PR SZ VW"
]
}
]
},
{
// Non-alphabet characters drop test
name: "Enigma: non-alphabet drop",
@ -322,10 +358,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"", true
]
@ -341,10 +378,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"", false
]
@ -359,10 +397,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQ", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQ", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
""
]
@ -377,10 +416,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQo", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQo", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
""
]
@ -395,10 +435,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQA", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQA", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
""
]
@ -413,10 +454,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<RR", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<RR", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
""
]
@ -431,10 +473,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<a", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<a", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
""
]
@ -451,10 +494,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AY BR CU DH EQ FS GL IP JX KN MO", // B
""
]
@ -464,16 +508,17 @@ TestRegister.addTests([
{
name: "Enigma: reflector validation 2",
input: "Hello, world. This is a test.",
expectedOutput: "Reflector: cannot connect A to itself",
expectedOutput: "Reflector must have exactly 13 pairs covering every letter",
recipeConfig: [
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AA BR CU DH EQ FS GL IP JX KN MO TZ", // B
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AA BR CU DH EQ FS GL IP JX KN MO TZ VV WY", // B
""
]
}
@ -487,10 +532,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AY AR CU DH EQ FS GL IP JX KN MO TZ", // B
""
]
@ -505,10 +551,11 @@ TestRegister.addTests([
{
"op": "Enigma",
"args": [
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"3-rotor",
"", "A", "A",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", "A", "A", // I
"", "A",
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "A", "A", // II
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "A", "A", // III
"AYBR CU DH EQ FS GL IP JX KN MO TZ", // B
""
]

View File

@ -1,55 +0,0 @@
/**
* To Geohash tests
*
* @author gchq77703
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
name: "From Geohash",
input: "ww8p1r4t8",
expectedOutput: "37.83238649368286,112.55838632583618",
recipeConfig: [
{
op: "From Geohash",
args: [],
},
],
},
{
name: "From Geohash",
input: "ww8p1r",
expectedOutput: "37.83416748046875,112.5604248046875",
recipeConfig: [
{
op: "From Geohash",
args: [],
},
],
},
{
name: "From Geohash",
input: "ww8",
expectedOutput: "37.265625,113.203125",
recipeConfig: [
{
op: "From Geohash",
args: [],
},
],
},
{
name: "From Geohash",
input: "w",
expectedOutput: "22.5,112.5",
recipeConfig: [
{
op: "From Geohash",
args: [],
},
],
},
]);

View File

@ -0,0 +1,49 @@
/**
* Bombe machine tests.
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
name: "Multi-Bombe: 3 rotor",
input: "BBYFLTHHYIJQAYBBYS",
expectedMatch: /<td>LGA<\/td><td>SS<\/td><td>VFISUSGTKSTMPSUNAK<\/td>/,
recipeConfig: [
{
"op": "Multiple Bombe",
"args": [
// I, II and III
"User defined",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\nAJDKSIRUXBLHWTMCQGZNPYFVOE<F\nBDFHJLCPRTXVZNYEIWGAKMUSQO<W",
"",
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
"THISISATESTMESSAGE", 0, false
]
}
]
},
/*
* This is too slow to run regularly
{
name: "Multi-Bombe: 4 rotor",
input: "LUOXGJSHGEDSRDOQQX",
expectedMatch: /<td>LHSC<\/td><td>SS<\/td><td>HHHSSSGQUUQPKSEKWK<\/td>/,
recipeConfig: [
{
"op": "Multiple Bombe",
"args": [
// I, II and III
"User defined",
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\nAJDKSIRUXBLHWTMCQGZNPYFVOE<F\nBDFHJLCPRTXVZNYEIWGAKMUSQO<W",
"LEYJVCNIXWPBQMDRTAKZGFUHOS", // Beta
"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV", // B thin
"THISISATESTMESSAGE", 0, false
]
}
]
},
*/
]);

View File

@ -0,0 +1,56 @@
/**
* To/From Case Insensitive Regex tests.
*
* @author masq [github.cyberchef@masq.cc]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
name: "To Case Insensitive Regex: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "To Case Insensitive Regex",
args: [],
},
],
},
{
name: "From Case Insensitive Regex: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "From Case Insensitive Regex",
args: [],
},
],
},
{
name: "To Case Insensitive Regex: simple test",
input: "S0meth!ng",
expectedOutput: "[sS]0[mM][eE][tT][hH]![nN][gG]",
recipeConfig: [
{
op: "To Case Insensitive Regex",
args: [],
},
],
},
{
name: "From Case Insensitive Regex: simple test",
input: "[sS]0[mM][eE][tT][hH]![nN][Gg] [wr][On][g]?",
expectedOutput: "s0meth!nG [wr][On][g]?",
recipeConfig: [
{
op: "From Case Insensitive Regex",
args: [],
},
],
},
]);

View File

@ -1,55 +0,0 @@
/**
* To Geohash tests
*
* @author gchq77703
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
name: "To Geohash",
input: "37.8324,112.5584",
expectedOutput: "ww8p1r4t8",
recipeConfig: [
{
op: "To Geohash",
args: [9],
},
],
},
{
name: "To Geohash",
input: "37.9324,-112.2584",
expectedOutput: "9w8pv3ruj",
recipeConfig: [
{
op: "To Geohash",
args: [9],
},
],
},
{
name: "To Geohash",
input: "37.8324,112.5584",
expectedOutput: "ww8",
recipeConfig: [
{
op: "To Geohash",
args: [3],
},
],
},
{
name: "To Geohash",
input: "37.9324,-112.2584",
expectedOutput: "9w8pv3rujxy5b99",
recipeConfig: [
{
op: "To Geohash",
args: [15],
},
],
},
]);

View File

@ -0,0 +1,24 @@
/**
* YARA Rules tests.
*
* @author Matt C [matt@artemisbot.uk]
*
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
name: "YARA Match: simple foobar",
input: "foobar foobar bar foo foobar",
expectedOutput: "Rule \"foo\" matches (4 times):\nPos 0, length 3, identifier $re1, data: \"foo\"\nPos 7, length 3, identifier $re1, data: \"foo\"\nPos 18, length 3, identifier $re1, data: \"foo\"\nPos 22, length 3, identifier $re1, data: \"foo\"\nRule \"bar\" matches (4 times):\nPos 3, length 3, identifier $re1, data: \"bar\"\nPos 10, length 3, identifier $re1, data: \"bar\"\nPos 14, length 3, identifier $re1, data: \"bar\"\nPos 25, length 3, identifier $re1, data: \"bar\"\n",
recipeConfig: [
{
"op": "YARA Rules",
"args": ["rule foo {strings: $re1 = /foo/ condition: $re1} rule bar {strings: $re1 = /bar/ condition: $re1}", true, true, true, true],
}
],
},
]);

View File

@ -1,5 +1,5 @@
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require("path");
/**
@ -31,8 +31,6 @@ const banner = `/**
* limitations under the License.
*/`;
const vendorCSS = new ExtractTextPlugin("vendor.css");
const projectCSS = new ExtractTextPlugin("styles.css");
module.exports = {
plugins: [
@ -49,8 +47,9 @@ module.exports = {
new webpack.DefinePlugin({
"process.browser": "true"
}),
vendorCSS,
projectCSS
new MiniCssExtractPlugin({
filename: "[name].css"
}),
],
resolve: {
alias: {
@ -80,21 +79,19 @@ module.exports = {
},
{
test: /\.css$/,
use: projectCSS.extract({
use: [
{ loader: "css-loader" },
{ loader: "postcss-loader" },
]
})
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
]
},
{
test: /\.scss$/,
use: vendorCSS.extract({
use: [
{ loader: "css-loader" },
{ loader: "sass-loader" }
]
})
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader",
]
},
{
test: /\.(ico|eot|ttf|woff|woff2)$/,