[
  {
    "path": ".cspell.json",
    "content": "{\n    \"version\": \"0.2\",\n    \"language\": \"en,en-gb\",\n    \"words\": [],\n    \"dictionaries\": [\n      \"npm\",\n      \"softwareTerms\",\n      \"node\",\n      \"html\",\n      \"css\",\n      \"bash\",\n      \"en-gb\",\n      \"misc\"\n    ],\n    \"ignorePaths\": [\"package.json\", \"package-lock.json\", \"node_modules\"]\n  }\n  "
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node\n{\n\t\"name\": \"CyberChef\",\n\t// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile\n\t\"image\": \"mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm\",\n\n\t// Features to add to the dev container. More info: https://containers.dev/features.\n\t\"features\": {\n\t\t\"ghcr.io/devcontainers/features/github-cli\": \"latest\"\n\t},\n\n\t// Use 'forwardPorts' to make a list of ports inside the container available locally.\n\t\"forwardPorts\": [8080],\n\n\t// Use 'postCreateCommand' to run commands after the container is created.\n\t\"postCreateCommand\": {\n\t\t\"npm\": \"bash -c \\\"sudo chown node node_modules && npm install\\\"\"\n\t},\n\n\t\"containerEnv\": {\n\t\t\"DISPLAY\": \":99\"\n\t},\n\n\t\"mounts\": [\n\t\t\"source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume\"\n\t],\n\n\t// Configure tool-specific properties.\n\t\"customizations\": {\n\t\t\"vscode\": {\n\t\t\t\"extensions\": [\n\t\t\t\t\"dbaeumer.vscode-eslint\",\n\t\t\t\t\"GitHub.vscode-github-actions\"\n\t\t\t]\n\t\t}\n\t}\n\n\t// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.\n\t// \"remoteUser\": \"root\"\n}\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\nbuild\n"
  },
  {
    "path": ".editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 4\n\n[{package.json,.travis.yml,nightwatch.json}]\nindent_style = space\nindent_size = 2\n\n[.github/**.yml]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nTake a look through the [Wiki pages](https://github.com/gchq/CyberChef/wiki) for guides on [compiling CyberChef](https://github.com/gchq/CyberChef/wiki/Getting-started) and [adding new operations](https://github.com/gchq/CyberChef/wiki/Adding-a-new-operation).\n\nThere are lots of opportunities to contribute to CyberChef. If you want ideas, take a look at any [Issues](https://github.com/gchq/CyberChef/issues) tagged with '[help wanted](https://github.com/gchq/CyberChef/labels/help%20wanted)'.\n\nBefore your contributions can be accepted, you must:\n\n - Sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef)\n - Push your changes to your fork.\n - Submit a pull request.\n\n\n## Coding conventions\n\n* Indentation: Each block should consist of 4 spaces\n* Object/namespace identifiers: CamelCase\n* Function/variable names: camelCase\n* Constants: UNDERSCORE_UPPER_CASE\n* Source code encoding: UTF-8 (without BOM)\n* All source files must end with a newline\n* Line endings: UNIX style (\\n)\n\n\n## Design Principles\n\n1. If at all possible, all operations and features should be client-side and not rely on connections to an external server. This increases the utility of CyberChef on closed networks and in virtual machines that are not connected to the Internet. Calls to external APIs may be accepted if there is no other option, but not for critical components.\n2. Latency should be kept to a minimum to enhance the user experience. This means that operation code should sit on the client and be executed there. However, as a trade-off between latency and bandwidth, operation code with large dependencies can be loaded in discrete modules in order to reduce the size of the initial download. The downloading of additional modules must remain entirely transparent so that the user is not inconvenienced.\n3. Large libraries should be kept in separate modules so that they are not downloaded by everyone who uses the app, just those who specifically require the relevant operations.\n4. Use Vanilla JS if at all possible to reduce the number of libraries required and relied upon. Frameworks like jQuery, although included, should not be used unless absolutely necessary.\n\n\nWith these principles in mind, any changes or additions to CyberChef should keep it:\n\n - Standalone\n - Efficient\n - As small as possible\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: 'Bug report: <Insert title here>'\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behaviour or a link to the recipe / input used to cause the bug:\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behaviour**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (if relevant, please complete the following information):**\n - OS: [e.g. Windows] \n - Browser: [e.g. chrome 72, firefox 60]\n - CyberChef version: [e.g. 9.7.14]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for the project\ntitle: 'Feature request: <Insert title here>'\nlabels: feature\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. E.g. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/operation-request.md",
    "content": "---\nname: Operation request\nabout: Suggest a new operation\ntitle: 'Operation request: <Insert title here>'\nlabels: operation\nassignees: ''\n\n---\n\n## Summary\n\n### Example Input\n\n### Example Output\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!-- Prefix the title above with 'Misc:' -->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# See the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  #\n  # Check for minor/patch versions only on a weekly basis - we are likely to be able to\n  # merge these routinely. Major versions we'll check for and update manually.\n  #\n  - package-ecosystem: 'npm'\n    directory: '/'\n    versioning-strategy: increase\n    schedule:\n      interval: 'weekly'\n      day: 'friday'\n      time: '03:00'\n      timezone: Europe/London\n    commit-message:\n      prefix: 'chore (deps): '\n    ignore:\n      # we'll do any major version updates manually\n      - dependency-name: '*'\n        update-types: ['version-update:semver-major']\n      # packages we can't currently update\n      # see issue #2214 for rationale for each of these\n      - dependency-name: '@xmldom/xmldom'\n        versions: [ '>=0.9.0' ]\n      - dependency-name: 'bcryptjs'\n        versions: [ '>=3.0.0' ]\n      - dependency-name: 'bootstrap'\n        versions: [ '>=5.0.0' ]\n      - dependency-name: 'bson'\n        versions: [ '>=5.0.0' ]\n      - dependency-name: 'cbor'\n        versions: [ '>=10.0.0' ]\n      - dependency-name: 'cspell'\n        versions: [ '>=9.0.0' ]\n      - dependency-name: 'eslint'\n        versions: [ '>=10.0.0' ]\n      - dependency-name: 'eslint-plugin-jsdoc'\n        versions: [ '>=51.0.0' ]\n      - dependency-name: 'fernet'\n        versions: [ '>=0.4.0' ]\n      - dependency-name: 'geodesy'\n        versions: [ '>=2.0.0' ]\n      - dependency-name: 'otpauth'\n        versions: [ '>=9.4.0' ]\n      - dependency-name: 'webpack-dev-server'\n        versions: [ '>=5.1.0' ]\n    groups:\n      #\n      # Grouping so we don't get a seperate PR for every patch version.\n      #\n      patch-updates:\n        applies-to: version-updates\n        patterns:\n          - '*'\n        update-types:\n          - 'patch'\n\n  # Can't enable this until we are using Node 24 as the latest actions all require this version\n  # - package-ecosystem: \"github-actions\"\n  #   # Workflow files stored in the default location of `.github/workflows`; no need to\n  #   # specify `/.github/workflows` for `directory`\n  #   directory: '/'\n  #   schedule:\n  #     interval: 'weekly'\n  #     day: 'friday'\n  #     time: '03:00'\n  #     timezone: Europe/London\n  #   commit-message:\n  #     prefix: 'chore (deps): '\n"
  },
  {
    "path": ".github/workflows/master.yml",
    "content": "name: \"Master Build, Test & Deploy\"\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - master\n\npermissions:\n  contents: read\n\njobs:\n  main:\n    permissions:\n      contents: write\n      pages: write\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set node version\n        uses: actions/setup-node@v6\n        with:\n          node-version: 18\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Install\n        run: |\n          export DETECT_CHROMEDRIVER_VERSION=true\n          npm install\n          npm run setheapsize\n\n      - name: Lint\n        run: npx grunt lint\n\n      - name: Unit Tests\n        run: |\n          npm test\n          npm run testnodeconsumer\n\n      - name: Production Build\n        if: success()\n        run: npx grunt prod --msg=\"\"\n\n      - name: Generate sitemap\n        run: npx grunt exec:sitemap\n\n      - name: UI Tests\n        if: success()\n        run: |\n          sudo apt-get install xvfb\n          xvfb-run --server-args=\"-screen 0 1200x800x24\" npx grunt testui\n\n      - name: Prepare for GitHub Pages\n        if: success()\n        run: npx grunt copy:ghPages\n\n      - name: Deploy to GitHub Pages\n        if: success() && github.ref == 'refs/heads/master'\n        uses: crazy-max/ghaction-github-pages@v3\n        with:\n          target_branch: gh-pages\n          build_dir: ./build/prod\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/pull_requests.yml",
    "content": "name: \"Pull Requests\"\n\npermissions:\n  contents: read\n\non:\n  workflow_dispatch:\n  pull_request:\n    types: [synchronize, opened, reopened]\n\njobs:\n  main:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set node version\n        uses: actions/setup-node@v6\n        with:\n          node-version: 18\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Install\n        run: |\n          export DETECT_CHROMEDRIVER_VERSION=true\n          npm install\n          npm run setheapsize\n\n      - name: Lint\n        run: npx grunt lint\n\n      - name: Unit Tests\n        run: |\n          npm test\n          npm run testnodeconsumer\n\n      - name: Production Build\n        if: success()\n        run: npx grunt prod\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n\n      - name: Production Image Build\n        if: success()\n        id: build-image\n        uses: docker/build-push-action@v6\n        with:\n          platforms: linux/amd64,linux/arm64\n      - name: UI Tests\n        if: success()\n        run: |\n          sudo apt-get install xvfb\n          xvfb-run --server-args=\"-screen 0 1200x800x24\" npx grunt testui\n"
  },
  {
    "path": ".github/workflows/releases.yml",
    "content": "name: \"Releases\"\n\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - \"v*\"\n\npermissions:\n  contents: read\n\nenv:\n  REGISTRY: ghcr.io\n  REGISTRY_USER: ${{ github.actor }}\n  REGISTRY_PASSWORD: ${{ github.token }}\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  main:\n    permissions:\n      packages: write\n      contents: write\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set node version\n        uses: actions/setup-node@v6\n        with:\n          node-version: 18\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Install\n        run: |\n          export DETECT_CHROMEDRIVER_VERSION=true\n          npm ci\n          npm run setheapsize\n\n      - name: Lint\n        run: npx grunt lint\n\n      - name: Unit Tests\n        run: |\n          npm test\n          npm run testnodeconsumer\n\n      - name: Production Build\n        run: npx grunt prod\n\n      - name: UI Tests\n        run: |\n          sudo apt-get install xvfb\n          xvfb-run --server-args=\"-screen 0 1200x800x24\" npx grunt testui\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n\n      - name: Image Metadata\n        id: image-metadata\n        uses: docker/metadata-action@v4\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          tags: |\n            type=semver,pattern={{major}}\n            type=semver,pattern={{major}}.{{minor}}\n            type=semver,pattern={{version}}\n\n      - name: Log in to GHCR\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ env.REGISTRY_USER }}\n          password: ${{ env.REGISTRY_PASSWORD }}\n\n      - name: Publish to GHCR\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.image-metadata.outputs.tags }}\n          labels: ${{ steps.image-metadata.outputs.labels }}\n          platforms: linux/amd64,linux/arm64\n\n      - name: Upload Release Assets\n        id: upload-release-assets\n        uses: svenstaro/upload-release-action@v2\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: build/prod/*.zip\n          tag: ${{ github.ref }}\n          overwrite: true\n          file_glob: true\n          body: \"See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details.\"\n\n  npm-publish:\n    permissions:\n      id-token: write\n      contents: read\n    needs: main\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set node version\n        uses: actions/setup-node@v6\n        with:\n          node-version: 18\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Install\n        run: npm ci\n\n      - name: Create machine generated files\n        run: npm run node\n\n      - name: Reset node version ready for publish\n        uses: actions/setup-node@v6\n        with:\n          node-version: ^24.5\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Publish to NPM\n        run: npm publish\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nnpm-debug.log\ntravis.log\nbuild\n.vscode\n.idea\n.*.swp\nsrc/core/config/modules/*\nsrc/core/config/OperationConfig.json\nsrc/core/operations/index.mjs\nsrc/node/config/OperationConfig.json\nsrc/node/index.mjs\n**/*.DS_Store\ntests/browser/output/*\n.node-version\n"
  },
  {
    "path": ".npmignore",
    "content": "node_modules\nnpm-debug.log\ntravis.log\nbuild/*\n!build/node\n.vscode\n.github\n"
  },
  {
    "path": ".nvmrc",
    "content": "18\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## Versioning\n\nCyberChef uses the [semver](https://semver.org/) system to manage versioning: `<MAJOR>.<MINOR>.<PATCH>`.\n\n- MAJOR version changes represent a significant change to the fundamental architecture of CyberChef and may (but don't always) make breaking changes that are not backwards compatible.\n- MINOR version changes usually mean the addition of new operations or reasonably significant new features.\n- PATCH versions are used for bug fixes and any other small tweaks that modify or improve existing capabilities.\n\nAll 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).\n\n\n## Details\n\n### [10.22.0] - 2026-02-11\n- Separate npm publish out into separate job and run with Node 24.5 [@GCHQDeveloper581] | [#2188]\n- Fixed Percent delimiter for hex encoding [@beneri] [@C85297] | [#2137]\n- Added the ability to paste one or more Images from the Clipboard [@t-martine] [@a3957273] [@C85297] | [#1876]\n- Quoted Printable - consistent reference to 'email' [@wesinator] | [#2186]\n- Fix freeze when output text decoding fails [@Raka-loah] | [#1573]\n- Update Browserslist DB [@C85297] | [#2183]\n- Add contents write permission to releases workflow [@C85297] | [#2182]\n- Fix release workflow permissions [@C85297] | [#2181]\n\n### [10.21.0] - 2026-02-05\n- Fix import operations with special chars in them [@d98762625] [@jg42526] | [#1040]\n- Remove custom CodeQL workflow [@C85297] | [#2176]\n- Fix code scanning warnings in workflows [@GCHQDeveloper581] | [#2177]\n- Use NPM trusted publishing [@C85297] [@GCHQDeveloper581] | [#2174]\n- Fix: Correctly parse xxd odd byte hexdumps [@ThomasNotTom] [@GCHQDeveloper581] | [#2058]\n- Update Sitemap URLs to Use Valid Paths in sitemap.mjs [@rbpi] [@C85297] | [#1861]\n- Use recommended GitHub Actions to build image [@AlexGustafsson] [@C85297] | [#2055]\n- Remove version 10 message from banner [@C85297] | [#2169]\n- Bump form-data from 4.0.1 to 4.0.5  | [#2175]\n- Bump node-forge from 1.3.1 to 1.3.3  | [#2173]\n- Update crypto browserify [@C85297] | [#2172]\n- Update kbpgp package (resolves #2135) [@GCHQDeveloper581] | [#2136]\n- Fix the processing of ALPNs for JA4 to align with new specification update [@tuliperis] | [#2165]\n- Add Bech32 and Bech32m encoding/decoding operations [@thomasxm] | [#2159]\n- Exclude Delete character from hex dump output [@mikecat] [@C85297] | [#2086]\n- Tiny typo fix in \"To Base85\" operation [@twostraws] | [#2118]\n- Bump jsonpath-plus [@C85297] | [#2166]\n\n### [10.20.0] - 2026-01-28\n- Fixed Optical Character Recognition and added tests [@n1474335] | [ab37c1e]\n- Fixed JA4 version fallback value [@n1474335] | [7a5225c]\n- Updated chromedriver [@n1474335] | [0e82e4b]\n- Fixed RSA Sign and Verify character encodings [@n1474335] | [895a929]\n- Updated chromedriver [@n1474335] | [d3adfc7]\n- Added message format arg to RSA Verify operation [@n1474335] | [47c85a1]\n- Add operation for parsing X.509 CRLs [@robinsandhu] | [#1887]\n- Fix typo in description of JWT Sign recipe [@GuilhermoReadonly] | [#1961]\n- Corrected path to generateNodeIndex.mjs [@simonarnell] | [#1959]\n- Add 'header' ingredient to JWT Sign operation [@RandomByte] | [#1957]\n- Add Parse TLS record operation [@c65722] | [#1936]\n- Automatically detect chrome driver version [@gchq] | [#1972]\n- Add Strip UDP header operation [@c65722] | [#1900]\n- Add Strip TCP header operation [@c65722] | [#1898]\n- Webpack compress with gzip and brotli [@max0x53] | [#1955]\n- add offset field to 'Add Line Numbers' operation [@Adamkadaban] | [#1866]\n- Disable flakey URL test [@a3957273] | [#1973]\n- Add Strip IPv4 header operation [@c65722] | [#1899]\n- IPv6 Transition Operation [@jb30795] | [#1780]\n- fix: Blowfish - ignore IV length in ECB mode [@FranciscoPombal] | [#1902]\n- Add 'Drop nth bytes' operation [@Oshawk] | [#1914]\n- Add 'Take nth bytes' operation  [@Oshawk] | [#1915]\n- Add Leet Speak [@bartblaze] | [#1971]\n- Fix Generate TOTP & HOPT [@exactlyaron] | [#1966]\n- Updated luhn checksum operation to work with different bases [@k3ach] | [#1933]\n- automatically theme mode based on user preference [@vs4vijay] | [#1921]\n- fix: DES/Triple DES - misleading error messages [@FranciscoPombal] | [#1904]\n- fix: ROT13 - shifting numbers by negative amounts [@FranciscoPombal] | [#1903]\n- Introduce Yubico's Modhex for Conversion [@linuxgemini] | [#1105]\n- Feature: MIME RFC2047 Decoding [@MShwed] | [#630]\n- CC-1889 add _ option [@depperm] | [#1977]\n- chore(root): add cspell [@evenstensberg] | [#1976]\n- Preserve uppercase for Leet Speak [@bartblaze] | [#1981]\n- Load the user's preferred color scheme if the URL contains an invalid theme [@0xh3xa] | [#2007]\n- Add SM2 Encrypt and Decrypt Operations [@flakjacket95] | [#1909]\n- Support jq as an operation. [@zhzy0077] | [#1604]\n- Add fingerprints to the 'Parse X.509 certificate' operation [@JSCU-CNI] | [#1863]\n- Added a JSON to YAML and a YAML to JSON operation [@ccarpo] | [#1286]\n- Add CRC Operation [@r4mos] | [#1993]\n- Bug Fix: selected theme not loading when refreshing [@0xh3xa] | [#2006]\n- Fix(RecipeWaiter): sanitize user input in addOperation to prevent XSS [@0xh3xa] | [#2014]\n- Docker multiplatform build support [@PathToLife] | [#1974]\n- Add Base32 Hex Extended Alphabet and Base32 Tests. [@peterc-s] | [#1991]\n- Add ECB/NoPadding and CBC/NoPadding support to AES encryption [@plvie] | [#2013]\n- Add new operation: PHP Serialize [@brun0ne] | [#1548]\n- Push input through postmessage [@kenduguay1] | [#1992]\n- Add jsonata query operation [@jonking-ajar] | [#1587]\n- Re-enable Npm Release in github workflows [@PathToLife] | [#2031]\n- Add to ECDSA Verify the message format [@r4mos] | [#2027]\n- Added alternating caps functionality [@sw5678] | [#1897]\n- XOR Checksum operation added [@jg42526] | [#2035]\n- Add GenerateAllChecksums operation * Remove checksums from GenerateAllHashes operation [@es45411] | [66d445c]\n- Update GenerateAllChecksums infoURL [@es45411] | [#2037]\n- Add toggle \"+\" character to URLDecode operation [@es45411] | [#2040]\n- Workaround for Safari load bug [@GCHQDeveloper94872] | [#2038]\n- Updated Dockerfile to correctly build on ARM64 platforms [@Sma-Das] | [#2042]\n- Addresses bug report #2008 Added explicit support for octal IP addresses. Changed approach to IPv4 regex to be string manipulation generated. Added some unit tests for IP address parsing - probably not full coverage. Added lookahead and lookbehind tricks to resolve warned issue that 1.2.3.256 would still be extracted as 1.2.3.25. Now only accepts valid IP addresses. Warning replaced with clause about infinite length dotted decimal forms. [@gchqdev364] | [#2041]\n- Remove trim from rail fence [@Odyhibit] | [#1986]\n- Fix email regex [@ericli-splunk] | [#2025]\n- Add Blake3 hashing [@xumptex] | [#2023]\n- Use defaultIndex instead of 0 in transformArgs [@bartvanandel] | [#2015]\n- Add \"Generate UUID\" and \"Analyse UUID\" operations [@bartvanandel] | [#2011]\n- Add new operation: Template [@kendallgoto] | [#2021]\n- Add more clear build instructions [@remingtr] | [#1873]\n- Show On Map updated to use leaflet over WikiMedia [@0xff1ce] | [#1884]\n- Fixed ToDecimal signed logic [@starplanet] | [#1545]\n- Use BigInt for encoding/decoding VarInt [@mikecat] | [#1978]\n\n### [10.19.0] - 2024-06-21\n- Add support for ECDSA and DSA in 'Parse CSR' [@robinsandhu] | [#1828]\n- Fix typos in SIGABA.mjs [@eltociear] | [#1834]\n\n### [10.18.0] - 2024-04-24\n- Added 'XXTEA Encrypt' and 'XXTEA Decrypt' operations [@n1474335] | [0a353ee]\n\n### [10.17.0] - 2024-04-13\n- Fix unit test 'expectOutput' implementation [@zb3] | [#1783]\n- Add accessibility labels for icons [@e218736] | [#1743]\n- Add focus styling for keyboard navigation [@e218736] | [#1739]\n- Add support for operation option hiding [@TheZ3ro] | [#541]\n- Improve efficiency of RAKE implementation [@sw5678] | [#1751]\n- Require (a, 26) to be coprime in 'Affine Encode' [@EvieHarv] | [#1788]\n- Added 'JWK to PEM' operation [@cplussharp] | [#1277]\n- Added 'PEM to JWK' operation [@cplussharp] | [#1277]\n- Added 'Public Key from Certificate' operation [@cplussharp] | [#1642]\n- Added 'Public Key from Private Key' operation [@cplussharp] | [#1642]\n\n### [10.16.0] - 2024-04-12\n- Added 'JA4Server Fingerprint' operation [@n1474335] | [#1789]\n\n### [10.15.0] - 2024-04-02\n- Fix Ciphersaber2 key concatenation [@zb3] | [#1765]\n- Fix DeriveEVPKey's array parsing [@zb3] | [#1767]\n- Fix JWT operations [@a3957273] | [#1769]\n- Added 'Parse Certificate Signing Request' operation [@jkataja] | [#1504]\n- Added 'Extract Hash Values' operation [@MShwed] | [#512]\n- Added 'DateTime Delta' operation [@tomgond] | [#1732]\n\n### [10.14.0] - 2024-03-31\n- Added 'To Float' and 'From Float' operations [@tcode2k16] | [#1762]\n- Fix ChaCha raw export option [@joostrijneveld] | [#1606]\n- Update x86 disassembler vendor library [@evanreichard] | [#1197]\n- Allow variable Blowfish key sizes [@cbeuw] | [#933]\n- Added 'XXTEA' operation [@devcydo] | [#1361]\n\n### [10.13.0] - 2024-03-30\n- Added 'FangURL' operation [@breakersall] [@arnydo] | [#1591] [#654]\n\n### [10.12.0] - 2024-03-29\n- Added 'Salsa20' and 'XSalsa20' operation [@joostrijneveld] | [#1750]\n\n### [10.11.0] - 2024-03-29\n- Add HEIC/HEIF file signatures [@simonw] | [#1757]\n- Update xmldom to fix medium security vulnerability [@chriswhite199] | [#1752]\n- Update JSONWebToken to fix medium security vulnerability [@chriswhite199] | [#1753]\n\n### [10.10.0] - 2024-03-27\n- Added 'JA4 Fingerprint' operation [@n1474335] | [#1759]\n\n### [10.9.0] - 2024-03-26\n- Line ending sequences and UTF-8 character encoding are now detected automatically [@n1474335] | [65ffd8d]\n\n### [10.8.0] - 2024-02-13\n- Add official Docker images [@AshCorr] | [#1699]\n\n### [10.7.0] - 2024-02-09\n- Added 'File Tree' operation [@sw5678] | [#1667]\n- Added 'RISON' operation [@sg5506844] | [#1555]\n- Added 'MurmurHash3' operation [@AliceGrey] | [#1694]\n\n### [10.6.0] -  2024-02-03\n- Updated 'Forensics Wiki' URLs to new domain [@a3957273] | [#1703]\n- Added 'LZNT1 Decompress' operation [@0xThiebaut] | [#1675]\n- Updated 'Regex Expression' UUID matcher [@cnotin] | [#1678]\n- Removed duplicate 'hover' message within baking info [@KevinSJ] | [#1541]\n\n### [10.5.0] - 2023-07-14\n- Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592]\n\n### [10.4.0] - 2023-03-24\n- Added 'Generate De Bruijn Sequence' operation [@gchq77703] | [#493]\n\n### [10.3.0] - 2023-03-24\n- Added 'Argon2' and 'Argon2 compare' operations [@Xenonym] | [#661]\n\n### [10.2.0] - 2023-03-23\n- Added 'Derive HKDF key' operation [@mikecat] | [#1528]\n\n### [10.1.0] - 2023-03-23\n- Added 'Levenshtein Distance' operation [@mikecat] | [#1498]\n- Added 'Swap case' operation [@mikecat] | [#1499]\n\n## [10.0.0] - 2023-03-22\n- [Full details explained here](https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features)\n- Status bars added to the Input and Output [@n1474335] | [#1405]\n- Character encoding selection added to the Input and Output [@n1474335] | [#1405]\n- End of line separator selection added to the Input and Output [@n1474335] | [#1405]\n- Non-printable characters are rendered as control character pictures [@n1474335] | [#1405]\n- Loaded files can now be edited in the Input [@n1474335] | [#1405]\n- Various editor features added such as multiple selections and bracket matching [@n1474335] | [#1405]\n- Contextual help added, activated by pressing F1 while hovering over features [@n1474335] | [#1405]\n- Many, many UI tests added for I/O features and operations [@n1474335] | [#1405]\n\n<details>\n    <summary>Click to expand v9 minor versions</summary>\n\n### [9.55.0] - 2022-12-09\n- Added 'AMF Encode' and 'AMF Decode' operations [@n1474335] | [760eff4]\n\n### [9.54.0] - 2022-11-25\n- Added 'Rabbit' operation [@mikecat] | [#1450]\n\n### [9.53.0] - 2022-11-25\n- Added 'AES Key Wrap' and 'AES Key Unwrap' operations [@mikecat] | [#1456]\n\n### [9.52.0] - 2022-11-25\n- Added 'ChaCha' operation [@joostrijneveld] | [#1466]\n\n### [9.51.0] - 2022-11-25\n- Added 'CMAC' operation [@mikecat] | [#1457]\n\n### [9.50.0] - 2022-11-25\n- Added 'Shuffle' operation [@mikecat] | [#1472]\n\n### [9.49.0] - 2022-11-11\n- Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83]\n\n### [9.48.0] - 2022-10-14\n- Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427]\n\n### [9.47.0] - 2022-10-14\n- Added 'LZMA Decompress' and 'LZMA Compress' operations [@mattnotmitt] | [#1421]\n\n### [9.46.0] - 2022-07-08\n- Added 'Cetacean Cipher Encode' and 'Cetacean Cipher Decode' operations [@valdelaseras] | [#1308]\n\n### [9.45.0] - 2022-07-08\n- Added 'ROT8000' operation [@thomasleplus] | [#1250]\n\n### [9.44.0] - 2022-07-08\n- Added 'LZString Compress' and 'LZString Decompress' operations [@crespyl] | [#1266]\n\n### [9.43.0] - 2022-07-08\n- Added 'ROT13 Brute Force' and 'ROT47 Brute Force' operations [@mikecat] | [#1264]\n\n### [9.42.0] - 2022-07-08\n- Added 'LS47 Encrypt' and 'LS47 Decrypt' operations [@n1073645] | [#951]\n\n### [9.41.0] - 2022-07-08\n- Added 'Caesar Box Cipher' operation [@n1073645] | [#1066]\n\n### [9.40.0] - 2022-07-08\n- Added 'P-list Viewer' operation [@n1073645] | [#906]\n\n### [9.39.0] - 2022-06-09\n- Added 'ELF Info' operation [@n1073645] | [#1364]\n\n### [9.38.0] - 2022-05-30\n- Added 'Parse TCP' operation [@n1474335] | [a895d1d]\n\n### [9.37.0] - 2022-03-29\n- 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189]\n- NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189]\n\n### [9.36.0] - 2022-03-29\n- 'SIGABA' operation added [@hettysymes] | [#934]\n\n### [9.35.0] - 2022-03-28\n- 'To Base45' and 'From Base45' operations added [@t-8ch] | [#1242]\n\n### [9.34.0] - 2022-03-28\n- 'Get All Casings' operation added [@n1073645] | [#1065]\n\n### [9.33.0] - 2022-03-25\n- Updated to support Node 17 [@n1474335] [@john19696] [@t-8ch] | [[#1326] [#1313] [#1244]\n- Improved CJS and ESM module support [@d98762625] | [#1037]\n\n### [9.32.0] - 2021-08-18\n- 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52]\n\n### [9.31.0] - 2021-08-10\n- 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc]\n\n### [9.30.0] - 2021-08-10\n- 'JA3S Fingerprint' operation added [@n1474335] | [289a417]\n\n### [9.29.0] - 2021-07-28\n- 'JA3 Fingerprint' operation added [@n1474335] | [9a33498]\n\n### [9.28.0] - 2021-03-26\n- 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999]\n\n### [9.27.0] - 2021-02-12\n- 'Fuzzy Match' operation added [@n1474335] | [8ad18b]\n\n### [9.26.0] - 2021-02-11\n- 'Get Time' operation added [@n1073645] [@n1474335] | [#1045]\n\n### [9.25.0] - 2021-02-11\n- 'Extract ID3' operation added [@n1073645] [@n1474335] | [#1006]\n\n### [9.24.0] - 2021-02-02\n- 'SM3' hashing function added along with more configuration options for other hashing operations [@n1073645] [@n1474335] | [#1022]\n\n### [9.23.0] - 2021-02-01\n- Various RSA operations added to encrypt, decrypt, sign, verify and generate keys [@mattnotmitt] [@GCHQ77703] | [#652]\n\n### [9.22.0] - 2021-02-01\n- 'Unicode Text Format' operation added [@mattnotmitt] | [#1083]\n\n### [9.21.0] - 2020-06-12\n- Node API now exports `magic` operation [@d98762625] | [#1049]\n\n### [9.20.0] - 2020-03-27\n- 'Parse ObjectID Timestamp' operation added [@dmfj] | [#987]\n\n### [9.19.0] - 2020-03-24\n- Improvements to the 'Magic' operation, allowing it to recognise more data formats and provide more accurate results [@n1073645] [@n1474335] | [#966] [b765534b](https://github.com/gchq/CyberChef/commit/b765534b8b2a0454a5132a0a52d1d8844bcbdaaa)\n\n### [9.18.0] - 2020-03-13\n- 'Convert to NATO alphabet' operation added [@MarvinJWendt] | [#674]\n\n### [9.17.0] - 2020-03-13\n- 'Generate Image' operation added [@pointhi] | [#683]\n\n### [9.16.0] - 2020-03-06\n- 'Colossus' operation added [@VirtualColossus] | [#917]\n\n### [9.15.0] - 2020-03-05\n- 'CipherSaber2 Encrypt' and 'CipherSaber2 Decrypt' operations added [@n1073645] | [#952]\n\n### [9.14.0] - 2020-03-05\n- 'Luhn Checksum' operation added [@n1073645] | [#965]\n\n### [9.13.0] - 2020-02-13\n- 'Rail Fence Cipher Encode' and 'Rail Fence Cipher Decode' operations added [@Flavsditz] | [#948]\n\n### [9.12.0] - 2019-12-20\n- 'Normalise Unicode' operation added [@matthieuxyz] | [#912]\n\n### [9.11.0] - 2019-11-06\n- Implemented CFB, OFB, and CTR modes for Blowfish operations [@cbeuw] | [#653]\n\n### [9.10.0] - 2019-11-06\n- 'Lorenz' operation added [@VirtualColossus] | [#528]\n\n### [9.9.0] - 2019-11-01\n- Added support for 109 more character encodings [@n1474335]\n\n### [9.8.0] - 2019-10-31\n- 'Avro to JSON' operation added [@jarrodconnolly] | [#865]\n\n### [9.7.0] - 2019-09-13\n- 'Optical Character Recognition' operation added [@MShwed] [@n1474335] | [#632]\n\n### [9.6.0] - 2019-09-04\n- 'Bacon Cipher Encode' and 'Bacon Cipher Decode' operations added [@kassi] | [#500]\n\n### [9.5.0] - 2019-09-04\n- Various Steganography operations added: 'Extract LSB', 'Extract RGBA', 'Randomize Colour Palette', and 'View Bit Plane' [@Ge0rg3] | [#625]\n\n### [9.4.0] - 2019-08-30\n- 'Render Markdown' operation added [@j433866] | [#627]\n\n### [9.3.0] - 2019-08-30\n- 'Show on map' operation added [@j433866] | [#477]\n\n### [9.2.0] - 2019-08-23\n- 'Parse UDP' operation added [@h345983745] | [#614]\n\n### [9.1.0] - 2019-08-22\n- 'Parse SSH Host Key' operation added [@j433866] | [#595]\n- 'Defang IP Addresses' operation added [@h345983745] | [#556]\n\n</details>\n\n## [9.0.0] - 2019-07-09\n- [Multiple inputs](https://github.com/gchq/CyberChef/wiki/Multiple-Inputs) are now supported in the main web UI, allowing you to upload and process multiple files at once [@j433866] | [#566]\n- A [Node.js API](https://github.com/gchq/CyberChef/wiki/Node-API) has been implemented, meaning that CyberChef can now be used as a library, either to provide specific operations, or an entire baking environment [@d98762625] | [#291]\n- A [read-eval-print loop (REPL)](https://github.com/gchq/CyberChef/wiki/Node-API#repl) is also included to enable prototyping and experimentation with the API [@d98762625] | [#291]\n- Light and dark Solarized themes added [@j433866] | [#566]\n\n<details>\n    <summary>Click to expand v8 minor versions</summary>\n\n### [8.38.0] - 2019-07-03\n- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]\n\n### [8.37.0] - 2019-07-03\n- 'CRC-8 Checksum' operation added [@MShwed] | [#591]\n\n### [8.36.0] - 2019-07-03\n- 'PGP Verify' operation added [@artemisbot] | [#585]\n\n### [8.35.0] - 2019-07-03\n- 'Sharpen Image', 'Convert Image Format' and 'Add Text To Image' operations added [@j433866] | [#515]\n\n### [8.34.0] - 2019-06-28\n- Various new visualisations added to the 'Entropy' operation [@MShwed] | [#535]\n- Efficiency improvements made to the 'Entropy' operation for large file support [@n1474335]\n\n### [8.33.0] - 2019-06-27\n- 'Bzip2 Compress' operation added and 'Bzip2 Decompress' operation greatly improved [@artemisbot] | [#531]\n\n### [8.32.0] - 2019-06-27\n- 'Index of Coincidence' operation added [@Ge0rg3] | [#571]\n\n### [8.31.0] - 2019-04-12\n- The downloadable version of CyberChef is now a .zip file containing separate modules rather than a single .htm file. It is still completely standalone and will not make any external network requests. This change reduces the complexity of the build process significantly. [@n1474335]\n\n### [8.30.0] - 2019-04-12\n- 'Decode Protobuf' operation added [@n1474335] | [#533]\n\n### [8.29.0] - 2019-03-31\n- 'BLAKE2s' and 'BLAKE2b' hashing operations added [@h345983745] | [#525]\n\n### [8.28.0] - 2019-03-31\n- 'Heatmap Chart', 'Hex Density Chart', 'Scatter Chart' and 'Series Chart' operation added [@artemisbot] [@tlwr] | [#496] [#143]\n\n### [8.27.0] - 2019-03-14\n- 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516]\n- See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations.\n- New Bombe-style loading animation added for long-running operations [@n1474335]\n- New operation argument types added: `populateMultiOption` and `argSelector` [@n1474335]\n\n### [8.26.0] - 2019-03-09\n- Various image manipulation operations added [@j433866] | [#506]\n\n### [8.25.0] - 2019-03-09\n- 'Extract Files' operation added and more file formats supported [@n1474335] | [#440]\n\n### [8.24.0] - 2019-02-08\n- 'DNS over HTTPS' operation added [@h345983745] | [#489]\n\n### [8.23.1] - 2019-01-18\n- 'Convert co-ordinate format' operation added [@j433866] | [#476]\n\n### [8.23.0] - 2019-01-18\n- 'YARA Rules' operation added [@artemisbot] | [#468]\n\n### [8.22.0] - 2019-01-10\n- 'Subsection' operation added [@j433866] | [#467]\n\n### [8.21.0] - 2019-01-10\n- 'To Case Insensitive Regex' and 'From Case Insensitive Regex' operations added [@masq] | [#461]\n\n### [8.20.0] - 2019-01-09\n- 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455]\n\n### [8.19.0] - 2018-12-30\n- 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]\n\n### [8.18.0] - 2018-12-26\n- 'Split Colour Channels' operation added [@artemisbot] | [#449]\n\n### [8.17.0] - 2018-12-25\n- 'Generate QR Code' and 'Parse QR Code' operations added [@j433866] | [#448]\n\n### [8.16.0] - 2018-12-19\n- 'Play Media' operation added [@anthony-arnold] | [#446]\n\n### [8.15.0] - 2018-12-18\n- 'Text Encoding Brute Force' operation added [@Cynser] | [#439]\n\n### [8.14.0] - 2018-12-18\n- 'To Base62' and 'From Base62' operations added [@tcode2k16] | [#443]\n\n### [8.13.0] - 2018-12-15\n- 'A1Z26 Cipher Encode' and 'A1Z26 Cipher Decode' operations added [@jarmovanlenthe] | [#441]\n\n### [8.12.0] - 2018-11-21\n- 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428]\n\n### [8.11.0] - 2018-11-13\n- 'CSV to JSON' and 'JSON to CSV' operations added [@n1474335] | [#277]\n\n### [8.10.0] - 2018-11-07\n- 'Remove Diacritics' operation added [@klaxon1] | [#387]\n\n### [8.9.0] - 2018-11-07\n- 'Defang URL' operation added [@arnydo] | [#394]\n\n### [8.8.0] - 2018-10-10\n- 'Parse TLV' operation added [@GCHQ77703] | [#351]\n\n### [8.7.0] - 2018-08-31\n- 'JWT Sign', 'JWT Verify' and 'JWT Decode' operations added [@GCHQ77703] | [#348]\n\n### [8.6.0] - 2018-08-29\n- 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344]\n\n### [8.5.0] - 2018-08-23\n- 'To Braille' and 'From Braille' operations added [@n1474335] | [#255]\n\n### [8.4.0] - 2018-08-23\n- 'To Base85' and 'From Base85' operations added [@PenguinGeorge] | [#340]\n\n### [8.3.0] - 2018-08-21\n- 'To MessagePack' and 'From MessagePack' operations added [@artemisbot] | [#338]\n\n### [8.2.0] - 2018-08-21\n- Information links added to most operations, accessible in the description popover [@PenguinGeorge] | [#298]\n\n### [8.1.0] - 2018-08-19\n- 'Dechunk HTTP response' operation added [@sevzero] | [#311]\n\n</details>\n\n## [8.0.0] - 2018-08-05\n- Codebase rewritten using [ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) and [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) [@n1474335] [@d98762625] [@artemisbot] [@picapi] | [#284]\n- Operation architecture restructured to make adding new operations a lot simpler [@n1474335] | [#284]\n- A script has been added to aid in the creation of new operations by running `npm run newop` [@n1474335] | [#284]\n- 'Magic' operation added - [automated detection of encoded data](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) [@n1474335] | [#239]\n- UI updated to use [Bootstrap Material Design](https://fezvrasta.github.io/bootstrap-material-design/) [@n1474335] | [#248]\n- `JSON`, `File` and `List<File>` Dish types added [@n1474335] | [#284]\n- `OperationError` type added for better handling of errors thrown by operations [@d98762625] | [#296]\n- A `present()` method has been added, allowing operations to pass machine-friendly data to subsequent operations whilst presenting human-friendly data to the user [@n1474335] | [#284]\n- Set operations added [@d98762625] | [#281]\n- 'To Table' operation added [@JustAnotherMark] | [#294]\n- 'Haversine distance' operation added [@Dachande663] | [#325]\n- Started keeping a changelog [@n1474335]\n\n## [7.0.0] - 2017-12-28\n- Added support for loading, processing and downloading files up to 500MB [@n1474335] | [#224]\n\n## [6.0.0] - 2017-09-19\n- Threading support added. All recipe processing moved into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and to allow long-running operations to be cancelled [@n1474335] | [#173]\n- Module system created so that operations relying on large libraries can be downloaded separately as required, reducing the initial loading time for the app [@n1474335] | [#173]\n\n## [5.0.0] - 2017-03-30\n-  Webpack build process configured with Babel transpilation and ES6 imports and exports [@n1474335] | [#95]\n\n## [4.0.0] - 2016-11-28\n-  Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)\n\n[10.22.0]: https://github.com/gchq/CyberChef/releases/tag/v10.22.0\n[10.21.0]: https://github.com/gchq/CyberChef/releases/tag/v10.21.0\n[10.20.0]: https://github.com/gchq/CyberChef/releases/tag/v10.20.0\n[10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0\n[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0\n[10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0\n[10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0\n[10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0\n[10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0\n[10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0\n[10.12.0]: https://github.com/gchq/CyberChef/releases/tag/v10.12.0\n[10.11.0]: https://github.com/gchq/CyberChef/releases/tag/v10.11.0\n[10.10.0]: https://github.com/gchq/CyberChef/releases/tag/v10.10.0\n[10.9.0]: https://github.com/gchq/CyberChef/releases/tag/v10.9.0\n[10.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0\n[10.7.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0\n[10.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0\n[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0\n[10.4.0]: https://github.com/gchq/CyberChef/releases/tag/v10.4.0\n[10.3.0]: https://github.com/gchq/CyberChef/releases/tag/v10.3.0\n[10.2.0]: https://github.com/gchq/CyberChef/releases/tag/v10.2.0\n[10.1.0]: https://github.com/gchq/CyberChef/releases/tag/v10.1.0\n[10.0.0]: https://github.com/gchq/CyberChef/releases/tag/v10.0.0\n[9.55.0]: https://github.com/gchq/CyberChef/releases/tag/v9.55.0\n[9.54.0]: https://github.com/gchq/CyberChef/releases/tag/v9.54.0\n[9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0\n[9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0\n[9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0\n[9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0\n[9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0\n[9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0\n[9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0\n[9.46.0]: https://github.com/gchq/CyberChef/releases/tag/v9.46.0\n[9.45.0]: https://github.com/gchq/CyberChef/releases/tag/v9.45.0\n[9.44.0]: https://github.com/gchq/CyberChef/releases/tag/v9.44.0\n[9.43.0]: https://github.com/gchq/CyberChef/releases/tag/v9.43.0\n[9.42.0]: https://github.com/gchq/CyberChef/releases/tag/v9.42.0\n[9.41.0]: https://github.com/gchq/CyberChef/releases/tag/v9.41.0\n[9.40.0]: https://github.com/gchq/CyberChef/releases/tag/v9.40.0\n[9.39.0]: https://github.com/gchq/CyberChef/releases/tag/v9.39.0\n[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0\n[9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0\n[9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0\n[9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0\n[9.34.0]: https://github.com/gchq/CyberChef/releases/tag/v9.34.0\n[9.33.0]: https://github.com/gchq/CyberChef/releases/tag/v9.33.0\n[9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0\n[9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0\n[9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0\n[9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0\n[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0\n[9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0\n[9.26.0]: https://github.com/gchq/CyberChef/releases/tag/v9.26.0\n[9.25.0]: https://github.com/gchq/CyberChef/releases/tag/v9.25.0\n[9.24.0]: https://github.com/gchq/CyberChef/releases/tag/v9.24.0\n[9.23.0]: https://github.com/gchq/CyberChef/releases/tag/v9.23.0\n[9.22.0]: https://github.com/gchq/CyberChef/releases/tag/v9.22.0\n[9.21.0]: https://github.com/gchq/CyberChef/releases/tag/v9.21.0\n[9.20.0]: https://github.com/gchq/CyberChef/releases/tag/v9.20.0\n[9.19.0]: https://github.com/gchq/CyberChef/releases/tag/v9.19.0\n[9.18.0]: https://github.com/gchq/CyberChef/releases/tag/v9.18.0\n[9.17.0]: https://github.com/gchq/CyberChef/releases/tag/v9.17.0\n[9.16.0]: https://github.com/gchq/CyberChef/releases/tag/v9.16.0\n[9.15.0]: https://github.com/gchq/CyberChef/releases/tag/v9.15.0\n[9.14.0]: https://github.com/gchq/CyberChef/releases/tag/v9.14.0\n[9.13.0]: https://github.com/gchq/CyberChef/releases/tag/v9.13.0\n[9.12.0]: https://github.com/gchq/CyberChef/releases/tag/v9.12.0\n[9.11.0]: https://github.com/gchq/CyberChef/releases/tag/v9.11.0\n[9.10.0]: https://github.com/gchq/CyberChef/releases/tag/v9.10.0\n[9.9.0]: https://github.com/gchq/CyberChef/releases/tag/v9.9.0\n[9.8.0]: https://github.com/gchq/CyberChef/releases/tag/v9.8.0\n[9.7.0]: https://github.com/gchq/CyberChef/releases/tag/v9.7.0\n[9.6.0]: https://github.com/gchq/CyberChef/releases/tag/v9.6.0\n[9.5.0]: https://github.com/gchq/CyberChef/releases/tag/v9.5.0\n[9.4.0]: https://github.com/gchq/CyberChef/releases/tag/v9.4.0\n[9.3.0]: https://github.com/gchq/CyberChef/releases/tag/v9.3.0\n[9.2.0]: https://github.com/gchq/CyberChef/releases/tag/v9.2.0\n[9.1.0]: https://github.com/gchq/CyberChef/releases/tag/v9.1.0\n[9.0.0]: https://github.com/gchq/CyberChef/releases/tag/v9.0.0\n[8.38.0]: https://github.com/gchq/CyberChef/releases/tag/v8.38.0\n[8.37.0]: https://github.com/gchq/CyberChef/releases/tag/v8.37.0\n[8.36.0]: https://github.com/gchq/CyberChef/releases/tag/v8.36.0\n[8.35.0]: https://github.com/gchq/CyberChef/releases/tag/v8.35.0\n[8.34.0]: https://github.com/gchq/CyberChef/releases/tag/v8.34.0\n[8.33.0]: https://github.com/gchq/CyberChef/releases/tag/v8.33.0\n[8.32.0]: https://github.com/gchq/CyberChef/releases/tag/v8.32.0\n[8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0\n[8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0\n[8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0\n[8.28.0]: https://github.com/gchq/CyberChef/releases/tag/v8.28.0\n[8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0\n[8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0\n[8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0\n[8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0\n[8.23.1]: https://github.com/gchq/CyberChef/releases/tag/v8.23.1\n[8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0\n[8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0\n[8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0\n[8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0\n[8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0\n[8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0\n[8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0\n[8.16.0]: https://github.com/gchq/CyberChef/releases/tag/v8.16.0\n[8.15.0]: https://github.com/gchq/CyberChef/releases/tag/v8.15.0\n[8.14.0]: https://github.com/gchq/CyberChef/releases/tag/v8.14.0\n[8.13.0]: https://github.com/gchq/CyberChef/releases/tag/v8.13.0\n[8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0\n[8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0\n[8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0\n[8.9.0]: https://github.com/gchq/CyberChef/releases/tag/v8.9.0\n[8.8.0]: https://github.com/gchq/CyberChef/releases/tag/v8.8.0\n[8.7.0]: https://github.com/gchq/CyberChef/releases/tag/v8.7.0\n[8.6.0]: https://github.com/gchq/CyberChef/releases/tag/v8.6.0\n[8.5.0]: https://github.com/gchq/CyberChef/releases/tag/v8.5.0\n[8.4.0]: https://github.com/gchq/CyberChef/releases/tag/v8.4.0\n[8.3.0]: https://github.com/gchq/CyberChef/releases/tag/v8.3.0\n[8.2.0]: https://github.com/gchq/CyberChef/releases/tag/v8.2.0\n[8.1.0]: https://github.com/gchq/CyberChef/releases/tag/v8.1.0\n[8.0.0]: https://github.com/gchq/CyberChef/releases/tag/v8.0.0\n[7.0.0]: https://github.com/gchq/CyberChef/releases/tag/v7.0.0\n[6.0.0]: https://github.com/gchq/CyberChef/releases/tag/v6.0.0\n[5.0.0]: https://github.com/gchq/CyberChef/releases/tag/v5.0.0\n[4.0.0]: https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306\n\n[@n1474335]: https://github.com/n1474335\n[@d98762625]: https://github.com/d98762625\n[@j433866]: https://github.com/j433866\n[@n1073645]: https://github.com/n1073645\n[@GCHQ77703]: https://github.com/GCHQ77703\n[@h345983745]: https://github.com/h345983745\n[@s2224834]: https://github.com/s2224834\n[@artemisbot]: https://github.com/artemisbot\n[@tlwr]: https://github.com/tlwr\n[@picapi]: https://github.com/picapi\n[@Dachande663]: https://github.com/Dachande663\n[@JustAnotherMark]: https://github.com/JustAnotherMark\n[@sevzero]: https://github.com/sevzero\n[@PenguinGeorge]: https://github.com/PenguinGeorge\n[@arnydo]: https://github.com/arnydo\n[@klaxon1]: https://github.com/klaxon1\n[@bwhitn]: https://github.com/bwhitn\n[@jarmovanlenthe]: https://github.com/jarmovanlenthe\n[@tcode2k16]: https://github.com/tcode2k16\n[@Cynser]: https://github.com/Cynser\n[@anthony-arnold]: https://github.com/anthony-arnold\n[@masq]: https://github.com/masq\n[@Ge0rg3]: https://github.com/Ge0rg3\n[@MShwed]: https://github.com/MShwed\n[@kassi]: https://github.com/kassi\n[@jarrodconnolly]: https://github.com/jarrodconnolly\n[@VirtualColossus]: https://github.com/VirtualColossus\n[@cbeuw]: https://github.com/cbeuw\n[@matthieuxyz]: https://github.com/matthieuxyz\n[@Flavsditz]: https://github.com/Flavsditz\n[@pointhi]: https://github.com/pointhi\n[@MarvinJWendt]: https://github.com/MarvinJWendt\n[@dmfj]: https://github.com/dmfj\n[@mattnotmitt]: https://github.com/mattnotmitt\n[@Danh4]: https://github.com/Danh4\n[@john19696]: https://github.com/john19696\n[@t-8ch]: https://github.com/t-8ch\n[@hettysymes]: https://github.com/hettysymes\n[@swesven]: https://github.com/swesven\n[@mikecat]: https://github.com/mikecat\n[@crespyl]: https://github.com/crespyl\n[@thomasleplus]: https://github.com/thomasleplus\n[@valdelaseras]: https://github.com/valdelaseras\n[@brun0ne]: https://github.com/brun0ne\n[@joostrijneveld]: https://github.com/joostrijneveld\n[@Xenonym]: https://github.com/Xenonym\n[@gchq77703]: https://github.com/gchq77703\n[@a3957273]: https://github.com/a3957273\n[@0xThiebaut]: https://github.com/0xThiebaut\n[@cnotin]: https://github.com/cnotin\n[@KevinSJ]: https://github.com/KevinSJ\n[@sw5678]: https://github.com/sw5678\n[@sg5506844]: https://github.com/sg5506844\n[@AliceGrey]: https://github.com/AliceGrey\n[@AshCorr]: https://github.com/AshCorr\n[@simonw]: https://github.com/simonw\n[@chriswhite199]: https://github.com/chriswhite199\n[@breakersall]: https://github.com/breakersall\n[@evanreichard]: https://github.com/evanreichard\n[@devcydo]: https://github.com/devcydo\n[@zb3]: https://github.com/zb3\n[@jkataja]: https://github.com/jkataja\n[@tomgond]: https://github.com/tomgond\n[@e218736]: https://github.com/e218736\n[@TheZ3ro]: https://github.com/TheZ3ro\n[@EvieHarv]: https://github.com/EvieHarv\n[@cplussharp]: https://github.com/cplussharp\n[@robinsandhu]: https://github.com/robinsandhu\n[@eltociear]: https://github.com/eltociear\n[@GuilhermoReadonly]: https://github.com/GuilhermoReadonly\n[@simonarnell]: https://github.com/simonarnell\n[@RandomByte]: https://github.com/RandomByte\n[@c65722]: https://github.com/c65722\n[@c65722]: https://github.com/c65722\n[@c65722]: https://github.com/c65722\n[@max0x53]: https://github.com/max0x53\n[@Adamkadaban]: https://github.com/Adamkadaban\n[@c65722]: https://github.com/c65722\n[@jb30795]: https://github.com/jb30795\n[@FranciscoPombal]: https://github.com/FranciscoPombal\n[@Oshawk]: https://github.com/Oshawk\n[@Oshawk]: https://github.com/Oshawk\n[@bartblaze]: https://github.com/bartblaze\n[@exactlyaron]: https://github.com/exactlyaron\n[@k3ach]: https://github.com/k3ach\n[@vs4vijay]: https://github.com/vs4vijay\n[@FranciscoPombal]: https://github.com/FranciscoPombal\n[@FranciscoPombal]: https://github.com/FranciscoPombal\n[@linuxgemini]: https://github.com/linuxgemini\n[@depperm]: https://github.com/depperm\n[@evenstensberg]: https://github.com/evenstensberg\n[@bartblaze]: https://github.com/bartblaze\n[@0xh3xa]: https://github.com/0xh3xa\n[@flakjacket95]: https://github.com/flakjacket95\n[@zhzy0077]: https://github.com/zhzy0077\n[@JSCU-CNI]: https://github.com/JSCU-CNI\n[@ccarpo]: https://github.com/ccarpo\n[@r4mos]: https://github.com/r4mos\n[@0xh3xa]: https://github.com/0xh3xa\n[@0xh3xa]: https://github.com/0xh3xa\n[@PathToLife]: https://github.com/PathToLife\n[@peterc-s]: https://github.com/peterc-s\n[@plvie]: https://github.com/plvie\n[@kenduguay1]: https://github.com/kenduguay1\n[@jonking-ajar]: https://github.com/jonking-ajar\n[@PathToLife]: https://github.com/PathToLife\n[@r4mos]: https://github.com/r4mos\n[@jg42526]: https://github.com/jg42526\n[@es45411]: https://github.com/es45411\n[@gchq]: https://github.com/gchq\n[@gchqdev364]: https://github.com/gchqdev364\n[@GCHQDeveloper94872]: https://github.com/GCHQDeveloper94872\n[@Sma-Das]: https://github.com/Sma-Das\n[@gchq]: https://github.com/gchq\n[@Odyhibit]: https://github.com/Odyhibit\n[@ericli-splunk]: https://github.com/ericli-splunk\n[@xumptex]: https://github.com/xumptex\n[@bartvanandel]: https://github.com/bartvanandel\n[@bartvanandel]: https://github.com/bartvanandel\n[@kendallgoto]: https://github.com/kendallgoto\n[@remingtr]: https://github.com/remingtr\n[@0xff1ce]: https://github.com/0xff1ce\n[@starplanet]: https://github.com/starplanet\n[@C85297]: https://github.com/C85297\n[@GCHQDeveloper581]: https://github.com/GCHQDeveloper581\n[@ThomasNotTom]: https://github.com/ThomasNotTom\n[@rbpi]: https://github.com/rbpi\n[@AlexGustafsson]: https://github.com/AlexGustafsson\n[@tuliperis]: https://github.com/tuliperis\n[@thomasxm]: https://github.com/thomasxm\n[@twostraws]: https://github.com/twostraws\n[@beneri]: https://github.com/beneri\n[@t-martine]: https://github.com/t-martine\n[@wesinator]: https://github.com/wesinator\n[@Raka-loah]: https://github.com/Raka-loah\n\n\n[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7\n[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513\n[289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe\n[e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8\n[dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da\n[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737\n[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff\n[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1\n[65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7\n[0a353ee]: https://github.com/gchq/CyberChef/commit/0a353eeb378b9ca5d49e23c7dfc175ae07107b08\n[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1\n[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25\n[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332\n[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac\n[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6\n[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914\n[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88\n[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155\n[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548\n[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5\n[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8\n[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33\n[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6\n[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1\n[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25\n[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332\n[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac\n[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6\n[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914\n[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88\n[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155\n[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548\n[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5\n[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8\n[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33\n[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6\n[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1\n[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25\n[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332\n[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac\n[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6\n[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914\n[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88\n[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155\n[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548\n[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5\n[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8\n[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33\n[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6\n[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1\n\n[#95]: https://github.com/gchq/CyberChef/pull/299\n[#173]: https://github.com/gchq/CyberChef/pull/173\n[#143]: https://github.com/gchq/CyberChef/pull/143\n[#224]: https://github.com/gchq/CyberChef/pull/224\n[#239]: https://github.com/gchq/CyberChef/pull/239\n[#248]: https://github.com/gchq/CyberChef/pull/248\n[#255]: https://github.com/gchq/CyberChef/issues/255\n[#277]: https://github.com/gchq/CyberChef/issues/277\n[#281]: https://github.com/gchq/CyberChef/pull/281\n[#284]: https://github.com/gchq/CyberChef/pull/284\n[#291]: https://github.com/gchq/CyberChef/pull/291\n[#294]: https://github.com/gchq/CyberChef/pull/294\n[#296]: https://github.com/gchq/CyberChef/pull/296\n[#298]: https://github.com/gchq/CyberChef/pull/298\n[#311]: https://github.com/gchq/CyberChef/pull/311\n[#325]: https://github.com/gchq/CyberChef/pull/325\n[#338]: https://github.com/gchq/CyberChef/pull/338\n[#340]: https://github.com/gchq/CyberChef/pull/340\n[#344]: https://github.com/gchq/CyberChef/pull/344\n[#348]: https://github.com/gchq/CyberChef/pull/348\n[#351]: https://github.com/gchq/CyberChef/pull/351\n[#387]: https://github.com/gchq/CyberChef/pull/387\n[#394]: https://github.com/gchq/CyberChef/pull/394\n[#428]: https://github.com/gchq/CyberChef/pull/428\n[#439]: https://github.com/gchq/CyberChef/pull/439\n[#440]: https://github.com/gchq/CyberChef/pull/440\n[#441]: https://github.com/gchq/CyberChef/pull/441\n[#443]: https://github.com/gchq/CyberChef/pull/443\n[#446]: https://github.com/gchq/CyberChef/pull/446\n[#448]: https://github.com/gchq/CyberChef/pull/448\n[#449]: https://github.com/gchq/CyberChef/pull/449\n[#455]: https://github.com/gchq/CyberChef/pull/455\n[#458]: https://github.com/gchq/CyberChef/pull/458\n[#461]: https://github.com/gchq/CyberChef/pull/461\n[#467]: https://github.com/gchq/CyberChef/pull/467\n[#468]: https://github.com/gchq/CyberChef/pull/468\n[#476]: https://github.com/gchq/CyberChef/pull/476\n[#477]: https://github.com/gchq/CyberChef/pull/477\n[#489]: https://github.com/gchq/CyberChef/pull/489\n[#496]: https://github.com/gchq/CyberChef/pull/496\n[#500]: https://github.com/gchq/CyberChef/pull/500\n[#506]: https://github.com/gchq/CyberChef/pull/506\n[#515]: https://github.com/gchq/CyberChef/pull/515\n[#516]: https://github.com/gchq/CyberChef/pull/516\n[#525]: https://github.com/gchq/CyberChef/pull/525\n[#528]: https://github.com/gchq/CyberChef/pull/528\n[#530]: https://github.com/gchq/CyberChef/pull/530\n[#531]: https://github.com/gchq/CyberChef/pull/531\n[#533]: https://github.com/gchq/CyberChef/pull/533\n[#535]: https://github.com/gchq/CyberChef/pull/535\n[#556]: https://github.com/gchq/CyberChef/pull/556\n[#566]: https://github.com/gchq/CyberChef/pull/566\n[#571]: https://github.com/gchq/CyberChef/pull/571\n[#585]: https://github.com/gchq/CyberChef/pull/585\n[#591]: https://github.com/gchq/CyberChef/pull/591\n[#595]: https://github.com/gchq/CyberChef/pull/595\n[#614]: https://github.com/gchq/CyberChef/pull/614\n[#625]: https://github.com/gchq/CyberChef/pull/625\n[#627]: https://github.com/gchq/CyberChef/pull/627\n[#632]: https://github.com/gchq/CyberChef/pull/632\n[#652]: https://github.com/gchq/CyberChef/pull/652\n[#653]: https://github.com/gchq/CyberChef/pull/653\n[#674]: https://github.com/gchq/CyberChef/pull/674\n[#683]: https://github.com/gchq/CyberChef/pull/683\n[#865]: https://github.com/gchq/CyberChef/pull/865\n[#906]: https://github.com/gchq/CyberChef/pull/906\n[#912]: https://github.com/gchq/CyberChef/pull/912\n[#917]: https://github.com/gchq/CyberChef/pull/917\n[#934]: https://github.com/gchq/CyberChef/pull/934\n[#948]: https://github.com/gchq/CyberChef/pull/948\n[#951]: https://github.com/gchq/CyberChef/pull/951\n[#952]: https://github.com/gchq/CyberChef/pull/952\n[#965]: https://github.com/gchq/CyberChef/pull/965\n[#966]: https://github.com/gchq/CyberChef/pull/966\n[#987]: https://github.com/gchq/CyberChef/pull/987\n[#999]: https://github.com/gchq/CyberChef/pull/999\n[#1006]: https://github.com/gchq/CyberChef/pull/1006\n[#1022]: https://github.com/gchq/CyberChef/pull/1022\n[#1037]: https://github.com/gchq/CyberChef/pull/1037\n[#1045]: https://github.com/gchq/CyberChef/pull/1045\n[#1049]: https://github.com/gchq/CyberChef/pull/1049\n[#1065]: https://github.com/gchq/CyberChef/pull/1065\n[#1066]: https://github.com/gchq/CyberChef/pull/1066\n[#1083]: https://github.com/gchq/CyberChef/pull/1083\n[#1189]: https://github.com/gchq/CyberChef/pull/1189\n[#1242]: https://github.com/gchq/CyberChef/pull/1242\n[#1244]: https://github.com/gchq/CyberChef/pull/1244\n[#1313]: https://github.com/gchq/CyberChef/pull/1313\n[#1326]: https://github.com/gchq/CyberChef/pull/1326\n[#1364]: https://github.com/gchq/CyberChef/pull/1364\n[#1264]: https://github.com/gchq/CyberChef/pull/1264\n[#1266]: https://github.com/gchq/CyberChef/pull/1266\n[#1250]: https://github.com/gchq/CyberChef/pull/1250\n[#1308]: https://github.com/gchq/CyberChef/pull/1308\n[#1405]: https://github.com/gchq/CyberChef/pull/1405\n[#1421]: https://github.com/gchq/CyberChef/pull/1421\n[#1427]: https://github.com/gchq/CyberChef/pull/1427\n[#1472]: https://github.com/gchq/CyberChef/pull/1472\n[#1457]: https://github.com/gchq/CyberChef/pull/1457\n[#1466]: https://github.com/gchq/CyberChef/pull/1466\n[#1456]: https://github.com/gchq/CyberChef/pull/1456\n[#1450]: https://github.com/gchq/CyberChef/pull/1450\n[#1498]: https://github.com/gchq/CyberChef/pull/1498\n[#1499]: https://github.com/gchq/CyberChef/pull/1499\n[#1528]: https://github.com/gchq/CyberChef/pull/1528\n[#661]: https://github.com/gchq/CyberChef/pull/661\n[#493]: https://github.com/gchq/CyberChef/pull/493\n[#592]: https://github.com/gchq/CyberChef/issues/592\n[#1703]: https://github.com/gchq/CyberChef/issues/1703\n[#1675]: https://github.com/gchq/CyberChef/issues/1675\n[#1678]: https://github.com/gchq/CyberChef/issues/1678\n[#1541]: https://github.com/gchq/CyberChef/issues/1541\n[#1667]: https://github.com/gchq/CyberChef/issues/1667\n[#1555]: https://github.com/gchq/CyberChef/issues/1555\n[#1694]: https://github.com/gchq/CyberChef/issues/1694\n[#1699]: https://github.com/gchq/CyberChef/issues/1699\n[#1757]: https://github.com/gchq/CyberChef/issues/1757\n[#1752]: https://github.com/gchq/CyberChef/issues/1752\n[#1753]: https://github.com/gchq/CyberChef/issues/1753\n[#1750]: https://github.com/gchq/CyberChef/issues/1750\n[#1591]: https://github.com/gchq/CyberChef/issues/1591\n[#654]: https://github.com/gchq/CyberChef/issues/654\n[#1762]: https://github.com/gchq/CyberChef/issues/1762\n[#1606]: https://github.com/gchq/CyberChef/issues/1606\n[#1197]: https://github.com/gchq/CyberChef/issues/1197\n[#933]: https://github.com/gchq/CyberChef/issues/933\n[#1361]: https://github.com/gchq/CyberChef/issues/1361\n[#1765]: https://github.com/gchq/CyberChef/issues/1765\n[#1767]: https://github.com/gchq/CyberChef/issues/1767\n[#1769]: https://github.com/gchq/CyberChef/issues/1769\n[#1759]: https://github.com/gchq/CyberChef/issues/1759\n[#1504]: https://github.com/gchq/CyberChef/issues/1504\n[#512]: https://github.com/gchq/CyberChef/issues/512\n[#1732]: https://github.com/gchq/CyberChef/issues/1732\n[#1789]: https://github.com/gchq/CyberChef/issues/1789\n[#1040]: https://github.com/gchq/CyberChef/pull/1040\n[#2176]: https://github.com/gchq/CyberChef/pull/2176\n[#2177]: https://github.com/gchq/CyberChef/pull/2177\n[#2174]: https://github.com/gchq/CyberChef/pull/2174\n[#2058]: https://github.com/gchq/CyberChef/pull/2058\n[#1861]: https://github.com/gchq/CyberChef/pull/1861\n[#2055]: https://github.com/gchq/CyberChef/pull/2055\n[#2169]: https://github.com/gchq/CyberChef/pull/2169\n[#2175]: https://github.com/gchq/CyberChef/pull/2175\n[#2173]: https://github.com/gchq/CyberChef/pull/2173\n[#2172]: https://github.com/gchq/CyberChef/pull/2172\n[#2136]: https://github.com/gchq/CyberChef/pull/2136\n[#2165]: https://github.com/gchq/CyberChef/pull/2165\n[#2159]: https://github.com/gchq/CyberChef/pull/2159\n[#2086]: https://github.com/gchq/CyberChef/pull/2086\n[#2118]: https://github.com/gchq/CyberChef/pull/2118\n[#2166]: https://github.com/gchq/CyberChef/pull/2166\n[#2188]: https://github.com/gchq/CyberChef/pull/2188\n[#2137]: https://github.com/gchq/CyberChef/pull/2137\n[#1876]: https://github.com/gchq/CyberChef/pull/1876\n[#2186]: https://github.com/gchq/CyberChef/pull/2186\n[#1573]: https://github.com/gchq/CyberChef/pull/1573\n[#2183]: https://github.com/gchq/CyberChef/pull/2183\n[#2182]: https://github.com/gchq/CyberChef/pull/2182\n[#2181]: https://github.com/gchq/CyberChef/pull/2181\n\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at oss@gchq.gov.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "Dockerfile",
    "content": "#####################################\n# Build the app to a static website #\n#####################################\n# Modifier --platform=$BUILDPLATFORM limits the platform to \"BUILDPLATFORM\" during buildx multi-platform builds\n# This is because npm \"chromedriver\" package is not compatiable with all platforms\n# For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation\nFROM --platform=$BUILDPLATFORM node:18-alpine AS builder\n\nWORKDIR /app\n\nCOPY package.json .\nCOPY package-lock.json .\n\n# Install dependencies\n# --ignore-scripts prevents postinstall script (which runs grunt) as it depends on files other than package.json\nRUN npm ci --ignore-scripts\n\n# Copy files needed for postinstall and build\nCOPY . .\n\n# npm postinstall runs grunt, which depends on files other than package.json\nRUN npm run postinstall\n\n# Build the app\nRUN npm run build\n\n#########################################\n# Package static build files into nginx #\n#########################################\nFROM nginx:stable-alpine AS cyberchef\n\nLABEL maintainer=\"GCHQ <oss@gchq.gov.uk>\"\n\nCOPY --from=builder /app/build/prod /usr/share/nginx/html/\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "\"use strict\";\n\nconst webpack = require(\"webpack\");\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\nconst BundleAnalyzerPlugin = require(\"webpack-bundle-analyzer\").BundleAnalyzerPlugin;\nconst glob = require(\"glob\");\nconst path = require(\"path\");\n\nconst nodeFlags = \"--experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings --no-deprecation\";\n\n/**\n * Grunt configuration for building the app in various formats.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nmodule.exports = function (grunt) {\n    grunt.file.defaultEncoding = \"utf8\";\n    grunt.file.preserveBOM = false;\n\n    // Tasks\n    grunt.registerTask(\"dev\",\n        \"A persistent task which creates a development build whenever source files are modified.\",\n        [\"clean:dev\", \"clean:config\", \"exec:generateConfig\", \"concurrent:dev\"]);\n\n    grunt.registerTask(\"prod\",\n        \"Creates a production-ready build. Use the --msg flag to add a compile message.\",\n        [\n            \"eslint\", \"clean:prod\", \"clean:config\", \"exec:generateConfig\", \"findModules\", \"webpack:web\",\n            \"copy:standalone\", \"zip:standalone\", \"clean:standalone\", \"exec:calcDownloadHash\", \"chmod\"\n        ]);\n\n    grunt.registerTask(\"node\",\n        \"Compiles CyberChef into a single NodeJS module.\",\n        [\n            \"clean:node\", \"clean:config\", \"clean:nodeConfig\", \"exec:generateConfig\", \"exec:generateNodeIndex\"\n        ]);\n\n    grunt.registerTask(\"configTests\",\n        \"A task which configures config files in preparation for tests to be run. Use `npm test` to run tests.\",\n        [\n            \"clean:config\", \"clean:nodeConfig\", \"exec:generateConfig\", \"exec:generateNodeIndex\"\n        ]);\n\n    grunt.registerTask(\"testui\",\n        \"A task which runs all the UI tests in the tests directory. The prod task must already have been run.\",\n        [\"connect:prod\", \"exec:browserTests\"]);\n\n    grunt.registerTask(\"testnodeconsumer\",\n        \"A task which checks whether consuming CJS and ESM apps work with the CyberChef build\",\n        [\"exec:setupNodeConsumers\", \"exec:testCJSNodeConsumer\", \"exec:testESMNodeConsumer\", \"exec:teardownNodeConsumers\"]);\n\n    grunt.registerTask(\"default\",\n        \"Lints the code base\",\n        [\"eslint\", \"exec:repoSize\"]);\n\n    grunt.registerTask(\"lint\", \"eslint\");\n\n    grunt.registerTask(\"findModules\",\n        \"Finds all generated modules and updates the entry point list for Webpack\",\n        function(arg1, arg2) {\n            const moduleEntryPoints = listEntryModules();\n\n            grunt.log.writeln(`Found ${Object.keys(moduleEntryPoints).length} modules.`);\n\n            grunt.config.set(\"webpack.web.entry\",\n                Object.assign({\n                    main: \"./src/web/index.js\"\n                }, moduleEntryPoints));\n        });\n\n\n    // Load tasks provided by each plugin\n    grunt.loadNpmTasks(\"grunt-eslint\");\n    grunt.loadNpmTasks(\"grunt-webpack\");\n    grunt.loadNpmTasks(\"grunt-contrib-clean\");\n    grunt.loadNpmTasks(\"grunt-contrib-copy\");\n    grunt.loadNpmTasks(\"grunt-contrib-watch\");\n    grunt.loadNpmTasks(\"grunt-chmod\");\n    grunt.loadNpmTasks(\"grunt-exec\");\n    grunt.loadNpmTasks(\"grunt-concurrent\");\n    grunt.loadNpmTasks(\"grunt-contrib-connect\");\n    grunt.loadNpmTasks(\"grunt-zip\");\n\n\n    // Project configuration\n    const compileYear = grunt.template.today(\"UTC:yyyy\"),\n        compileTime = grunt.template.today(\"UTC:dd/mm/yyyy HH:MM:ss\") + \" UTC\",\n        pkg = grunt.file.readJSON(\"package.json\"),\n        webpackConfig = require(\"./webpack.config.js\"),\n        BUILD_CONSTANTS = {\n            COMPILE_YEAR: JSON.stringify(compileYear),\n            COMPILE_TIME: JSON.stringify(compileTime),\n            COMPILE_MSG: JSON.stringify(grunt.option(\"compile-msg\") || grunt.option(\"msg\") || \"\"),\n            PKG_VERSION: JSON.stringify(pkg.version),\n        },\n        moduleEntryPoints = listEntryModules(),\n        nodeConsumerTestPath = \"~/tmp-cyberchef\",\n        /**\n         * Configuration for Webpack production build. Defined as a function so that it\n         * can be recalculated when new modules are generated.\n         */\n        webpackProdConf = () => {\n            return {\n                mode: \"production\",\n                target: \"web\",\n                entry: Object.assign({\n                    main: \"./src/web/index.js\"\n                }, moduleEntryPoints),\n                output: {\n                    path: __dirname + \"/build/prod\",\n                    filename: chunkData => {\n                        return chunkData.chunk.name === \"main\" ? \"assets/[name].js\": \"[name].js\";\n                    },\n                    globalObject: \"this\"\n                },\n                resolve: {\n                    alias: {\n                        \"./config/modules/OpModules.mjs\": \"./config/modules/Default.mjs\"\n                    }\n                },\n                plugins: [\n                    new webpack.DefinePlugin(BUILD_CONSTANTS),\n                    new HtmlWebpackPlugin({\n                        filename: \"index.html\",\n                        template: \"./src/web/html/index.html\",\n                        chunks: [\"main\"],\n                        compileYear: compileYear,\n                        compileTime: compileTime,\n                        version: pkg.version,\n                        minify: {\n                            removeComments: true,\n                            collapseWhitespace: true,\n                            minifyJS: true,\n                            minifyCSS: true\n                        }\n                    }),\n                    new BundleAnalyzerPlugin({\n                        analyzerMode: \"static\",\n                        reportFilename: \"BundleAnalyzerReport.html\",\n                        openAnalyzer: false\n                    }),\n                ]\n            };\n        };\n\n\n    /**\n     * Generates an entry list for all the modules.\n     */\n    function listEntryModules() {\n        const entryModules = {};\n\n        glob.sync(\"./src/core/config/modules/*.mjs\").forEach(file => {\n            const basename = path.basename(file);\n            if (basename !== \"Default.mjs\" && basename !== \"OpModules.mjs\")\n                entryModules[\"modules/\" + basename.split(\".mjs\")[0]] = path.resolve(file);\n        });\n\n        return entryModules;\n    }\n\n    /**\n     * Detects the correct delimiter to use to chain shell commands together\n     * based on the current OS.\n     *\n     * @param {string[]} cmds\n     * @returns {string}\n     */\n    function chainCommands(cmds) {\n        const win = process.platform === \"win32\";\n        if (!win) {\n            return cmds.join(\";\");\n        }\n        return cmds\n            // && means that subsequent commands will not be executed if the\n            // previous one fails. & would coninue on a fail\n            .join(\"&&\")\n            // Windows does not support \\n properly\n            .replace(/\\n/g, \"\\\\n\");\n    }\n\n    grunt.initConfig({\n        clean: {\n            dev: [\"build/dev/*\"],\n            prod: [\"build/prod/*\"],\n            node: [\"build/node/*\"],\n            config: [\"src/core/config/OperationConfig.json\", \"src/core/config/modules/*\", \"src/code/operations/index.mjs\"],\n            nodeConfig: [\"src/node/index.mjs\", \"src/node/config/OperationConfig.json\"],\n            standalone: [\"build/prod/CyberChef*.html\"]\n        },\n        eslint: {\n            configs: [\"*.{js,mjs}\"],\n            core: [\"src/core/**/*.{js,mjs}\", \"!src/core/vendor/**/*\", \"!src/core/operations/legacy/**/*\"],\n            web: [\"src/web/**/*.{js,mjs}\", \"!src/web/static/**/*\"],\n            node: [\"src/node/**/*.{js,mjs}\"],\n            tests: [\"tests/**/*.{js,mjs}\"],\n        },\n        webpack: {\n            options: webpackConfig,\n            myConfig: webpackConfig,\n            web: webpackProdConf(),\n        },\n        \"webpack-dev-server\": {\n            options: webpackConfig,\n            start: {\n                mode: \"development\",\n                target: \"web\",\n                entry: Object.assign({\n                    main: \"./src/web/index.js\"\n                }, moduleEntryPoints),\n                resolve: {\n                    alias: {\n                        \"./config/modules/OpModules.mjs\": \"./config/modules/Default.mjs\"\n                    }\n                },\n                devServer: {\n                    port: grunt.option(\"port\") || 8080,\n                    client: {\n                        logging: \"error\",\n                        overlay: true\n                    },\n                    hot: \"only\"\n                },\n                plugins: [\n                    new webpack.DefinePlugin(BUILD_CONSTANTS),\n                    new HtmlWebpackPlugin({\n                        filename: \"index.html\",\n                        template: \"./src/web/html/index.html\",\n                        chunks: [\"main\"],\n                        compileYear: compileYear,\n                        compileTime: compileTime,\n                        version: pkg.version,\n                    })\n                ]\n            }\n        },\n        zip: {\n            standalone: {\n                cwd: \"build/prod/\",\n                src: [\n                    \"build/prod/**/*\",\n                    \"!build/prod/index.html\",\n                    \"!build/prod/BundleAnalyzerReport.html\",\n                ],\n                dest: `build/prod/CyberChef_v${pkg.version}.zip`\n            }\n        },\n        connect: {\n            prod: {\n                options: {\n                    port: grunt.option(\"port\") || 8000,\n                    base: \"build/prod/\"\n                }\n            }\n        },\n        copy: {\n            ghPages: {\n                options: {\n                    process: function (content, srcpath) {\n                        if (srcpath.indexOf(\"index.html\") >= 0) {\n                            // Add Google Analytics code to index.html\n                            content = content.replace(\"</body></html>\",\n                                grunt.file.read(\"src/web/static/ga.html\") + \"</body></html>\");\n\n                            // Add Structured Data for SEO\n                            content = content.replace(\"</head>\",\n                                \"<script type='application/ld+json'>\" +\n                                JSON.stringify(JSON.parse(grunt.file.read(\"src/web/static/structuredData.json\"))) +\n                                \"</script></head>\");\n                            return grunt.template.process(content, srcpath);\n                        } else {\n                            return content;\n                        }\n                    },\n                    noProcess: [\"**\", \"!**/*.html\"]\n                },\n                files: [\n                    {\n                        src: [\"build/prod/index.html\"],\n                        dest: \"build/prod/index.html\"\n                    }\n                ]\n            },\n            standalone: {\n                options: {\n                    process: function (content, srcpath) {\n                        if (srcpath.indexOf(\"index.html\") >= 0) {\n                            // Replace download link with version number\n                            content = content.replace(/<a [^>]+>Download CyberChef.+?<\\/a>/,\n                                `<span>Version ${pkg.version}</span>`);\n\n                            return grunt.template.process(content, srcpath);\n                        } else {\n                            return content;\n                        }\n                    },\n                    noProcess: [\"**\", \"!**/*.html\"]\n                },\n                files: [\n                    {\n                        src: [\"build/prod/index.html\"],\n                        dest: `build/prod/CyberChef_v${pkg.version}.html`\n                    }\n                ]\n            }\n        },\n        chmod: {\n            build: {\n                options: {\n                    mode: \"755\",\n                },\n                src: [\"build/**/*\", \"build/\"]\n            }\n        },\n        watch: {\n            config: {\n                files: [\"src/core/operations/**/*\", \"!src/core/operations/index.mjs\"],\n                tasks: [\"exec:generateNodeIndex\", \"exec:generateConfig\"]\n            }\n        },\n        concurrent: {\n            dev: [\"watch:config\", \"webpack-dev-server:start\"],\n            options: {\n                logConcurrentOutput: true\n            }\n        },\n        exec: {\n            calcDownloadHash: {\n                command: function () {\n                    switch (process.platform) {\n                        case \"darwin\":\n                            return chainCommands([\n                                `shasum -a 256 build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,\n                                `sed -i '' -e \"s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/\" build/prod/index.html`\n                            ]);\n                        default:\n                            return chainCommands([\n                                `sha256sum build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,\n                                `sed -i -e \"s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/\" build/prod/index.html`\n                            ]);\n                    }\n                },\n            },\n            repoSize: {\n                command: chainCommands([\n                    \"git ls-files | wc -l | xargs printf '\\n%b\\ttracked files\\n'\",\n                    \"du -hs | egrep -o '^[^\\t]*' | xargs printf '%b\\trepository size\\n'\"\n                ]),\n                stderr: false\n            },\n            cleanGit: {\n                command: \"git gc --prune=now --aggressive\"\n            },\n            sitemap: {\n                command: `node ${nodeFlags} src/web/static/sitemap.mjs > build/prod/sitemap.xml`,\n                sync: true\n            },\n            generateConfig: {\n                command: chainCommands([\n                    \"echo '\\n--- Regenerating config files. ---'\",\n                    \"echo [] > src/core/config/OperationConfig.json\",\n                    `node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`,\n                    `node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`,\n                    \"echo '--- Config scripts finished. ---\\n'\"\n                ]),\n                sync: true\n            },\n            generateNodeIndex: {\n                command: chainCommands([\n                    \"echo '\\n--- Regenerating node index ---'\",\n                    `node ${nodeFlags} src/node/config/scripts/generateNodeIndex.mjs`,\n                    \"echo '--- Node index generated. ---\\n'\"\n                ]),\n                sync: true\n            },\n            browserTests: {\n                command: \"./node_modules/.bin/nightwatch --env prod\"\n            },\n            setupNodeConsumers: {\n                command: chainCommands([\n                    \"echo '\\n--- Testing node consumers ---'\",\n                    \"npm link\",\n                    `mkdir ${nodeConsumerTestPath}`,\n                    `cp tests/node/consumers/* ${nodeConsumerTestPath}`,\n                    `cd ${nodeConsumerTestPath}`,\n                    \"npm link cyberchef\"\n                ]),\n                sync: true\n            },\n            teardownNodeConsumers: {\n                command: chainCommands([\n                    `rm -rf ${nodeConsumerTestPath}`,\n                    \"echo '\\n--- Node consumer tests complete ---'\"\n                ]),\n            },\n            testCJSNodeConsumer: {\n                command: chainCommands([\n                    `cd ${nodeConsumerTestPath}`,\n                    `node ${nodeFlags} cjs-consumer.js`,\n                ]),\n                stdout: false,\n            },\n            testESMNodeConsumer: {\n                command: chainCommands([\n                    `cd ${nodeConsumerTestPath}`,\n                    `node ${nodeFlags} esm-consumer.mjs`,\n                ]),\n                stdout: false,\n            },\n            fixCryptoApiImports: {\n                command: function () {\n                    switch (process.platform) {\n                        case \"darwin\":\n                            return `find ./node_modules/crypto-api/src/ \\\\( -type d -name .git -prune \\\\) -o -type f -print0 | xargs -0 sed -i '' -e '/\\\\.mjs/!s/\\\\(from \"\\\\.[^\"]*\\\\)\";/\\\\1.mjs\";/g'`;\n                        default:\n                            return `find ./node_modules/crypto-api/src/ \\\\( -type d -name .git -prune \\\\) -o -type f -print0 | xargs -0 sed -i -e '/\\\\.mjs/!s/\\\\(from \"\\\\.[^\"]*\\\\)\";/\\\\1.mjs\";/g'`;\n                    }\n                },\n                stdout: false\n            },\n            fixSnackbarMarkup: {\n                command: function () {\n                    switch (process.platform) {\n                        case \"darwin\":\n                            return `sed -i '' 's/<div id=snackbar-container\\\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;\n                        default:\n                            return `sed -i 's/<div id=snackbar-container\\\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;\n                    }\n                },\n                stdout: false\n            },\n        },\n    });\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# CyberChef\n\n[![](https://github.com/gchq/CyberChef/workflows/Master%20Build,%20Test%20&%20Deploy/badge.svg)](https://github.com/gchq/CyberChef/actions?query=workflow%3A%22Master+Build%2C+Test+%26+Deploy%22)\n[![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef)\n[![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE)\n[![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\n\n\n#### *The Cyber Swiss Army Knife*\n\nCyberChef is a simple, intuitive web app for carrying out all manner of \"cyber\" operations within a web browser. These operations include simple encoding like XOR and Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.\n\nThe tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years.\n\n## Live demo\n\nCyberChef is still under active development. As a result, it shouldn't be considered a finished product. There is still testing and bug fixing to do, new features to be added and additional documentation to write. Please contribute!\n\nCryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness.\n\n[A live demo can be found here][1] - have fun!\n\n## Running Locally with Docker\n\n**Prerequisites**\n\n- [Docker](https://www.docker.com/products/docker-desktop/)\n  - Docker Desktop must be open and running on your machine\n\n\n#### Option 1: Build the Docker Image Yourself\n\n1. Build the docker image\n```bash\ndocker build --tag cyberchef --ulimit nofile=10000 .\n```\n2. Run the docker container\n```bash\ndocker run -it -p 8080:80 cyberchef\n```\n3. Navigate to `http://localhost:8080` in your browser\n\n#### Option 2: Use the pre-built Docker Image\n\nIf you prefer to skip the build process, you can use the pre-built image\n\n```bash\ndocker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest\n```\n\nJust like before, navigate to `http://localhost:8080` in your browser.\n\nThis image is built and published through our [GitHub Workflows](.github/workflows/releases.yml)\n\n## How it works\n\nThere are four main areas in CyberChef:\n\n 1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on.\n 2. The **output** box in the bottom right, where the outcome of your processing will be displayed.\n 3. The **operations** list on the far left, where you can find all the operations that CyberChef is capable of in categorised lists, or by searching.\n 4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options.\n\nYou can use as many operations as you like in simple or complex ways. Some examples are as follows:\n\n - [Decode a Base64-encoded string][2]\n - [Convert a date and time to a different time zone][3]\n - [Parse a Teredo IPv6 address][4]\n - [Convert data from a hexdump, then decompress][5]\n - [Decrypt and disassemble shellcode][6]\n - [Display multiple timestamps as full dates][7]\n - [Carry out different operations on data of different types][8]\n - [Use parts of the input as arguments to operations][9]\n - [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10]\n - [Automagically detect several layers of nested encoding][12]\n\n\n## Features\n\n - Drag and drop\n     - Operations can be dragged in and out of the recipe list, or reorganised.\n     - Files up to 2GB can be dragged over the input box to load them directly into the browser.\n - Auto Bake\n     - Whenever you modify the input or the recipe, CyberChef will automatically \"bake\" for you and produce the output immediately.\n     - This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).\n - Automated encoding detection\n     - CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation that make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.\n - Breakpoints\n     - You can set breakpoints on any operation in your recipe to pause execution before running it.\n     - You can also step through the recipe one operation at a time to see what the data looks like at each stage.\n - Save and load recipes\n     - If you come up with an awesome recipe that you know you’ll want to use again, just click \"Save recipe\" and add it to your local storage. It'll be waiting for you next time you visit CyberChef.\n     - You can also copy the URL, which includes your recipe and input, to easily share it with others.\n - Search\n     - If you know the name of the operation you want or a word associated with it, start typing it into the search field and any matching operations will immediately be shown.\n - Highlighting\n     - When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).\n - Save to file and load from file\n     - You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 2GB are supported (depending on your browser), however, some operations may take a very long time to run over this much data.\n - CyberChef is entirely client-side\n     - It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.\n     - Due to this feature, CyberChef can be downloaded and run locally. You can use the link in the top left corner of the app to download a full copy of CyberChef and drop it into a virtual machine, share it with other people, or host it in a closed network.\n\n\n## Deep linking\n\nBy manipulating CyberChef's URL hash, you can change the initial settings with which the page opens.\nThe format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...`\n\nSupported arguments are `recipe`, `input` (encoded in Base64), and `theme`.\n\n\n## Browser support\n\nCyberChef is built to support\n\n - Google Chrome 50+\n - Mozilla Firefox 38+\n\n\n## Node.js support\n\nCyberChef is built to fully support Node.js `v16`. For more information, see the [\"Node API\" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)\n\n\n## Contributing\n\nContributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.\n\nAn installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the [\"Contributing\" wiki page](https://github.com/gchq/CyberChef/wiki/Contributing).\n\n - Push your changes to your fork.\n - Submit a pull request. If you are doing this for the first time, you will be prompted to sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) via the CLA assistant on the pull request. This will also ask whether you are happy for GCHQ to contact you about a token of thanks for your contribution, or about job opportunities at GCHQ.\n\n\n## Licencing\n\nCyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/).\n\n\n  [1]: https://gchq.github.io/CyberChef\n  [2]: https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1\n  [3]: https://gchq.github.io/CyberChef/#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA\n  [4]: https://gchq.github.io/CyberChef/#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy\n  [5]: https://gchq.github.io/CyberChef/#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw\n  [6]: https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ\n  [7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA\n  [8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA\n  [9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ\n  [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg\n  [11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4\n  [12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nCyberChef is supported on a best endeavours basis. Patches will be applied to\nthe latest version rather than retroactively to older versions. To ensure you\nare using the most secure version of CyberChef, please make sure you have the\n[latest release](https://github.com/gchq/CyberChef/releases/latest). The\nofficial [live demo](https://gchq.github.io/CyberChef/) is always up to date.\n\n## Reporting a Vulnerability\n\nIn most scenarios, the most appropriate way to report a vulnerability is to\n[raise a new issue](https://github.com/gchq/CyberChef/issues/new/choose)\ndescribing the problem in as much detail as possible, ideally with examples.\nThis will obviously be public. If you feel that the vulnerability is\nsignificant enough to warrant a private disclosure, please email\n[oss@gchq.gov.uk](mailto:oss@gchq.gov.uk) and\n[n1474335@gmail.com](mailto:n1474335@gmail.com).\n\nDisclosures of vulnerabilities in CyberChef are always welcomed. Whilst we aim\nto write clean and secure code free from bugs, we recognise that this is an open\nsource project written by analysts in their spare time, relying on dozens of\nopen source libraries that are modified and updated on a regular basis. We hope\nthat the community will continue to support us as we endeavour to maintain and\ndevelop this tool together.\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = function(api) {\n    api.cache.forever();\n\n    return {\n        \"presets\": [\n            [\"@babel/preset-env\", {\n                \"modules\": false,\n                \"useBuiltIns\": \"entry\",\n                \"corejs\": 3\n            }]\n        ],\n        \"plugins\": [\n            \"@babel/plugin-syntax-import-assertions\",\n            [\n                \"@babel/plugin-transform-runtime\", {\n                    \"regenerator\": true\n                }\n            ]\n        ]\n    };\n};\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import babelParser from \"@babel/eslint-parser\";\nimport jsdoc from \"eslint-plugin-jsdoc\";\nimport js from \"@eslint/js\";\nimport globals from \"globals\";\n\nexport default [\n    js.configs.recommended,\n    {\n        languageOptions: {\n            ecmaVersion: 2022,\n            parser: babelParser,\n            parserOptions: {\n                ecmaVersion: 2022,\n                ecmaFeatures: {\n                    impliedStrict: true\n                },\n                sourceType: \"module\",\n                allowImportExportEverywhere: true\n            },\n            globals: {\n                ...globals.browser,\n                ...globals.node,\n                ...globals.es6,\n                \"$\": false,\n                \"jQuery\": false,\n                \"log\": false,\n                \"app\": false,\n\n                \"COMPILE_TIME\": false,\n                \"COMPILE_MSG\": false,\n                \"PKG_VERSION\": false\n            },\n        },\n        ignores: [\"src/core/vendor/**\"],\n        plugins: {\n            jsdoc\n        },\n        rules: {\n            // enable additional rules\n            \"no-eval\": \"error\",\n            \"no-implied-eval\": \"error\",\n            \"dot-notation\": \"error\",\n            \"eqeqeq\": [\"error\", \"smart\"],\n            \"no-caller\": \"error\",\n            \"no-extra-bind\": \"error\",\n            \"no-unused-expressions\": \"error\",\n            \"no-useless-call\": \"error\",\n            \"no-useless-return\": \"error\",\n            \"radix\": \"warn\",\n\n            // modify rules from base configurations\n            \"no-unused-vars\": [\"error\", {\n                \"args\": \"none\",\n                \"vars\": \"all\",\n                \"caughtErrors\": \"none\"\n            }],\n            \"no-empty\": [\"error\", {\n                \"allowEmptyCatch\": true\n            }],\n\n            // disable rules from base configurations\n            \"no-control-regex\": \"off\",\n            \"require-atomic-updates\": \"off\",\n            \"no-async-promise-executor\": \"off\",\n\n            // stylistic conventions\n            \"brace-style\": [\"error\", \"1tbs\"],\n            \"space-before-blocks\": [\"error\", \"always\"],\n            \"block-spacing\": \"error\",\n            \"array-bracket-spacing\": \"error\",\n            \"comma-spacing\": \"error\",\n            \"spaced-comment\": [\"error\", \"always\", { \"exceptions\": [\"/\"] }],\n            \"comma-style\": \"error\",\n            \"computed-property-spacing\": \"error\",\n            \"no-trailing-spaces\": \"warn\",\n            \"eol-last\": \"error\",\n            \"func-call-spacing\": \"error\",\n            \"key-spacing\": [\"warn\", {\n                \"mode\": \"minimum\"\n            }],\n            \"indent\": [\"error\", 4, {\n                \"ignoreComments\": true,\n                \"ArrayExpression\": \"first\",\n                \"SwitchCase\": 1\n            }],\n            \"linebreak-style\": [\"error\", \"unix\"],\n            \"quotes\": [\"error\", \"double\", {\n                \"avoidEscape\": true,\n                \"allowTemplateLiterals\": true\n            }],\n            \"camelcase\": [\"error\", {\n                \"properties\": \"always\"\n            }],\n            \"semi\": [\"error\", \"always\"],\n            \"unicode-bom\": \"error\",\n            \"jsdoc/require-jsdoc\": [\"error\", {\n                \"require\": {\n                    \"FunctionDeclaration\": true,\n                    \"MethodDefinition\": true,\n                    \"ClassDeclaration\": true,\n                    \"ArrowFunctionExpression\": false\n                }\n            }],\n            \"keyword-spacing\": [\"error\", {\n                \"before\": true,\n                \"after\": true\n            }],\n            \"no-multiple-empty-lines\": [\"warn\", {\n                \"max\": 2,\n                \"maxEOF\": 1,\n                \"maxBOF\": 0\n            }],\n            \"no-whitespace-before-property\": \"error\",\n            \"operator-linebreak\": [\"error\", \"after\"],\n            \"space-in-parens\": \"error\",\n            \"no-var\": \"error\",\n            \"prefer-const\": \"error\",\n            \"no-console\": \"error\"\n        },\n    },\n    // File-pattern specific overrides\n    {\n        files: [\"tests/**/*\"],\n        rules: {\n            \"no-unused-expressions\": \"off\",\n            \"no-console\": \"off\"\n        }\n    },\n];\n"
  },
  {
    "path": "nightwatch.json",
    "content": "{\n  \"src_folders\": [\"tests/browser\"],\n  \"exclude\": [\"tests/browser/browserUtils.js\"],\n  \"output_folder\": \"tests/browser/output\",\n\n  \"test_settings\": {\n\n    \"default\": {\n      \"launch_url\": \"http://localhost:8080\",\n      \"webdriver\": {\n        \"start_process\": true,\n        \"server_path\": \"./node_modules/.bin/chromedriver\",\n        \"port\": 9515,\n        \"log_path\": \"tests/browser/output\"\n      },\n      \"desiredCapabilities\": {\n        \"browserName\": \"chrome\"\n      },\n      \"enable_fail_fast\": true\n    },\n\n    \"dev\": {\n      \"launch_url\": \"http://localhost:8080\"\n    },\n\n    \"prod\": {\n      \"launch_url\": \"http://localhost:8000/index.html\"\n    }\n\n  }\n}\n\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"cyberchef\",\n  \"version\": \"10.22.1\",\n  \"description\": \"The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.\",\n  \"author\": \"n1474335 <n1474335@gmail.com>\",\n  \"homepage\": \"https://gchq.github.io/CyberChef\",\n  \"copyright\": \"Crown copyright 2016\",\n  \"license\": \"Apache-2.0\",\n  \"keywords\": [\n    \"cipher\",\n    \"cypher\",\n    \"encode\",\n    \"decode\",\n    \"encrypt\",\n    \"decrypt\",\n    \"base64\",\n    \"xor\",\n    \"charset\",\n    \"hex\",\n    \"encoding\",\n    \"format\",\n    \"cybersecurity\",\n    \"data manipulation\",\n    \"data analysis\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/gchq/CyberChef/\"\n  },\n  \"main\": \"src/node/wrapper.js\",\n  \"exports\": {\n    \"import\": \"./src/node/index.mjs\",\n    \"require\": \"./src/node/wrapper.js\"\n  },\n  \"bugs\": \"https://github.com/gchq/CyberChef/issues\",\n  \"browserslist\": [\n    \"Chrome >= 50\",\n    \"Firefox >= 38\",\n    \"node >= 16\"\n  ],\n  \"devDependencies\": {\n    \"@babel/eslint-parser\": \"^7.28.6\",\n    \"@babel/plugin-syntax-import-assertions\": \"^7.28.6\",\n    \"@babel/plugin-transform-runtime\": \"^7.29.0\",\n    \"@babel/preset-env\": \"^7.29.2\",\n    \"@babel/runtime\": \"^7.28.6\",\n    \"@codemirror/commands\": \"^6.10.3\",\n    \"@codemirror/language\": \"^6.12.2\",\n    \"@codemirror/search\": \"^6.6.0\",\n    \"@codemirror/state\": \"^6.5.4\",\n    \"@codemirror/view\": \"^6.39.17\",\n    \"autoprefixer\": \"^10.4.27\",\n    \"babel-loader\": \"^10.1.1\",\n    \"base64-loader\": \"^1.0.0\",\n    \"chromedriver\": \"^130.0.4\",\n    \"cli-progress\": \"^3.12.0\",\n    \"colors\": \"^1.4.0\",\n    \"compression-webpack-plugin\": \"^11.1.0\",\n    \"copy-webpack-plugin\": \"^13.0.1\",\n    \"core-js\": \"^3.48.0\",\n    \"cspell\": \"^8.19.4\",\n    \"css-loader\": \"7.1.4\",\n    \"eslint\": \"^9.39.4\",\n    \"eslint-plugin-jsdoc\": \"^50.8.0\",\n    \"globals\": \"^15.15.0\",\n    \"grunt\": \"^1.6.1\",\n    \"grunt-chmod\": \"~1.1.1\",\n    \"grunt-concurrent\": \"^3.0.0\",\n    \"grunt-contrib-clean\": \"~2.0.1\",\n    \"grunt-contrib-connect\": \"^5.0.1\",\n    \"grunt-contrib-copy\": \"~1.0.0\",\n    \"grunt-contrib-watch\": \"^1.1.0\",\n    \"grunt-eslint\": \"^25.0.0\",\n    \"grunt-exec\": \"~3.0.0\",\n    \"grunt-webpack\": \"^6.0.0\",\n    \"grunt-zip\": \"^1.0.0\",\n    \"html-webpack-plugin\": \"^5.6.6\",\n    \"imports-loader\": \"^5.0.0\",\n    \"mini-css-extract-plugin\": \"2.10.1\",\n    \"modify-source-webpack-plugin\": \"^4.1.0\",\n    \"nightwatch\": \"^3.15.0\",\n    \"postcss\": \"^8.5.8\",\n    \"postcss-css-variables\": \"^0.19.0\",\n    \"postcss-import\": \"^16.1.1\",\n    \"postcss-loader\": \"^8.2.1\",\n    \"prompt\": \"^1.3.0\",\n    \"sitemap\": \"^8.0.3\",\n    \"terser\": \"^5.46.1\",\n    \"webpack\": \"^5.105.4\",\n    \"webpack-bundle-analyzer\": \"^4.10.2\",\n    \"webpack-dev-server\": \"5.0.4\",\n    \"webpack-node-externals\": \"^3.0.0\",\n    \"worker-loader\": \"^3.0.8\"\n  },\n  \"dependencies\": {\n    \"@alexaltea/capstone-js\": \"^3.0.5\",\n    \"@astronautlabs/amf\": \"^0.0.6\",\n    \"@blu3r4y/lzma\": \"^2.3.3\",\n    \"@wavesenterprise/crypto-gost-js\": \"^2.1.0-RC1\",\n    \"@xmldom/xmldom\": \"^0.8.11\",\n    \"argon2-browser\": \"^1.18.0\",\n    \"arrive\": \"^2.5.2\",\n    \"assert\": \"^2.1.0\",\n    \"avsc\": \"^5.7.9\",\n    \"bcryptjs\": \"^2.4.3\",\n    \"bignumber.js\": \"^9.3.1\",\n    \"blakejs\": \"^1.2.1\",\n    \"bootstrap\": \"4.6.2\",\n    \"bootstrap-colorpicker\": \"^3.4.0\",\n    \"bootstrap-material-design\": \"^4.1.3\",\n    \"browserify-zlib\": \"^0.2.0\",\n    \"bson\": \"^4.7.2\",\n    \"buffer\": \"^6.0.3\",\n    \"cbor\": \"9.0.2\",\n    \"chi-squared\": \"^1.1.0\",\n    \"codepage\": \"^1.15.0\",\n    \"crypto-api\": \"^0.8.5\",\n    \"crypto-browserify\": \"^3.12.1\",\n    \"crypto-js\": \"^4.2.0\",\n    \"ctph.js\": \"0.0.5\",\n    \"d3\": \"7.9.0\",\n    \"d3-hexbin\": \"^0.2.2\",\n    \"diff\": \"^5.2.2\",\n    \"dompurify\": \"^3.3.3\",\n    \"es6-promisify\": \"^7.0.0\",\n    \"escodegen\": \"^2.1.0\",\n    \"esprima\": \"^4.0.1\",\n    \"events\": \"^3.3.0\",\n    \"exif-parser\": \"^0.1.12\",\n    \"fernet\": \"^0.3.3\",\n    \"file-saver\": \"^2.0.5\",\n    \"flat\": \"^6.0.1\",\n    \"geodesy\": \"1.1.3\",\n    \"handlebars\": \"^4.7.8\",\n    \"hash-wasm\": \"^4.12.0\",\n    \"highlight.js\": \"^11.11.1\",\n    \"ieee754\": \"^1.2.1\",\n    \"jimp\": \"^1.6.0\",\n    \"jq-web\": \"^0.5.1\",\n    \"jquery\": \"3.7.1\",\n    \"js-sha3\": \"^0.9.3\",\n    \"jsesc\": \"^3.1.0\",\n    \"json5\": \"^2.2.3\",\n    \"jsonata\": \"^2.1.0\",\n    \"jsonpath-plus\": \"^10.4.0\",\n    \"jsonwebtoken\": \"9.0.3\",\n    \"jsqr\": \"^1.4.0\",\n    \"jsrsasign\": \"^11.1.1\",\n    \"kbpgp\": \"^2.1.17\",\n    \"libbzip2-wasm\": \"0.0.4\",\n    \"libyara-wasm\": \"^1.2.1\",\n    \"lodash\": \"^4.17.23\",\n    \"loglevel\": \"^1.9.2\",\n    \"loglevel-message-prefix\": \"^3.0.0\",\n    \"lz-string\": \"^1.5.0\",\n    \"lz4js\": \"^0.2.0\",\n    \"markdown-it\": \"^14.1.1\",\n    \"moment\": \"^2.30.1\",\n    \"moment-timezone\": \"^0.6.1\",\n    \"ngeohash\": \"^0.6.3\",\n    \"node-forge\": \"^1.3.3\",\n    \"node-md6\": \"^0.1.0\",\n    \"nodom\": \"^2.4.0\",\n    \"notepack.io\": \"^3.0.1\",\n    \"ntlm\": \"^0.1.3\",\n    \"nwmatcher\": \"^1.4.4\",\n    \"otpauth\": \"9.3.6\",\n    \"path\": \"^0.12.7\",\n    \"popper.js\": \"^1.16.1\",\n    \"process\": \"^0.11.10\",\n    \"protobufjs\": \"^7.5.4\",\n    \"qr-image\": \"^3.2.0\",\n    \"reflect-metadata\": \"^0.2.2\",\n    \"rison\": \"^0.1.1\",\n    \"scryptsy\": \"^2.1.0\",\n    \"snackbarjs\": \"^1.1.0\",\n    \"sortablejs\": \"^1.15.7\",\n    \"split.js\": \"^1.6.5\",\n    \"sql-formatter\": \"^15.6.12\",\n    \"ssdeep.js\": \"0.0.3\",\n    \"stream-browserify\": \"^3.0.0\",\n    \"tesseract.js\": \"^6.0.1\",\n    \"ua-parser-js\": \"^1.0.41\",\n    \"unorm\": \"^1.6.0\",\n    \"url\": \"^0.11.4\",\n    \"utf8\": \"^3.0.0\",\n    \"uuid\": \"^13.0.0\",\n    \"vkbeautify\": \"^0.99.3\",\n    \"xpath\": \"0.0.34\",\n    \"xregexp\": \"^5.1.2\",\n    \"zlibjs\": \"^0.3.1\"\n  },\n  \"scripts\": {\n    \"start\": \"npx grunt dev\",\n    \"build\": \"npx grunt prod\",\n    \"node\": \"npx grunt node\",\n    \"repl\": \"node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs\",\n    \"test\": \"npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs\",\n    \"testnodeconsumer\": \"npx grunt testnodeconsumer\",\n    \"testui\": \"npx grunt testui\",\n    \"testuidev\": \"npx nightwatch --env=dev\",\n    \"lint\": \"npx grunt lint\",\n    \"lint:grammar\": \"cspell ./src\",\n    \"postinstall\": \"npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup\",\n    \"newop\": \"node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs\",\n    \"minor\": \"node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs && npm version minor --git-tag-version=false && echo \\\"Updated to version v$(npm pkg get version | xargs), please create a pull request and once merged use 'npm run tag'\\\"\",\n    \"tag\": \"git tag -s \\\"v$(npm pkg get version | xargs)\\\" -m \\\"$(npm pkg get version | xargs)\\\" && echo \\\"Created v$(npm pkg get version | xargs), now check and push the tag\\\"\",\n    \"getheapsize\": \"node -e 'console.log(`node heap limit = ${require(\\\"v8\\\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'\",\n    \"setheapsize\": \"export NODE_OPTIONS=--max_old_space_size=2048\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n    plugins: [\n        require(\"postcss-import\"),\n        require(\"autoprefixer\"),\n        require(\"postcss-css-variables\")({\n            preserve: true\n        }),\n    ]\n};\n"
  },
  {
    "path": "src/core/Chef.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Dish from \"./Dish.mjs\";\nimport Recipe from \"./Recipe.mjs\";\nimport log from \"loglevel\";\nimport { isWorkerEnvironment } from \"./Utils.mjs\";\n\n/**\n * The main controller for CyberChef.\n */\nclass Chef {\n\n    /**\n     * Chef constructor\n     */\n    constructor() {\n        this.dish = new Dish();\n    }\n\n\n    /**\n     * Runs the recipe over the input.\n     *\n     * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer\n     * @param {Object[]} recipeConfig - The recipe configuration object\n     * @param {Object} [options={}] - The options object storing various user choices\n     * @param {string} [options.returnType] - What type to return the result as\n     *\n     * @returns {Object} response\n     * @returns {string} response.result - The output of the recipe\n     * @returns {string} response.type - The data type of the result\n     * @returns {number} response.progress - The position that we have got to in the recipe\n     * @returns {number} response.duration - The number of ms it took to execute the recipe\n     * @returns {number} response.error - The error object thrown by a failed operation (false if no error)\n    */\n    async bake(input, recipeConfig, options={}) {\n        log.debug(\"Chef baking\");\n        const startTime = Date.now(),\n            recipe      = new Recipe(recipeConfig),\n            containsFc  = recipe.containsFlowControl();\n        let error = false,\n            progress = 0;\n\n        if (containsFc && isWorkerEnvironment()) self.setOption(\"attemptHighlight\", false);\n\n        // Load data\n        const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;\n        this.dish.set(input, type);\n\n        try {\n            progress = await recipe.execute(this.dish, progress);\n        } catch (err) {\n            log.error(err);\n            error = {\n                displayStr: err.displayStr,\n            };\n            progress = err.progress;\n        }\n\n        // Create a raw version of the dish, unpresented\n        const rawDish = this.dish.clone();\n\n        // Present the raw result\n        await recipe.present(this.dish);\n\n        const returnType =\n            this.dish.type === Dish.HTML ? Dish.HTML :\n                options?.returnType ? options.returnType : Dish.ARRAY_BUFFER;\n\n        return {\n            dish: rawDish,\n            result: await this.dish.get(returnType),\n            type: Dish.enumLookup(this.dish.type),\n            progress: progress,\n            duration: Date.now() - startTime,\n            error: error\n        };\n    }\n\n\n    /**\n     * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,\n     * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a\n     * minute, we run a silent bake which will force the browser to load and cache all the relevant\n     * JavaScript code needed to do a real bake.\n     *\n     * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a\n     * long time and the browser has swapped out all its memory.\n     *\n     * The output will not be modified (hence \"silent\" bake).\n     *\n     * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load\n     * the recipe, ingredients and dish.\n     *\n     * @param {Object[]} recipeConfig - The recipe configuration object\n     * @returns {number} The time it took to run the silent bake in milliseconds.\n    */\n    silentBake(recipeConfig) {\n        log.debug(\"Running silent bake\");\n\n        const startTime = Date.now(),\n            recipe = new Recipe(recipeConfig),\n            dish = new Dish();\n\n        try {\n            recipe.execute(dish);\n        } catch (err) {\n            // Suppress all errors\n        }\n        return Date.now() - startTime;\n    }\n\n\n    /**\n     * Calculates highlight offsets if possible.\n     *\n     * @param {Object[]} recipeConfig\n     * @param {string} direction\n     * @param {Object} pos - The position object for the highlight.\n     * @param {number} pos.start - The start offset.\n     * @param {number} pos.end - The end offset.\n     * @returns {Object}\n     */\n    async calculateHighlights(recipeConfig, direction, pos) {\n        const recipe = new Recipe(recipeConfig);\n        const highlights = await recipe.generateHighlightList();\n\n        if (!highlights) return false;\n\n        for (let i = 0; i < highlights.length; i++) {\n            // Remove multiple highlights before processing again\n            pos = [pos[0]];\n\n            const func = direction === \"forward\" ? highlights[i].f : highlights[i].b;\n\n            if (typeof func == \"function\") {\n                try {\n                    pos = func(pos, highlights[i].args);\n                } catch (err) {\n                    // Throw away highlighting errors\n                    pos = [];\n                }\n            }\n        }\n\n        return {\n            pos: pos,\n            direction: direction\n        };\n    }\n\n\n    /**\n     * Translates the dish to a specified type and returns it.\n     *\n     * @param {Dish} dish\n     * @param {string} type\n     * @returns {Dish}\n     */\n    async getDishAs(dish, type) {\n        const newDish = new Dish(dish);\n        return await newDish.get(type);\n    }\n\n    /**\n     * Gets the title of a dish and returns it\n     *\n     * @param {Dish} dish\n     * @param {number} [maxLength=100]\n     * @returns {string}\n     */\n    async getDishTitle(dish, maxLength=100) {\n        const newDish = new Dish(dish);\n        return await newDish.getTitle(maxLength);\n    }\n\n}\n\nexport default Chef;\n"
  },
  {
    "path": "src/core/ChefWorker.js",
    "content": "/**\n * Web Worker to handle communications between the front-end and the core.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Chef from \"./Chef.mjs\";\nimport OperationConfig from \"./config/OperationConfig.json\" assert {type: \"json\"};\nimport OpModules from \"./config/modules/OpModules.mjs\";\nimport loglevelMessagePrefix from \"loglevel-message-prefix\";\n\n\n// Set up Chef instance\nself.chef = new Chef();\n\nself.OpModules = OpModules;\nself.OperationConfig = OperationConfig;\nself.inputNum = -1;\n\n\n// Tell the app that the worker has loaded and is ready to operate\nself.postMessage({\n    action: \"workerLoaded\",\n    data: {}\n});\n\n/**\n * Respond to message from parent thread.\n *\n * inputNum is optional and only used for baking multiple inputs.\n * Defaults to -1 when one isn't sent with the bake message.\n *\n * Messages should have the following format:\n * {\n *     action: \"bake\" | \"silentBake\",\n *     data: {\n *         input: {string},\n *         recipeConfig: {[Object]},\n *         options: {Object},\n *         progress: {number},\n *         step: {boolean},\n *         [inputNum=-1]: {number}\n *     }\n * }\n */\nself.addEventListener(\"message\", function(e) {\n    // Handle message\n    const r = e.data;\n    log.debug(`Receiving command '${r.action}'`);\n\n    switch (r.action) {\n        case \"bake\":\n            bake(r.data);\n            break;\n        case \"silentBake\":\n            silentBake(r.data);\n            break;\n        case \"getDishAs\":\n            getDishAs(r.data);\n            break;\n        case \"getDishTitle\":\n            getDishTitle(r.data);\n            break;\n        case \"docURL\":\n            // Used to set the URL of the current document so that scripts can be\n            // imported into an inline worker.\n            self.docURL = r.data;\n            break;\n        case \"highlight\":\n            calculateHighlights(\n                r.data.recipeConfig,\n                r.data.direction,\n                r.data.pos\n            );\n            break;\n        case \"setLogLevel\":\n            log.setLevel(r.data, false);\n            break;\n        case \"setLogPrefix\":\n            loglevelMessagePrefix(log, {\n                prefixes: [],\n                staticPrefixes: [r.data]\n            });\n            break;\n        default:\n            break;\n    }\n});\n\n\n/**\n * Baking handler\n *\n * @param {Object} data\n */\nasync function bake(data) {\n    // Ensure the relevant modules are loaded\n    self.loadRequiredModules(data.recipeConfig);\n    try {\n        self.inputNum = data.inputNum === undefined ? -1 : data.inputNum;\n        const response = await self.chef.bake(\n            data.input,          // The user's input\n            data.recipeConfig,   // The configuration of the recipe\n            data.options         // Options set by the user\n        );\n\n        const transferable = (response.dish.value instanceof ArrayBuffer) ?\n            [response.dish.value] :\n            undefined;\n\n        self.postMessage({\n            action: \"bakeComplete\",\n            data: Object.assign(response, {\n                id: data.id,\n                inputNum: data.inputNum,\n                bakeId: data.bakeId\n            })\n        }, transferable);\n\n    } catch (err) {\n        self.postMessage({\n            action: \"bakeError\",\n            data: {\n                error: err.message || err,\n                id: data.id,\n                inputNum: data.inputNum\n            }\n        });\n    }\n    self.inputNum = -1;\n}\n\n\n/**\n * Silent baking handler\n */\nfunction silentBake(data) {\n    const duration = self.chef.silentBake(data.recipeConfig);\n\n    self.postMessage({\n        action: \"silentBakeComplete\",\n        data: duration\n    });\n}\n\n\n/**\n * Translates the dish to a given type.\n */\nasync function getDishAs(data) {\n    const value = await self.chef.getDishAs(data.dish, data.type);\n    const transferable = (data.type === \"ArrayBuffer\") ? [value] : undefined;\n    self.postMessage({\n        action: \"dishReturned\",\n        data: {\n            value: value,\n            id: data.id\n        }\n    }, transferable);\n}\n\n\n/**\n * Gets the dish title\n *\n * @param {object} data\n * @param {Dish} data.dish\n * @param {number} data.maxLength\n * @param {number} data.id\n */\nasync function getDishTitle(data) {\n    const title = await self.chef.getDishTitle(data.dish, data.maxLength);\n    self.postMessage({\n        action: \"dishReturned\",\n        data: {\n            value: title,\n            id: data.id\n        }\n    });\n}\n\n\n/**\n * Calculates highlight offsets if possible.\n *\n * @param {Object[]} recipeConfig\n * @param {string} direction\n * @param {Object[]} pos - The position object for the highlight.\n * @param {number} pos.start - The start offset.\n * @param {number} pos.end - The end offset.\n */\nasync function calculateHighlights(recipeConfig, direction, pos) {\n    pos = await self.chef.calculateHighlights(recipeConfig, direction, pos);\n\n    self.postMessage({\n        action: \"highlightsCalculated\",\n        data: pos\n    });\n}\n\n\n/**\n * Checks that all required modules are loaded and loads them if not.\n *\n * @param {Object} recipeConfig\n */\nself.loadRequiredModules = function(recipeConfig) {\n    recipeConfig.forEach(op => {\n        const module = self.OperationConfig[op.op].module;\n\n        if (!(module in OpModules)) {\n            log.info(`Loading ${module} module`);\n            self.sendStatusMessage(`Loading ${module} module`);\n            self.importScripts(`${self.docURL}/modules/${module}.js`); // lgtm [js/client-side-unvalidated-url-redirection]\n            self.sendStatusMessage(\"\");\n        }\n    });\n};\n\n\n/**\n * Send status update to the app.\n *\n * @param {string} msg\n */\nself.sendStatusMessage = function(msg) {\n    self.postMessage({\n        action: \"statusMessage\",\n        data: {\n            message: msg,\n            inputNum: self.inputNum\n        }\n    });\n};\n\n\n/**\n * Send progress update to the app.\n *\n * @param {number} progress\n * @param {number} total\n */\nself.sendProgressMessage = function(progress, total) {\n    self.postMessage({\n        action: \"progressMessage\",\n        data: {\n            progress: progress,\n            total: total,\n            inputNum: self.inputNum\n        }\n    });\n};\n\n\n/**\n * Send an option value update to the app.\n *\n * @param {string} option\n * @param {*} value\n */\nself.setOption = function(option, value) {\n    self.postMessage({\n        action: \"optionUpdate\",\n        data: {\n            option: option,\n            value: value\n        }\n    });\n};\n\n\n/**\n * Send register values back to the app.\n *\n * @param {number} opIndex\n * @param {number} numPrevRegisters\n * @param {string[]} registers\n */\nself.setRegisters = function(opIndex, numPrevRegisters, registers) {\n    self.postMessage({\n        action: \"setRegisters\",\n        data: {\n            opIndex: opIndex,\n            numPrevRegisters: numPrevRegisters,\n            registers: registers\n        }\n    });\n};\n"
  },
  {
    "path": "src/core/Dish.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils, { isNodeEnvironment } from \"./Utils.mjs\";\nimport DishError from \"./errors/DishError.mjs\";\nimport BigNumber from \"bignumber.js\";\nimport { detectFileType } from \"./lib/FileType.mjs\";\nimport log from \"loglevel\";\n\nimport DishByteArray from \"./dishTypes/DishByteArray.mjs\";\nimport DishBigNumber from \"./dishTypes/DishBigNumber.mjs\";\nimport DishFile from \"./dishTypes/DishFile.mjs\";\nimport DishHTML from \"./dishTypes/DishHTML.mjs\";\nimport DishJSON from \"./dishTypes/DishJSON.mjs\";\nimport DishListFile from \"./dishTypes/DishListFile.mjs\";\nimport DishNumber from \"./dishTypes/DishNumber.mjs\";\nimport DishString from \"./dishTypes/DishString.mjs\";\n\n\n/**\n * The data being operated on by each operation.\n */\nclass Dish {\n\n    /**\n     * Dish constructor\n     *\n     * @param {Dish || *} [dishOrInput=null] - A dish to clone OR an object\n     * literal to make into a dish\n     * @param {Enum} [type=null] (optional) - A type to accompany object\n     * literal input\n     */\n    constructor(dishOrInput=null, type = null) {\n        this.value = new ArrayBuffer(0);\n        this.type = Dish.ARRAY_BUFFER;\n\n        // Case: dishOrInput is dish object\n        if (dishOrInput &&\n            Object.prototype.hasOwnProperty.call(dishOrInput, \"value\") &&\n            Object.prototype.hasOwnProperty.call(dishOrInput, \"type\")) {\n            this.set(dishOrInput.value, dishOrInput.type);\n        // input and type defined separately\n        } else if (dishOrInput && type !== null) {\n            this.set(dishOrInput, type);\n        // No type declared, so infer it.\n        } else if (dishOrInput) {\n            const inferredType = Dish.typeEnum(dishOrInput.constructor.name);\n            this.set(dishOrInput, inferredType);\n        }\n    }\n\n\n    /**\n     * Returns the data type enum for the given type string.\n     *\n     * @param {string} typeStr - The name of the data type.\n     * @returns {number} The data type enum value.\n     */\n    static typeEnum(typeStr) {\n        switch (typeStr.toLowerCase()) {\n            case \"bytearray\":\n            case \"byte array\":\n                return Dish.BYTE_ARRAY;\n            case \"string\":\n                return Dish.STRING;\n            case \"number\":\n                return Dish.NUMBER;\n            case \"html\":\n                return Dish.HTML;\n            case \"arraybuffer\":\n            case \"array buffer\":\n                return Dish.ARRAY_BUFFER;\n            case \"bignumber\":\n            case \"big number\":\n                return Dish.BIG_NUMBER;\n            case \"json\":\n            case \"object\": // object constructor name. To allow JSON input in node.\n                return Dish.JSON;\n            case \"file\":\n                return Dish.FILE;\n            case \"list<file>\":\n                return Dish.LIST_FILE;\n            default:\n                throw new DishError(\"Invalid data type string. No matching enum.\");\n        }\n    }\n\n\n    /**\n     * Returns the data type string for the given type enum.\n     *\n     * @param {number} typeEnum - The enum value of the data type.\n     * @returns {string} The data type as a string.\n     */\n    static enumLookup(typeEnum) {\n        switch (typeEnum) {\n            case Dish.BYTE_ARRAY:\n                return \"byteArray\";\n            case Dish.STRING:\n                return \"string\";\n            case Dish.NUMBER:\n                return \"number\";\n            case Dish.HTML:\n                return \"html\";\n            case Dish.ARRAY_BUFFER:\n                return \"ArrayBuffer\";\n            case Dish.BIG_NUMBER:\n                return \"BigNumber\";\n            case Dish.JSON:\n                return \"JSON\";\n            case Dish.FILE:\n                return \"File\";\n            case Dish.LIST_FILE:\n                return \"List<File>\";\n            default:\n                throw new DishError(\"Invalid data type enum. No matching type.\");\n        }\n    }\n\n\n    /**\n     * Returns the value of the data in the type format specified.\n     *\n     * If running in a browser, get is asynchronous.\n     *\n     * @param {number} type - The data type of value, see Dish enums.\n     * @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type\n     */\n    get(type) {\n        if (typeof type === \"string\") {\n            type = Dish.typeEnum(type);\n        }\n\n        if (this.type !== type) {\n\n            // Node environment => _translate is sync\n            if (isNodeEnvironment()) {\n                this._translate(type);\n                return this.value;\n\n            // Browser environment => _translate is async\n            } else {\n                return new Promise((resolve, reject) => {\n                    this._translate(type)\n                        .then(() => {\n                            resolve(this.value);\n                        })\n                        .catch(reject);\n                });\n            }\n        }\n\n        return this.value;\n    }\n\n\n    /**\n     * Sets the data value and type and then validates them.\n     *\n     * @param {*} value\n     *     - The value of the input data.\n     * @param {number} type\n     *     - The data type of value, see Dish enums.\n     */\n    set(value, type) {\n        if (typeof type === \"string\") {\n            type = Dish.typeEnum(type);\n        }\n\n        log.debug(\"Dish type: \" + Dish.enumLookup(type));\n        this.value = value;\n        this.type = type;\n\n        if (!this.valid()) {\n            const sample = Utils.truncate(JSON.stringify(this.value), 25);\n            throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`);\n        }\n    }\n\n    /**\n     * Returns the Dish as the given type, without mutating the original dish.\n     *\n     * If running in a browser, get is asynchronous.\n     *\n     * @Node\n     *\n     * @param {number} type - The data type of value, see Dish enums.\n     * @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type\n     */\n    presentAs(type) {\n        const clone = this.clone();\n        return clone.get(type);\n    }\n\n\n    /**\n     * Detects the MIME type of the current dish\n     * @returns {string}\n     */\n    detectDishType() {\n        const data = new Uint8Array(this.value.slice(0, 2048)),\n            types = detectFileType(data);\n\n        if (!types.length || !types[0].mime || !(types[0].mime === \"text/plain\")) {\n            return null;\n        } else {\n            return types[0].mime;\n        }\n    }\n\n\n    /**\n     * Returns the title of the data up to the specified length\n     *\n     * @param {number} maxLength - The maximum title length\n     * @returns {string}\n     */\n    async getTitle(maxLength) {\n        let title = \"\";\n        let cloned;\n\n        switch (this.type) {\n            case Dish.FILE:\n                title = this.value.name;\n                break;\n            case Dish.LIST_FILE:\n                title = `${this.value.length} file(s)`;\n                break;\n            case Dish.JSON:\n                title = \"application/json\";\n                break;\n            case Dish.NUMBER:\n            case Dish.BIG_NUMBER:\n                title = this.value.toString();\n                break;\n            case Dish.ARRAY_BUFFER:\n            case Dish.BYTE_ARRAY:\n                title = this.detectDishType();\n                if (title !== null) break;\n                // fall through if no mime type was detected\n            default:\n                try {\n                    cloned = this.clone();\n                    cloned.value = cloned.value.slice(0, 256);\n                    title = await cloned.get(Dish.STRING);\n                } catch (err) {\n                    log.error(`${Dish.enumLookup(this.type)} cannot be sliced. ${err}`);\n                }\n        }\n\n        return title.slice(0, maxLength);\n    }\n\n    /**\n     * Validates that the value is the type that has been specified.\n     * May have to disable parts of BYTE_ARRAY validation if it effects performance.\n     *\n     * @returns {boolean} Whether the data is valid or not.\n    */\n    valid() {\n        switch (this.type) {\n            case Dish.BYTE_ARRAY:\n                if (!(this.value instanceof Uint8Array) && !(this.value instanceof Array)) {\n                    return false;\n                }\n\n                // Check that every value is a number between 0 - 255\n                for (let i = 0; i < this.value.length; i++) {\n                    if (typeof this.value[i] !== \"number\" ||\n                        this.value[i] < 0 ||\n                        this.value[i] > 255) {\n                        return false;\n                    }\n                }\n                return true;\n            case Dish.STRING:\n            case Dish.HTML:\n                return typeof this.value === \"string\";\n            case Dish.NUMBER:\n                return typeof this.value === \"number\";\n            case Dish.ARRAY_BUFFER:\n                return this.value instanceof ArrayBuffer;\n            case Dish.BIG_NUMBER:\n                if (BigNumber.isBigNumber(this.value)) return true;\n                /*\n                    If a BigNumber is passed between WebWorkers it is serialised as a JSON\n                    object with a coefficient (c), exponent (e) and sign (s). We detect this\n                    and reinitialise it as a BigNumber object.\n                */\n                if (Object.keys(this.value).sort().equals([\"c\", \"e\", \"s\"])) {\n                    const temp = new BigNumber();\n                    temp.c = this.value.c;\n                    temp.e = this.value.e;\n                    temp.s = this.value.s;\n                    this.value = temp;\n                    return true;\n                }\n                return false;\n            case Dish.JSON:\n                // All values can be serialised in some manner, so we return true in all cases\n                return true;\n            case Dish.FILE:\n                return this.value instanceof File;\n            case Dish.LIST_FILE:\n                return this.value instanceof Array &&\n                    this.value.reduce((acc, curr) => acc && curr instanceof File, true);\n            default:\n                return false;\n        }\n    }\n\n\n    /**\n     * Determines how much space the Dish takes up.\n     * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish,\n     * we measure how many bytes are taken up when the number is written as a string.\n     *\n     * @returns {number}\n    */\n    get size() {\n        switch (this.type) {\n            case Dish.BYTE_ARRAY:\n            case Dish.STRING:\n            case Dish.HTML:\n                return this.value.length;\n            case Dish.NUMBER:\n            case Dish.BIG_NUMBER:\n                return this.value.toString().length;\n            case Dish.ARRAY_BUFFER:\n                return this.value.byteLength;\n            case Dish.JSON:\n                return JSON.stringify(this.value).length;\n            case Dish.FILE:\n                return this.value.size;\n            case Dish.LIST_FILE:\n                return this.value.reduce((acc, curr) => acc + curr.size, 0);\n            default:\n                return -1;\n        }\n    }\n\n\n    /**\n     * Returns a deep clone of the current Dish.\n     *\n     * @returns {Dish}\n     */\n    clone() {\n        const newDish = new Dish();\n\n        switch (this.type) {\n            case Dish.STRING:\n            case Dish.HTML:\n            case Dish.NUMBER:\n            case Dish.BIG_NUMBER:\n                // These data types are immutable so it is acceptable to copy them by reference\n                newDish.set(\n                    this.value,\n                    this.type\n                );\n                break;\n            case Dish.BYTE_ARRAY:\n            case Dish.JSON:\n                // These data types are mutable so they need to be copied by value\n                newDish.set(\n                    JSON.parse(JSON.stringify(this.value)),\n                    this.type\n                );\n                break;\n            case Dish.ARRAY_BUFFER:\n                // Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents\n                newDish.set(\n                    this.value.slice(0),\n                    this.type\n                );\n                break;\n            case Dish.FILE:\n                // A new file can be created by copying over all the values from the original\n                newDish.set(\n                    new File([this.value], this.value.name, {\n                        \"type\": this.value.type,\n                        \"lastModified\": this.value.lastModified\n                    }),\n                    this.type\n                );\n                break;\n            case Dish.LIST_FILE:\n                newDish.set(\n                    this.value.map(f =>\n                        new File([f], f.name, {\n                            \"type\": f.type,\n                            \"lastModified\": f.lastModified\n                        })\n                    ),\n                    this.type\n                );\n                break;\n            default:\n                throw new DishError(\"Cannot clone Dish, unknown type\");\n        }\n\n        return newDish;\n    }\n\n    /**\n     * Translates the data to the given type format.\n     *\n     * If running in the browser, _translate is asynchronous.\n     *\n     * @param {number} toType - The data type of value, see Dish enums.\n     * @returns {Promise || undefined}\n     */\n    _translate(toType) {\n        log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);\n\n        // Node environment => translate is sync\n        if (isNodeEnvironment()) {\n            this._toArrayBuffer();\n            this.type = Dish.ARRAY_BUFFER;\n            this._fromArrayBuffer(toType);\n\n        // Browser environment => translate is async\n        } else {\n            return new Promise((resolve, reject) => {\n                this._toArrayBuffer()\n                    .then(() => this.type = Dish.ARRAY_BUFFER)\n                    .then(() => {\n                        this._fromArrayBuffer(toType);\n                        resolve();\n                    })\n                    .catch(reject);\n            });\n        }\n\n    }\n\n    /**\n     * Convert this.value to an ArrayBuffer\n     *\n     * If running in a browser, _toByteArray is asynchronous.\n     *\n     * @returns {Promise || undefined}\n     */\n    _toArrayBuffer() {\n        // Using 'bind' here to allow this.value to be mutated within translation functions\n        const toByteArrayFuncs = {\n            browser: {\n                [Dish.STRING]:          () => Promise.resolve(DishString.toArrayBuffer.bind(this)()),\n                [Dish.NUMBER]:          () => Promise.resolve(DishNumber.toArrayBuffer.bind(this)()),\n                [Dish.HTML]:            () => Promise.resolve(DishHTML.toArrayBuffer.bind(this)()),\n                [Dish.ARRAY_BUFFER]:    () => Promise.resolve(),\n                [Dish.BIG_NUMBER]:      () => Promise.resolve(DishBigNumber.toArrayBuffer.bind(this)()),\n                [Dish.JSON]:            () => Promise.resolve(DishJSON.toArrayBuffer.bind(this)()),\n                [Dish.FILE]:            () => DishFile.toArrayBuffer.bind(this)(),\n                [Dish.LIST_FILE]:       () => Promise.resolve(DishListFile.toArrayBuffer.bind(this)()),\n                [Dish.BYTE_ARRAY]:      () => Promise.resolve(DishByteArray.toArrayBuffer.bind(this)()),\n            },\n            node: {\n                [Dish.STRING]:          () => DishString.toArrayBuffer.bind(this)(),\n                [Dish.NUMBER]:          () => DishNumber.toArrayBuffer.bind(this)(),\n                [Dish.HTML]:            () => DishHTML.toArrayBuffer.bind(this)(),\n                [Dish.ARRAY_BUFFER]:    () => {},\n                [Dish.BIG_NUMBER]:      () => DishBigNumber.toArrayBuffer.bind(this)(),\n                [Dish.JSON]:            () => DishJSON.toArrayBuffer.bind(this)(),\n                [Dish.FILE]:            () => DishFile.toArrayBuffer.bind(this)(),\n                [Dish.LIST_FILE]:       () => DishListFile.toArrayBuffer.bind(this)(),\n                [Dish.BYTE_ARRAY]:      () => DishByteArray.toArrayBuffer.bind(this)(),\n            }\n        };\n\n        try {\n            return toByteArrayFuncs[isNodeEnvironment() && \"node\" || \"browser\"][this.type]();\n        } catch (err) {\n            throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`);\n        }\n    }\n\n    /**\n     * Convert this.value to the given type from ArrayBuffer\n     *\n     * @param {number} toType - the Dish enum to convert to\n    */\n    _fromArrayBuffer(toType) {\n\n        // Using 'bind' here to allow this.value to be mutated within translation functions\n        const toTypeFunctions = {\n            [Dish.STRING]:          () => DishString.fromArrayBuffer.bind(this)(),\n            [Dish.NUMBER]:          () => DishNumber.fromArrayBuffer.bind(this)(),\n            [Dish.HTML]:            () => DishHTML.fromArrayBuffer.bind(this)(),\n            [Dish.ARRAY_BUFFER]:    () => {},\n            [Dish.BIG_NUMBER]:      () => DishBigNumber.fromArrayBuffer.bind(this)(),\n            [Dish.JSON]:            () => DishJSON.fromArrayBuffer.bind(this)(),\n            [Dish.FILE]:            () => DishFile.fromArrayBuffer.bind(this)(),\n            [Dish.LIST_FILE]:       () => DishListFile.fromArrayBuffer.bind(this)(),\n            [Dish.BYTE_ARRAY]:      () => DishByteArray.fromArrayBuffer.bind(this)(),\n        };\n\n        try {\n            toTypeFunctions[toType]();\n            this.type = toType;\n        } catch (err) {\n            throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`);\n        }\n    }\n\n}\n\n\n/**\n * Dish data type enum for byte arrays.\n * @readonly\n * @enum\n */\nDish.BYTE_ARRAY = 0;\n/**\n * Dish data type enum for strings.\n * @readonly\n * @enum\n */\nDish.STRING = 1;\n/**\n * Dish data type enum for numbers.\n * @readonly\n * @enum\n */\nDish.NUMBER = 2;\n/**\n * Dish data type enum for HTML.\n * @readonly\n * @enum\n */\nDish.HTML = 3;\n/**\n * Dish data type enum for ArrayBuffers.\n * @readonly\n * @enum\n */\nDish.ARRAY_BUFFER = 4;\n/**\n * Dish data type enum for BigNumbers.\n * @readonly\n * @enum\n */\nDish.BIG_NUMBER = 5;\n/**\n * Dish data type enum for JSON.\n * @readonly\n * @enum\n */\nDish.JSON = 6;\n/**\n * Dish data type enum for lists of files.\n * @readonly\n * @enum\n */\nDish.FILE = 7;\n/**\n* Dish data type enum for lists of files.\n* @readonly\n* @enum\n*/\nDish.LIST_FILE = 8;\n\n\nexport default Dish;\n"
  },
  {
    "path": "src/core/Ingredient.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils from \"./Utils.mjs\";\nimport {fromHex} from \"./lib/Hex.mjs\";\n\n/**\n * The arguments to operations.\n */\nclass Ingredient {\n\n    /**\n     * Ingredient constructor\n     *\n     * @param {Object} ingredientConfig\n     */\n    constructor(ingredientConfig) {\n        this.name  = \"\";\n        this.type  = \"\";\n        this._value = null;\n        this.disabled = false;\n        this.hint = \"\";\n        this.rows = 0;\n        this.toggleValues = [];\n        this.target = null;\n        this.defaultIndex = 0;\n        this.maxLength = null;\n        this.min = null;\n        this.max = null;\n        this.step = 1;\n\n        if (ingredientConfig) {\n            this._parseConfig(ingredientConfig);\n        }\n    }\n\n\n    /**\n     * Reads and parses the given config.\n     *\n     * @private\n     * @param {Object} ingredientConfig\n     */\n    _parseConfig(ingredientConfig) {\n        this.name = ingredientConfig.name;\n        this.type = ingredientConfig.type;\n        this.defaultValue = ingredientConfig.value;\n        this.disabled = !!ingredientConfig.disabled;\n        this.hint = ingredientConfig.hint || false;\n        this.rows = ingredientConfig.rows || false;\n        this.toggleValues = ingredientConfig.toggleValues;\n        this.target = typeof ingredientConfig.target !== \"undefined\" ? ingredientConfig.target : null;\n        this.defaultIndex = typeof ingredientConfig.defaultIndex !== \"undefined\" ? ingredientConfig.defaultIndex : 0;\n        this.maxLength = ingredientConfig.maxLength || null;\n        this.min = ingredientConfig.min;\n        this.max = ingredientConfig.max;\n        this.step = ingredientConfig.step;\n    }\n\n\n    /**\n     * Returns the value of the Ingredient as it should be displayed in a recipe config.\n     *\n     * @returns {*}\n     */\n    get config() {\n        return this._value;\n    }\n\n\n    /**\n     * Sets the value of the Ingredient.\n     *\n     * @param {*} value\n     */\n    set value(value) {\n        this._value = Ingredient.prepare(value, this.type);\n    }\n\n\n    /**\n     * Gets the value of the Ingredient.\n     *\n     * @returns {*}\n     */\n    get value() {\n        return this._value;\n    }\n\n\n    /**\n     * Most values will be strings when they are entered. This function converts them to the correct\n     * type.\n     *\n     * @param {*} data\n     * @param {string} type - The name of the data type.\n    */\n    static prepare(data, type) {\n        let number;\n\n        switch (type) {\n            case \"binaryString\":\n            case \"binaryShortString\":\n            case \"editableOption\":\n            case \"editableOptionShort\":\n                return Utils.parseEscapedChars(data);\n            case \"byteArray\":\n                if (typeof data == \"string\") {\n                    data = data.replace(/\\s+/g, \"\");\n                    return fromHex(data);\n                } else {\n                    return data;\n                }\n            case \"number\":\n                if (data === null) return data;\n                number = parseFloat(data);\n                if (isNaN(number)) {\n                    const sample = Utils.truncate(data.toString(), 10);\n                    throw \"Invalid ingredient value. Not a number: \" + sample;\n                }\n                return number;\n            default:\n                return data;\n        }\n    }\n\n}\n\nexport default Ingredient;\n"
  },
  {
    "path": "src/core/Operation.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Dish from \"./Dish.mjs\";\nimport Ingredient from \"./Ingredient.mjs\";\n\n/**\n * The Operation specified by the user to be run.\n */\nclass Operation {\n\n    /**\n     * Operation constructor\n     */\n    constructor() {\n        // Private fields\n        this._inputType       = -1;\n        this._outputType      = -1;\n        this._presentType     = -1;\n        this._breakpoint      = false;\n        this._disabled        = false;\n        this._flowControl     = false;\n        this._manualBake      = false;\n        this._ingList         = [];\n\n        // Public fields\n        this.name             = \"\";\n        this.module           = \"\";\n        this.description      = \"\";\n        this.infoURL          = null;\n    }\n\n\n    /**\n     * Interface for operation runner\n     *\n     * @param {*} input\n     * @param {Object[]} args\n     * @returns {*}\n     */\n    run(input, args) {\n        return input;\n    }\n\n\n    /**\n     * Interface for forward highlighter\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return false;\n    }\n\n\n    /**\n     * Interface for reverse highlighter\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return false;\n    }\n\n\n    /**\n     * Method to be called when displaying the result of an operation in a human-readable\n     * format. This allows operations to return usable data from their run() method and\n     * only format them when this method is called.\n     *\n     * The default action is to return the data unchanged, but child classes can override\n     * this behaviour.\n     *\n     * @param {*} data - The result of the run() function\n     * @param {Object[]} args - The operation's arguments\n     * @returns {*} - A human-readable version of the data\n     */\n    present(data, args) {\n        return data;\n    }\n\n\n    /**\n     * Sets the input type as a Dish enum.\n     *\n     * @param {string} typeStr\n     */\n    set inputType(typeStr) {\n        this._inputType = Dish.typeEnum(typeStr);\n    }\n\n\n    /**\n     * Gets the input type as a readable string.\n     *\n     * @returns {string}\n     */\n    get inputType() {\n        return Dish.enumLookup(this._inputType);\n    }\n\n\n    /**\n     * Sets the output type as a Dish enum.\n     *\n     * @param {string} typeStr\n     */\n    set outputType(typeStr) {\n        this._outputType = Dish.typeEnum(typeStr);\n        if (this._presentType < 0) this._presentType = this._outputType;\n    }\n\n\n    /**\n     * Gets the output type as a readable string.\n     *\n     * @returns {string}\n     */\n    get outputType() {\n        return Dish.enumLookup(this._outputType);\n    }\n\n\n    /**\n     * Sets the presentation type as a Dish enum.\n     *\n     * @param {string} typeStr\n     */\n    set presentType(typeStr) {\n        this._presentType = Dish.typeEnum(typeStr);\n    }\n\n\n    /**\n     * Gets the presentation type as a readable string.\n     *\n     * @returns {string}\n     */\n    get presentType() {\n        return Dish.enumLookup(this._presentType);\n    }\n\n\n    /**\n     * Sets the args for the current operation.\n     *\n     * @param {Object[]} conf\n     */\n    set args(conf) {\n        conf.forEach(arg => {\n            const ingredient = new Ingredient(arg);\n            this.addIngredient(ingredient);\n        });\n    }\n\n\n    /**\n     * Gets the args for the current operation.\n     *\n     * @param {Object[]} conf\n     */\n    get args() {\n        return this._ingList.map(ing => {\n            const conf = {\n                name: ing.name,\n                type: ing.type,\n                value: ing.defaultValue\n            };\n\n            if (ing.toggleValues) conf.toggleValues = ing.toggleValues;\n            if (ing.hint) conf.hint = ing.hint;\n            if (ing.rows) conf.rows = ing.rows;\n            if (ing.disabled) conf.disabled = ing.disabled;\n            if (ing.target) conf.target = ing.target;\n            if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;\n            if (ing.maxLength) conf.maxLength = ing.maxLength;\n            if (typeof ing.min === \"number\") conf.min = ing.min;\n            if (typeof ing.max === \"number\") conf.max = ing.max;\n            if (ing.step) conf.step = ing.step;\n            return conf;\n        });\n    }\n\n\n    /**\n     * Returns the value of the Operation as it should be displayed in a recipe config.\n     *\n     * @returns {Object}\n     */\n    get config() {\n        return {\n            \"op\": this.name,\n            \"args\": this._ingList.map(ing => ing.config)\n        };\n    }\n\n\n    /**\n     * Adds a new Ingredient to this Operation.\n     *\n     * @param {Ingredient} ingredient\n     */\n    addIngredient(ingredient) {\n        this._ingList.push(ingredient);\n    }\n\n\n    /**\n     * Set the Ingredient values for this Operation.\n     *\n     * @param {Object[]} ingValues\n     */\n    set ingValues(ingValues) {\n        ingValues.forEach((val, i) => {\n            this._ingList[i].value = val;\n        });\n    }\n\n\n    /**\n     * Get the Ingredient values for this Operation.\n     *\n     * @returns {Object[]}\n     */\n    get ingValues() {\n        return this._ingList.map(ing => ing.value);\n    }\n\n\n    /**\n     * Set whether this Operation has a breakpoint.\n     *\n     * @param {boolean} value\n     */\n    set breakpoint(value) {\n        this._breakpoint = !!value;\n    }\n\n\n    /**\n     * Returns true if this Operation has a breakpoint set.\n     *\n     * @returns {boolean}\n     */\n    get breakpoint() {\n        return this._breakpoint;\n    }\n\n\n    /**\n     * Set whether this Operation is disabled.\n     *\n     * @param {boolean} value\n     */\n    set disabled(value) {\n        this._disabled = !!value;\n    }\n\n\n    /**\n     * Returns true if this Operation is disabled.\n     *\n     * @returns {boolean}\n     */\n    get disabled() {\n        return this._disabled;\n    }\n\n\n    /**\n     * Returns true if this Operation is a flow control.\n     *\n     * @returns {boolean}\n     */\n    get flowControl() {\n        return this._flowControl;\n    }\n\n\n    /**\n     * Set whether this Operation is a flowcontrol op.\n     *\n     * @param {boolean} value\n     */\n    set flowControl(value) {\n        this._flowControl = !!value;\n    }\n\n\n    /**\n     * Returns true if this Operation should not trigger AutoBake.\n     *\n     * @returns {boolean}\n     */\n    get manualBake() {\n        return this._manualBake;\n    }\n\n\n    /**\n     * Set whether this Operation should trigger AutoBake.\n     *\n     * @param {boolean} value\n     */\n    set manualBake(value) {\n        this._manualBake = !!value;\n    }\n\n}\n\nexport default Operation;\n"
  },
  {
    "path": "src/core/Recipe.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport OperationConfig from \"./config/OperationConfig.json\" assert {type: \"json\"};\nimport OperationError from \"./errors/OperationError.mjs\";\nimport Operation from \"./Operation.mjs\";\nimport DishError from \"./errors/DishError.mjs\";\nimport log from \"loglevel\";\nimport { isWorkerEnvironment } from \"./Utils.mjs\";\n\n// Cache container for modules\nlet modules = null;\n\n/**\n * The Recipe controls a list of Operations and the Dish they operate on.\n */\nclass Recipe  {\n\n    /**\n     * Recipe constructor\n     *\n     * @param {Object} recipeConfig\n     */\n    constructor(recipeConfig) {\n        this.opList = [];\n\n        if (recipeConfig) {\n            this._parseConfig(recipeConfig);\n        }\n    }\n\n\n    /**\n     * Reads and parses the given config.\n     *\n     * @private\n     * @param {Object} recipeConfig\n     */\n    _parseConfig(recipeConfig) {\n        recipeConfig.forEach(c => {\n            this.opList.push({\n                name: c.op,\n                module: OperationConfig[c.op].module,\n                ingValues: c.args,\n                breakpoint: c.breakpoint,\n                disabled: c.disabled || c.op === \"Comment\",\n            });\n        });\n    }\n\n\n    /**\n     * Populate elements of opList with operation instances.\n     * Dynamic import here removes top-level cyclic dependency issue.\n     *\n     * @private\n     */\n    async _hydrateOpList() {\n        if (!modules) {\n            // Using Webpack Magic Comments to force the dynamic import to be included in the main chunk\n            // https://webpack.js.org/api/module-methods/\n            modules = await import(/* webpackMode: \"eager\" */ \"./config/modules/OpModules.mjs\");\n            modules = modules.default;\n        }\n\n        this.opList = this.opList.map(o => {\n            if (o instanceof Operation) {\n                return o;\n            } else {\n                const op = new modules[o.module][o.name]();\n                op.ingValues = o.ingValues;\n                op.breakpoint = o.breakpoint;\n                op.disabled = o.disabled;\n                return op;\n            }\n        });\n    }\n\n\n    /**\n     * Returns the value of the Recipe as it should be displayed in a recipe config.\n     *\n     * @returns {Object[]}\n     */\n    get config() {\n        return this.opList.map(op => ({\n            op: op.name,\n            args: op.ingValues,\n        }));\n    }\n\n\n    /**\n     * Adds a new Operation to this Recipe.\n     *\n     * @param {Operation} operation\n     */\n    addOperation(operation) {\n        this.opList.push(operation);\n    }\n\n\n    /**\n     * Adds a list of Operations to this Recipe.\n     *\n     * @param {Operation[]} operations\n     */\n    addOperations(operations) {\n        operations.forEach(o => {\n            if (o instanceof Operation) {\n                this.opList.push(o);\n            } else {\n                this.opList.push({\n                    name: o.name,\n                    module: o.module,\n                    ingValues: o.args,\n                    breakpoint: o.breakpoint,\n                    disabled: o.disabled,\n                });\n            }\n        });\n    }\n\n\n    /**\n     * Set a breakpoint on a specified Operation.\n     *\n     * @param {number} position - The index of the Operation\n     * @param {boolean} value\n     */\n    setBreakpoint(position, value) {\n        try {\n            this.opList[position].breakpoint = value;\n        } catch (err) {\n            // Ignore index error\n        }\n    }\n\n\n    /**\n     * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow\n     * Control Fork operation.\n     *\n     * @param {number} pos\n     */\n    removeBreaksUpTo(pos) {\n        for (let i = 0; i < pos; i++) {\n            this.opList[i].breakpoint = false;\n        }\n    }\n\n\n    /**\n     * Returns true if there is a Flow Control Operation in this Recipe.\n     *\n     * @returns {boolean}\n     */\n    containsFlowControl() {\n        return this.opList.reduce((acc, curr) => {\n            return acc || curr.flowControl;\n        }, false);\n    }\n\n\n    /**\n     * Executes each operation in the recipe over the given Dish.\n     *\n     * @param {Dish} dish\n     * @param {number} [startFrom=0]\n     *     - The index of the Operation to start executing from\n     * @param {number} [forkState={}]\n     *     - If this is a forked recipe, the state of the recipe up to this point\n     * @returns {number}\n     *     - The final progress through the recipe\n     */\n    async execute(dish, startFrom=0, forkState={}) {\n        let op, input, output,\n            numJumps = 0,\n            numRegisters = forkState.numRegisters || 0;\n\n        if (startFrom === 0) this.lastRunOp = null;\n\n        await this._hydrateOpList();\n\n        log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);\n\n        for (let i = startFrom; i < this.opList.length; i++) {\n            op = this.opList[i];\n            log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`);\n            if (op.disabled) {\n                log.debug(\"Operation is disabled, skipping\");\n                continue;\n            }\n            if (op.breakpoint) {\n                log.debug(\"Pausing at breakpoint\");\n                return i;\n            }\n\n            try {\n                input = await dish.get(op.inputType);\n                log.debug(`Executing operation '${op.name}'`);\n\n                if (isWorkerEnvironment()) {\n                    self.sendStatusMessage(`Baking... (${i+1}/${this.opList.length})`);\n                    self.sendProgressMessage(i + 1, this.opList.length);\n                }\n\n                if (op.flowControl) {\n                    // Package up the current state\n                    let state = {\n                        \"progress\":     i,\n                        \"dish\":         dish,\n                        \"opList\":       this.opList,\n                        \"numJumps\":     numJumps,\n                        \"numRegisters\": numRegisters,\n                        \"forkOffset\":   forkState.forkOffset || 0\n                    };\n\n                    state = await op.run(state);\n                    i = state.progress;\n                    numJumps = state.numJumps;\n                    numRegisters = state.numRegisters;\n                } else {\n                    output = await op.run(input, op.ingValues);\n                    dish.set(output, op.outputType);\n                }\n                this.lastRunOp = op;\n            } catch (err) {\n                log.error(err);\n                // Return expected errors as output\n                if (err instanceof OperationError || err?.type === \"OperationError\") {\n                    // Cannot rely on `err instanceof OperationError` here as extending\n                    // native types is not fully supported yet.\n                    dish.set(err.message, \"string\");\n                    return i;\n                } else if (err instanceof DishError || err?.type === \"DishError\") {\n                    dish.set(err.message, \"string\");\n                    return i;\n                } else {\n                    const e = typeof err == \"string\" ? { message: err } : err;\n\n                    e.progress = i;\n                    if (e.fileName) {\n                        e.displayStr = `${op.name} - ${e.name} in ${e.fileName} on line ` +\n                            `${e.lineNumber}.<br><br>Message: ${e.displayStr || e.message}`;\n                    } else {\n                        e.displayStr = `${op.name} - ${e.displayStr || e.message}`;\n                    }\n\n                    throw e;\n                }\n            }\n        }\n\n        log.debug(\"Recipe complete\");\n        return this.opList.length;\n    }\n\n\n    /**\n     * Present the results of the final operation.\n     *\n     * @param {Dish} dish\n     */\n    async present(dish) {\n        if (!this.lastRunOp) return;\n\n        const output = await this.lastRunOp.present(\n            await dish.get(this.lastRunOp.outputType),\n            this.lastRunOp.ingValues\n        );\n        dish.set(output, this.lastRunOp.presentType);\n    }\n\n\n    /**\n     * Returns the recipe configuration in string format.\n     *\n     * @returns {string}\n     */\n    toString() {\n        return JSON.stringify(this.config);\n    }\n\n\n    /**\n     * Creates a Recipe from a given configuration string.\n     *\n     * @param {string} recipeStr\n     */\n    fromString(recipeStr) {\n        const recipeConfig = JSON.parse(recipeStr);\n        this._parseConfig(recipeConfig);\n    }\n\n\n    /**\n     * Generates a list of all the highlight functions assigned to operations in the recipe, if the\n     * entire recipe supports highlighting.\n     *\n     * @returns {Object[]} highlights\n     * @returns {function} highlights[].f\n     * @returns {function} highlights[].b\n     * @returns {Object[]} highlights[].args\n     */\n    async generateHighlightList() {\n        await this._hydrateOpList();\n        const highlights = [];\n\n        for (let i = 0; i < this.opList.length; i++) {\n            const op = this.opList[i];\n            if (op.disabled) continue;\n\n            // If any breakpoints are set, do not attempt to highlight\n            if (op.breakpoint) return false;\n\n            // If any of the operations do not support highlighting, fail immediately.\n            if (op.highlight === false || op.highlight === undefined) return false;\n\n            highlights.push({\n                f: op.highlight,\n                b: op.highlightReverse,\n                args: op.ingValues\n            });\n        }\n\n        return highlights;\n    }\n\n\n    /**\n     * Determines whether the previous operation has a different presentation type to its normal output.\n     *\n     * @param {number} progress\n     * @returns {boolean}\n     */\n    lastOpPresented(progress) {\n        if (progress < 1) return false;\n        return this.opList[progress-1].presentType !== this.opList[progress-1].outputType;\n    }\n\n}\n\nexport default Recipe;\n"
  },
  {
    "path": "src/core/Utils.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n// loglevel import required for Node API\nimport log from \"loglevel\";\nimport utf8 from \"utf8\";\nimport {fromBase64, toBase64} from \"./lib/Base64.mjs\";\nimport {fromHex} from \"./lib/Hex.mjs\";\nimport {fromDecimal} from \"./lib/Decimal.mjs\";\nimport {fromBinary} from \"./lib/Binary.mjs\";\n\n/**\n * Utility functions for use in operations, the core framework and the stage.\n */\nclass Utils {\n\n    /**\n     * Translates an ordinal into a character.\n     *\n     * @param {number} o\n     * @returns {char}\n     *\n     * @example\n     * // returns 'a'\n     * Utils.chr(97);\n     */\n    static chr(o) {\n        // Detect astral symbols\n        // Thanks to @mathiasbynens for this solution\n        // https://mathiasbynens.be/notes/javascript-unicode\n        if (o > 0xffff) {\n            o -= 0x10000;\n            const high = String.fromCharCode(o >>> 10 & 0x3ff | 0xd800);\n            o = 0xdc00 | o & 0x3ff;\n            return high + String.fromCharCode(o);\n        }\n\n        return String.fromCharCode(o);\n    }\n\n\n    /**\n     * Translates a character into an ordinal.\n     *\n     * @param {char} c\n     * @returns {number}\n     *\n     * @example\n     * // returns 97\n     * Utils.ord('a');\n     */\n    static ord(c) {\n        // Detect astral symbols\n        // Thanks to @mathiasbynens for this solution\n        // https://mathiasbynens.be/notes/javascript-unicode\n        if (c.length === 2) {\n            const high = c.charCodeAt(0);\n            const low = c.charCodeAt(1);\n            if (high >= 0xd800 && high < 0xdc00 &&\n                low >= 0xdc00 && low < 0xe000) {\n                return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000;\n            }\n        }\n\n        return c.charCodeAt(0);\n    }\n\n\n    /**\n     * Adds trailing bytes to a byteArray.\n     *\n     * @author tlwr [toby@toby.codes]\n     *\n     * @param {byteArray} arr - byteArray to add trailing bytes to.\n     * @param {number} numBytes - Maximum width of the array.\n     * @param {Integer} [padByte=0] - The byte to pad with.\n     * @returns {byteArray}\n     *\n     * @example\n     * // returns [\"a\", 0, 0, 0]\n     * Utils.padBytesRight(\"a\", 4);\n     *\n     * // returns [\"a\", 1, 1, 1]\n     * Utils.padBytesRight(\"a\", 4, 1);\n     *\n     * // returns [\"t\", \"e\", \"s\", \"t\", 0, 0, 0, 0]\n     * Utils.padBytesRight(\"test\", 8);\n     *\n     * // returns [\"t\", \"e\", \"s\", \"t\", 1, 1, 1, 1]\n     * Utils.padBytesRight(\"test\", 8, 1);\n     */\n    static padBytesRight(arr, numBytes, padByte=0) {\n        const paddedBytes = new Array(numBytes);\n        paddedBytes.fill(padByte);\n\n        [...arr].forEach((b, i) => {\n            paddedBytes[i] = b;\n        });\n\n        return paddedBytes;\n    }\n\n\n    /**\n     * Truncates a long string to max length and adds suffix.\n     *\n     * @param {string} str - String to truncate\n     * @param {number} max - Maximum length of the final string\n     * @param {string} [suffix='...'] - The string to add to the end of the final string\n     * @returns {string}\n     *\n     * @example\n     * // returns \"A long...\"\n     * Utils.truncate(\"A long string\", 9);\n     *\n     * // returns \"A long s-\"\n     * Utils.truncate(\"A long string\", 9, \"-\");\n     */\n    static truncate(str, max, suffix=\"...\") {\n        if (str.length > max) {\n            str = str.slice(0, max - suffix.length) + suffix;\n        }\n        return str;\n    }\n\n\n    /**\n     * Converts a character or number to its hex representation.\n     *\n     * @param {char|number} c\n     * @param {number} [length=2] - The width of the resulting hex number.\n     * @returns {string}\n     *\n     * @example\n     * // returns \"6e\"\n     * Utils.hex(\"n\");\n     *\n     * // returns \"6e\"\n     * Utils.hex(110);\n     */\n    static hex(c, length=2) {\n        c = typeof c == \"string\" ? Utils.ord(c) : c;\n        return c.toString(16).padStart(length, \"0\");\n    }\n\n\n    /**\n     * Converts a character or number to its binary representation.\n     *\n     * @param {char|number} c\n     * @param {number} [length=8] - The width of the resulting binary number.\n     * @returns {string}\n     *\n     * @example\n     * // returns \"01101110\"\n     * Utils.bin(\"n\");\n     *\n     * // returns \"01101110\"\n     * Utils.bin(110);\n     */\n    static bin(c, length=8) {\n        c = typeof c == \"string\" ? Utils.ord(c) : c;\n        return c.toString(2).padStart(length, \"0\");\n    }\n\n\n    /**\n     * Returns a string with all non-printable chars as dots, optionally preserving whitespace.\n     *\n     * @param {string} str - The input string to display.\n     * @param {boolean} [preserveWs=false] - Whether or not to print whitespace.\n     * @param {boolean} [onlyAscii=false] - Whether or not to replace non ASCII characters.\n     * @returns {string}\n     */\n    static printable(str, preserveWs=false, onlyAscii=false) {\n        if (onlyAscii) {\n            return str.replace(/[^\\x20-\\x7e]/g, \".\");\n        }\n\n        // eslint-disable-next-line no-misleading-character-class\n        const re = /[\\0-\\x08\\x0B-\\x0C\\x0E-\\x1F\\x7F-\\x9F\\xAD\\u0378\\u0379\\u037F-\\u0383\\u038B\\u038D\\u03A2\\u0528-\\u0530\\u0557\\u0558\\u0560\\u0588\\u058B-\\u058E\\u0590\\u05C8-\\u05CF\\u05EB-\\u05EF\\u05F5-\\u0605\\u061C\\u061D\\u06DD\\u070E\\u070F\\u074B\\u074C\\u07B2-\\u07BF\\u07FB-\\u07FF\\u082E\\u082F\\u083F\\u085C\\u085D\\u085F-\\u089F\\u08A1\\u08AD-\\u08E3\\u08FF\\u0978\\u0980\\u0984\\u098D\\u098E\\u0991\\u0992\\u09A9\\u09B1\\u09B3-\\u09B5\\u09BA\\u09BB\\u09C5\\u09C6\\u09C9\\u09CA\\u09CF-\\u09D6\\u09D8-\\u09DB\\u09DE\\u09E4\\u09E5\\u09FC-\\u0A00\\u0A04\\u0A0B-\\u0A0E\\u0A11\\u0A12\\u0A29\\u0A31\\u0A34\\u0A37\\u0A3A\\u0A3B\\u0A3D\\u0A43-\\u0A46\\u0A49\\u0A4A\\u0A4E-\\u0A50\\u0A52-\\u0A58\\u0A5D\\u0A5F-\\u0A65\\u0A76-\\u0A80\\u0A84\\u0A8E\\u0A92\\u0AA9\\u0AB1\\u0AB4\\u0ABA\\u0ABB\\u0AC6\\u0ACA\\u0ACE\\u0ACF\\u0AD1-\\u0ADF\\u0AE4\\u0AE5\\u0AF2-\\u0B00\\u0B04\\u0B0D\\u0B0E\\u0B11\\u0B12\\u0B29\\u0B31\\u0B34\\u0B3A\\u0B3B\\u0B45\\u0B46\\u0B49\\u0B4A\\u0B4E-\\u0B55\\u0B58-\\u0B5B\\u0B5E\\u0B64\\u0B65\\u0B78-\\u0B81\\u0B84\\u0B8B-\\u0B8D\\u0B91\\u0B96-\\u0B98\\u0B9B\\u0B9D\\u0BA0-\\u0BA2\\u0BA5-\\u0BA7\\u0BAB-\\u0BAD\\u0BBA-\\u0BBD\\u0BC3-\\u0BC5\\u0BC9\\u0BCE\\u0BCF\\u0BD1-\\u0BD6\\u0BD8-\\u0BE5\\u0BFB-\\u0C00\\u0C04\\u0C0D\\u0C11\\u0C29\\u0C34\\u0C3A-\\u0C3C\\u0C45\\u0C49\\u0C4E-\\u0C54\\u0C57\\u0C5A-\\u0C5F\\u0C64\\u0C65\\u0C70-\\u0C77\\u0C80\\u0C81\\u0C84\\u0C8D\\u0C91\\u0CA9\\u0CB4\\u0CBA\\u0CBB\\u0CC5\\u0CC9\\u0CCE-\\u0CD4\\u0CD7-\\u0CDD\\u0CDF\\u0CE4\\u0CE5\\u0CF0\\u0CF3-\\u0D01\\u0D04\\u0D0D\\u0D11\\u0D3B\\u0D3C\\u0D45\\u0D49\\u0D4F-\\u0D56\\u0D58-\\u0D5F\\u0D64\\u0D65\\u0D76-\\u0D78\\u0D80\\u0D81\\u0D84\\u0D97-\\u0D99\\u0DB2\\u0DBC\\u0DBE\\u0DBF\\u0DC7-\\u0DC9\\u0DCB-\\u0DCE\\u0DD5\\u0DD7\\u0DE0-\\u0DF1\\u0DF5-\\u0E00\\u0E3B-\\u0E3E\\u0E5C-\\u0E80\\u0E83\\u0E85\\u0E86\\u0E89\\u0E8B\\u0E8C\\u0E8E-\\u0E93\\u0E98\\u0EA0\\u0EA4\\u0EA6\\u0EA8\\u0EA9\\u0EAC\\u0EBA\\u0EBE\\u0EBF\\u0EC5\\u0EC7\\u0ECE\\u0ECF\\u0EDA\\u0EDB\\u0EE0-\\u0EFF\\u0F48\\u0F6D-\\u0F70\\u0F98\\u0FBD\\u0FCD\\u0FDB-\\u0FFF\\u10C6\\u10C8-\\u10CC\\u10CE\\u10CF\\u1249\\u124E\\u124F\\u1257\\u1259\\u125E\\u125F\\u1289\\u128E\\u128F\\u12B1\\u12B6\\u12B7\\u12BF\\u12C1\\u12C6\\u12C7\\u12D7\\u1311\\u1316\\u1317\\u135B\\u135C\\u137D-\\u137F\\u139A-\\u139F\\u13F5-\\u13FF\\u169D-\\u169F\\u16F1-\\u16FF\\u170D\\u1715-\\u171F\\u1737-\\u173F\\u1754-\\u175F\\u176D\\u1771\\u1774-\\u177F\\u17DE\\u17DF\\u17EA-\\u17EF\\u17FA-\\u17FF\\u180F\\u181A-\\u181F\\u1878-\\u187F\\u18AB-\\u18AF\\u18F6-\\u18FF\\u191D-\\u191F\\u192C-\\u192F\\u193C-\\u193F\\u1941-\\u1943\\u196E\\u196F\\u1975-\\u197F\\u19AC-\\u19AF\\u19CA-\\u19CF\\u19DB-\\u19DD\\u1A1C\\u1A1D\\u1A5F\\u1A7D\\u1A7E\\u1A8A-\\u1A8F\\u1A9A-\\u1A9F\\u1AAE-\\u1AFF\\u1B4C-\\u1B4F\\u1B7D-\\u1B7F\\u1BF4-\\u1BFB\\u1C38-\\u1C3A\\u1C4A-\\u1C4C\\u1C80-\\u1CBF\\u1CC8-\\u1CCF\\u1CF7-\\u1CFF\\u1DE7-\\u1DFB\\u1F16\\u1F17\\u1F1E\\u1F1F\\u1F46\\u1F47\\u1F4E\\u1F4F\\u1F58\\u1F5A\\u1F5C\\u1F5E\\u1F7E\\u1F7F\\u1FB5\\u1FC5\\u1FD4\\u1FD5\\u1FDC\\u1FF0\\u1FF1\\u1FF5\\u1FFF\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\u2072\\u2073\\u208F\\u209D-\\u209F\\u20BB-\\u20CF\\u20F1-\\u20FF\\u218A-\\u218F\\u23F4-\\u23FF\\u2427-\\u243F\\u244B-\\u245F\\u2700\\u2B4D-\\u2B4F\\u2B5A-\\u2BFF\\u2C2F\\u2C5F\\u2CF4-\\u2CF8\\u2D26\\u2D28-\\u2D2C\\u2D2E\\u2D2F\\u2D68-\\u2D6E\\u2D71-\\u2D7E\\u2D97-\\u2D9F\\u2DA7\\u2DAF\\u2DB7\\u2DBF\\u2DC7\\u2DCF\\u2DD7\\u2DDF\\u2E3C-\\u2E7F\\u2E9A\\u2EF4-\\u2EFF\\u2FD6-\\u2FEF\\u2FFC-\\u2FFF\\u3040\\u3097\\u3098\\u3100-\\u3104\\u312E-\\u3130\\u318F\\u31BB-\\u31BF\\u31E4-\\u31EF\\u321F\\u32FF\\u4DB6-\\u4DBF\\u9FCD-\\u9FFF\\uA48D-\\uA48F\\uA4C7-\\uA4CF\\uA62C-\\uA63F\\uA698-\\uA69E\\uA6F8-\\uA6FF\\uA78F\\uA794-\\uA79F\\uA7AB-\\uA7F7\\uA82C-\\uA82F\\uA83A-\\uA83F\\uA878-\\uA87F\\uA8C5-\\uA8CD\\uA8DA-\\uA8DF\\uA8FC-\\uA8FF\\uA954-\\uA95E\\uA97D-\\uA97F\\uA9CE\\uA9DA-\\uA9DD\\uA9E0-\\uA9FF\\uAA37-\\uAA3F\\uAA4E\\uAA4F\\uAA5A\\uAA5B\\uAA7C-\\uAA7F\\uAAC3-\\uAADA\\uAAF7-\\uAB00\\uAB07\\uAB08\\uAB0F\\uAB10\\uAB17-\\uAB1F\\uAB27\\uAB2F-\\uABBF\\uABEE\\uABEF\\uABFA-\\uABFF\\uD7A4-\\uD7AF\\uD7C7-\\uD7CA\\uD7FC-\\uD7FF\\uE000-\\uF8FF\\uFA6E\\uFA6F\\uFADA-\\uFAFF\\uFB07-\\uFB12\\uFB18-\\uFB1C\\uFB37\\uFB3D\\uFB3F\\uFB42\\uFB45\\uFBC2-\\uFBD2\\uFD40-\\uFD4F\\uFD90\\uFD91\\uFDC8-\\uFDEF\\uFDFE\\uFDFF\\uFE1A-\\uFE1F\\uFE27-\\uFE2F\\uFE53\\uFE67\\uFE6C-\\uFE6F\\uFE75\\uFEFD-\\uFF00\\uFFBF-\\uFFC1\\uFFC8\\uFFC9\\uFFD0\\uFFD1\\uFFD8\\uFFD9\\uFFDD-\\uFFDF\\uFFE7\\uFFEF-\\uFFFB\\uFFFE\\uFFFF]/g;\n        const wsRe = /[\\x09-\\x10\\u2028\\u2029]/g;\n\n        str = str.replace(re, \".\");\n        if (!preserveWs) str = str.replace(wsRe, \".\");\n        return str;\n    }\n\n\n    /**\n     * Returns a string with whitespace represented as special characters from the\n     * Unicode Private Use Area, which CyberChef will display as control characters.\n     * Private Use Area characters are in the range U+E000..U+F8FF.\n     * https://en.wikipedia.org/wiki/Private_Use_Areas\n     * @param {string} str\n     * @returns {string}\n     */\n    static escapeWhitespace(str) {\n        return str.replace(/[\\x09-\\x10]/g, function(c) {\n            return String.fromCharCode(0xe000 + c.charCodeAt(0));\n        });\n    }\n\n\n    /**\n     * Parse a string entered by a user and replace escaped chars with the bytes they represent.\n     *\n     * @param {string} str\n     * @returns {string}\n     *\n     * @example\n     * // returns \"\\x00\"\n     * Utils.parseEscapedChars(\"\\\\x00\");\n     *\n     * // returns \"\\n\"\n     * Utils.parseEscapedChars(\"\\\\n\");\n     */\n    static parseEscapedChars(str) {\n        return str.replace(/\\\\([abfnrtv'\"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\\da-fA-F]{2}|u[\\da-fA-F]{4}|u\\{[\\da-fA-F]{1,6}\\}|\\\\)/g, function(m, a) {\n            switch (a[0]) {\n                case \"\\\\\":\n                    return \"\\\\\";\n                case \"0\":\n                case \"1\":\n                case \"2\":\n                case \"3\":\n                case \"4\":\n                case \"5\":\n                case \"6\":\n                case \"7\":\n                    return String.fromCharCode(parseInt(a, 8));\n                case \"a\":\n                    return String.fromCharCode(7);\n                case \"b\":\n                    return \"\\b\";\n                case \"t\":\n                    return \"\\t\";\n                case \"n\":\n                    return \"\\n\";\n                case \"v\":\n                    return \"\\v\";\n                case \"f\":\n                    return \"\\f\";\n                case \"r\":\n                    return \"\\r\";\n                case '\"':\n                    return '\"';\n                case \"'\":\n                    return \"'\";\n                case \"x\":\n                    return String.fromCharCode(parseInt(a.substr(1), 16));\n                case \"u\":\n                    if (a[1] === \"{\")\n                        return String.fromCodePoint(parseInt(a.slice(2, -1), 16));\n                    else\n                        return String.fromCharCode(parseInt(a.substr(1), 16));\n            }\n        });\n    }\n\n\n    /**\n     * Escape a string containing regex control characters so that it can be safely\n     * used in a regex without causing unintended behaviours.\n     *\n     * @param {string} str\n     * @returns {string}\n     *\n     * @example\n     * // returns \"\\[example\\]\"\n     * Utils.escapeRegex(\"[example]\");\n     */\n    static escapeRegex(str) {\n        return str.replace(/([.*+?^=!:${}()|[\\]/\\\\])/g, \"\\\\$1\");\n    }\n\n\n    /**\n     * Expand an alphabet range string into a list of the characters in that range.\n     *\n     * @param {string} alphStr\n     * @returns {char[]}\n     *\n     * @example\n     * // returns [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"]\n     * Utils.expandAlphRange(\"0-9\");\n     *\n     * // returns [\"a\", \"b\", \"c\", \"d\", \"0\", \"1\", \"2\", \"3\", \"+\", \"/\"]\n     * Utils.expandAlphRange(\"a-d0-3+/\");\n     *\n     * // returns [\"a\", \"b\", \"c\", \"d\", \"0\", \"-\", \"3\"]\n     * Utils.expandAlphRange(\"a-d0\\\\-3\")\n     */\n    static expandAlphRange(alphStr) {\n        const alphArr = [];\n\n        for (let i = 0; i < alphStr.length; i++) {\n            if (i < alphStr.length - 2 &&\n                alphStr[i+1] === \"-\" &&\n                alphStr[i] !== \"\\\\\") {\n                const start = Utils.ord(alphStr[i]),\n                    end = Utils.ord(alphStr[i+2]);\n\n                for (let j = start; j <= end; j++) {\n                    alphArr.push(Utils.chr(j));\n                }\n                i += 2;\n            } else if (i < alphStr.length - 2 &&\n                alphStr[i] === \"\\\\\" &&\n                alphStr[i+1] === \"-\") {\n                alphArr.push(\"-\");\n                i++;\n            } else {\n                alphArr.push(alphStr[i]);\n            }\n        }\n        return alphArr;\n    }\n\n\n    /**\n     * Coverts data of varying types to a byteArray.\n     * Accepts hex, Base64, UTF8 and Latin1 strings.\n     *\n     * @param {string} str\n     * @param {string} type - One of \"Binary\", \"Hex\", \"Decimal\", \"Base64\", \"UTF8\" or \"Latin1\"\n     * @returns {byteArray}\n     *\n     * @example\n     * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]\n     * Utils.convertToByteArray(\"Привет\", \"utf8\");\n     *\n     * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]\n     * Utils.convertToByteArray(\"d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5\", \"hex\");\n     *\n     * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]\n     * Utils.convertToByteArray(\"0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1\", \"base64\");\n     */\n    static convertToByteArray(str, type) {\n        switch (type.toLowerCase()) {\n            case \"binary\":\n                return fromBinary(str);\n            case \"hex\":\n                return fromHex(str);\n            case \"decimal\":\n                return fromDecimal(str);\n            case \"base64\":\n                return fromBase64(str, null, \"byteArray\");\n            case \"utf8\":\n                return Utils.strToUtf8ByteArray(str);\n            case \"latin1\":\n            default:\n                return Utils.strToByteArray(str);\n        }\n    }\n\n\n    /**\n     * Coverts data of varying types to a byte string.\n     * Accepts hex, Base64, UTF8 and Latin1 strings.\n     *\n     * @param {string} str\n     * @param {string} type - One of \"Binary\", \"Hex\", \"Decimal\", \"Base64\", \"UTF8\" or \"Latin1\"\n     * @returns {string}\n     *\n     * @example\n     * // returns \"ÐÑÐ¸Ð²ÐµÑ\"\n     * Utils.convertToByteString(\"Привет\", \"utf8\");\n     *\n     * // returns \"ÐÐ´ÑÐ°Ð²ÑÑÐ²ÑÐ¹ÑÐµ\"\n     * Utils.convertToByteString(\"d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5\", \"hex\");\n     *\n     * // returns \"ÐÐ´ÑÐ°Ð²ÑÑÐ²ÑÐ¹ÑÐµ\"\n     * Utils.convertToByteString(\"0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1\", \"base64\");\n     */\n    static convertToByteString(str, type) {\n        switch (type.toLowerCase()) {\n            case \"binary\":\n                return Utils.byteArrayToChars(fromBinary(str));\n            case \"hex\":\n                return Utils.byteArrayToChars(fromHex(str));\n            case \"decimal\":\n                return Utils.byteArrayToChars(fromDecimal(str));\n            case \"base64\":\n                return Utils.byteArrayToChars(fromBase64(str, null, \"byteArray\"));\n            case \"utf8\":\n                return utf8.encode(str);\n            case \"latin1\":\n            default:\n                return str;\n        }\n    }\n\n\n    /**\n     * Converts a byte array to an integer.\n     *\n     * @param {byteArray} byteArray\n     * @param {string} byteorder - \"little\" or \"big\"\n     * @returns {integer}\n     *\n     * @example\n     * // returns 67305985\n     * Utils.byteArrayToInt([1, 2, 3, 4], \"little\");\n     *\n     * // returns 16909060\n     * Utils.byteArrayToInt([1, 2, 3, 4], \"big\");\n     */\n    static byteArrayToInt(byteArray, byteorder) {\n        let value = 0;\n        if (byteorder === \"big\") {\n            for (let i = 0; i < byteArray.length; i++) {\n                value = (value * 256) + byteArray[i];\n            }\n        } else {\n            for (let i = byteArray.length - 1; i >= 0; i--) {\n                value = (value * 256) + byteArray[i];\n            }\n        }\n        return value;\n    }\n\n\n    /**\n     * Converts an integer to a byte array of {length} bytes.\n     *\n     * @param {integer} value\n     * @param {integer} length\n     * @param {string} byteorder - \"little\" or \"big\"\n     * @returns {byteArray}\n     *\n     * @example\n     * // returns [5, 255, 109, 1]\n     * Utils.intToByteArray(23985925, 4, \"little\");\n     *\n     * // returns [1, 109, 255, 5]\n     * Utils.intToByteArray(23985925, 4, \"big\");\n     *\n     * // returns [0, 0, 0, 0, 1, 109, 255, 5]\n     * Utils.intToByteArray(23985925, 8, \"big\");\n     */\n    static intToByteArray(value, length, byteorder) {\n        const arr = new Array(length);\n        if (byteorder === \"little\") {\n            for (let i = 0; i < length; i++) {\n                arr[i] = value & 0xFF;\n                value = value >>> 8;\n            }\n        } else {\n            for (let i = length - 1; i >= 0; i--) {\n                arr[i] = value & 0xFF;\n                value = value >>> 8;\n            }\n        }\n        return arr;\n    }\n\n\n    /**\n     * Converts a string to an ArrayBuffer.\n     * Treats the string as UTF-8 if any values are over 255.\n     *\n     * @param {string} str\n     * @returns {ArrayBuffer}\n     *\n     * @example\n     * // returns [72,101,108,108,111]\n     * Utils.strToArrayBuffer(\"Hello\");\n     *\n     * // returns [228,189,160,229,165,189]\n     * Utils.strToArrayBuffer(\"你好\");\n     */\n    static strToArrayBuffer(str) {\n        log.debug(`Converting string[${str?.length}] to array buffer`);\n        if (!str) return new ArrayBuffer;\n\n        const arr = new Uint8Array(str.length);\n        let i = str.length, b;\n        while (i--) {\n            b = str.charCodeAt(i);\n            arr[i] = b;\n            // If any of the bytes are over 255, read as UTF-8\n            if (b > 255) return Utils.strToUtf8ArrayBuffer(str);\n        }\n        return arr.buffer;\n    }\n\n\n    /**\n     * Converts a string to a UTF-8 ArrayBuffer.\n     *\n     * @param {string} str\n     * @returns {ArrayBuffer}\n     *\n     * @example\n     * // returns [72,101,108,108,111]\n     * Utils.strToUtf8ArrayBuffer(\"Hello\");\n     *\n     * // returns [228,189,160,229,165,189]\n     * Utils.strToUtf8ArrayBuffer(\"你好\");\n     */\n    static strToUtf8ArrayBuffer(str) {\n        log.debug(`Converting string[${str?.length}] to UTF8 array buffer`);\n        if (!str) return new ArrayBuffer;\n\n        const buffer = new TextEncoder(\"utf-8\").encode(str);\n\n        if (str.length !== buffer.length) {\n            if (isWorkerEnvironment() && self && typeof self.setOption === \"function\") {\n                self.setOption(\"attemptHighlight\", false);\n            } else if (isWebEnvironment()) {\n                window.app.options.attemptHighlight = false;\n            }\n        }\n\n        return buffer.buffer;\n    }\n\n\n    /**\n     * Converts a string to a byte array.\n     * Treats the string as UTF-8 if any values are over 255.\n     *\n     * @param {string} str\n     * @returns {byteArray}\n     *\n     * @example\n     * // returns [72,101,108,108,111]\n     * Utils.strToByteArray(\"Hello\");\n     *\n     * // returns [228,189,160,229,165,189]\n     * Utils.strToByteArray(\"你好\");\n     */\n    static strToByteArray(str) {\n        log.debug(`Converting string[${str?.length}] to byte array`);\n        if (!str) return [];\n        const byteArray = new Array(str.length);\n        let i = str.length, b;\n        while (i--) {\n            b = str.charCodeAt(i);\n            byteArray[i] = b;\n            // If any of the bytes are over 255, read as UTF-8\n            if (b > 255) return Utils.strToUtf8ByteArray(str);\n        }\n        return byteArray;\n    }\n\n\n    /**\n     * Converts a string to a UTF-8 byte array.\n     *\n     * @param {string} str\n     * @returns {byteArray}\n     *\n     * @example\n     * // returns [72,101,108,108,111]\n     * Utils.strToUtf8ByteArray(\"Hello\");\n     *\n     * // returns [228,189,160,229,165,189]\n     * Utils.strToUtf8ByteArray(\"你好\");\n     */\n    static strToUtf8ByteArray(str) {\n        log.debug(`Converting string[${str?.length}] to UTF8 byte array`);\n        if (!str) return [];\n        const utf8Str = utf8.encode(str);\n\n        if (str.length !== utf8Str.length) {\n            if (isWorkerEnvironment()) {\n                self.setOption(\"attemptHighlight\", false);\n            } else if (isWebEnvironment()) {\n                window.app.options.attemptHighlight = false;\n            }\n        }\n\n        return Utils.strToByteArray(utf8Str);\n    }\n\n\n    /**\n     * Converts a string to a unicode charcode array\n     *\n     * @param {string} str\n     * @returns {byteArray}\n     *\n     * @example\n     * // returns [72,101,108,108,111]\n     * Utils.strToCharcode(\"Hello\");\n     *\n     * // returns [20320,22909]\n     * Utils.strToCharcode(\"你好\");\n     */\n    static strToCharcode(str) {\n        log.debug(`Converting string[${str?.length}] to charcode`);\n        if (!str) return [];\n        const charcode = [];\n\n        for (let i = 0; i < str.length; i++) {\n            let ord = str.charCodeAt(i);\n\n            // Detect and merge astral symbols\n            if (i < str.length - 1 && ord >= 0xd800 && ord < 0xdc00) {\n                const low = str[i + 1].charCodeAt(0);\n                if (low >= 0xdc00 && low < 0xe000) {\n                    ord = Utils.ord(str[i] + str[++i]);\n                }\n            }\n\n            charcode.push(ord);\n        }\n\n        return charcode;\n    }\n\n\n    /**\n     * Attempts to convert a byte array to a UTF-8 string.\n     *\n     * @param {byteArray|Uint8Array} byteArray\n     * @returns {string}\n     *\n     * @example\n     * // returns \"Hello\"\n     * Utils.byteArrayToUtf8([72,101,108,108,111]);\n     *\n     * // returns \"你好\"\n     * Utils.byteArrayToUtf8([228,189,160,229,165,189]);\n     */\n    static byteArrayToUtf8(byteArray) {\n        log.debug(`Converting byte array[${byteArray?.length}] to UTF8`);\n        if (!byteArray || !byteArray.length) return \"\";\n        if (!(byteArray instanceof Uint8Array))\n            byteArray = new Uint8Array(byteArray);\n\n        try {\n            const str = new TextDecoder(\"utf-8\", {fatal: true}).decode(byteArray);\n\n            if (str.length !== byteArray.length) {\n                if (isWorkerEnvironment()) {\n                    self.setOption(\"attemptHighlight\", false);\n                } else if (isWebEnvironment()) {\n                    window.app.options.attemptHighlight = false;\n                }\n            }\n\n            return str;\n        } catch (err) {\n            // If it fails, treat it as ANSI\n            return Utils.byteArrayToChars(byteArray);\n        }\n    }\n\n\n    /**\n     * Converts a charcode array to a string.\n     *\n     * @param {byteArray|Uint8Array} byteArray\n     * @returns {string}\n     *\n     * @example\n     * // returns \"Hello\"\n     * Utils.byteArrayToChars([72,101,108,108,111]);\n     *\n     * // returns \"你好\"\n     * Utils.byteArrayToChars([20320,22909]);\n     */\n    static byteArrayToChars(byteArray) {\n        log.debug(`Converting byte array[${byteArray?.length}] to chars`);\n        if (!byteArray || !byteArray.length) return \"\";\n        let str = \"\";\n        // Maxiumum arg length for fromCharCode is 65535, but the stack may already be fairly deep,\n        // so don't get too near it.\n        for (let i = 0; i < byteArray.length; i += 20000) {\n            str += String.fromCharCode(...(byteArray.slice(i, i+20000)));\n        }\n        return str;\n    }\n\n\n    /**\n     * Converts an ArrayBuffer to a string.\n     *\n     * @param {ArrayBuffer} arrayBuffer\n     * @param {boolean} [utf8=true] - Whether to attempt to decode the buffer as UTF-8\n     * @returns {string}\n     *\n     * @example\n     * // returns \"hello\"\n     * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);\n     */\n    static arrayBufferToStr(arrayBuffer, utf8=true) {\n        log.debug(`Converting array buffer[${arrayBuffer?.byteLength}] to str`);\n        if (!arrayBuffer || !arrayBuffer.byteLength) return \"\";\n        const arr = new Uint8Array(arrayBuffer);\n        return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr);\n    }\n\n    /**\n     * Calculates the Shannon entropy for a given set of data.\n     *\n     * @param {Uint8Array|ArrayBuffer} input\n     * @returns {number}\n     */\n    static calculateShannonEntropy(data) {\n        if (data instanceof ArrayBuffer) {\n            data = new Uint8Array(data);\n        }\n        const prob = [],\n            occurrences = new Array(256).fill(0);\n\n        // Count occurrences of each byte in the input\n        let i;\n        for (i = 0; i < data.length; i++) {\n            occurrences[data[i]]++;\n        }\n\n        // Store probability list\n        for (i = 0; i < occurrences.length; i++) {\n            if (occurrences[i] > 0) {\n                prob.push(occurrences[i] / data.length);\n            }\n        }\n\n        // Calculate Shannon entropy\n        let entropy = 0,\n            p;\n\n        for (i = 0; i < prob.length; i++) {\n            p = prob[i];\n            entropy += p * Math.log(p) / Math.log(2);\n        }\n\n        return -entropy;\n    }\n\n\n    /**\n     * Parses CSV data and returns it as a two dimensional array or strings.\n     *\n     * @param {string} data\n     * @param {string[]} [cellDelims=[\",\"]]\n     * @param {string[]} [lineDelims=[\"\\n\", \"\\r\"]]\n     * @returns {string[][]}\n     *\n     * @example\n     * // returns [[\"head1\", \"head2\"], [\"data1\", \"data2\"]]\n     * Utils.parseCSV(\"head1,head2\\ndata1,data2\");\n     */\n    static parseCSV(data, cellDelims=[\",\"], lineDelims=[\"\\n\", \"\\r\"]) {\n        let b,\n            next,\n            renderNext = false,\n            inString = false,\n            cell = \"\",\n            line = [];\n        const lines = [];\n\n        // Remove BOM, often present in Excel CSV files\n        if (data.length && data[0] === \"\\uFEFF\") data = data.substr(1);\n\n        for (let i = 0; i < data.length; i++) {\n            b = data[i];\n            next = data[i+1] || \"\";\n            if (renderNext) {\n                cell += b;\n                renderNext = false;\n            } else if (b === \"\\\"\" && !inString) {\n                inString = true;\n            } else if (b === \"\\\"\" && inString) {\n                if (next === \"\\\"\") renderNext = true;\n                else inString = false;\n            } else if (!inString && cellDelims.indexOf(b) >= 0) {\n                line.push(cell);\n                cell = \"\";\n            } else if (!inString && lineDelims.indexOf(b) >= 0) {\n                line.push(cell);\n                cell = \"\";\n                lines.push(line);\n                line = [];\n                // Skip next byte if it is also a line delim (e.g. \\r\\n)\n                if (lineDelims.indexOf(next) >= 0 && next !== b) {\n                    i++;\n                }\n            } else {\n                cell += b;\n            }\n        }\n\n        if (line.length) {\n            line.push(cell);\n            lines.push(line);\n        }\n\n        return lines;\n    }\n\n\n    /**\n     * Removes all HTML (or XML) tags from the input string.\n     *\n     * @param {string} htmlStr\n     * @param {boolean} [removeScriptAndStyle=false]\n     *     - Flag to specify whether to remove entire script or style blocks\n     * @returns {string}\n     *\n     * @example\n     * // returns \"Test\"\n     * Utils.stripHtmlTags(\"<div>Test</div>\");\n     */\n    static stripHtmlTags(htmlStr, removeScriptAndStyle=false) {\n        /**\n         * Recursively remove a pattern from a string until there are no more matches.\n         * Avoids incomplete sanitization e.g. \"aabcbc\".replace(/abc/g, \"\") === \"abc\"\n         *\n         * @param {RegExp} pattern\n         * @param {string} str\n         * @returns {string}\n         */\n        function recursiveRemove(pattern, str) {\n            const newStr = str.replace(pattern, \"\");\n            return newStr.length === str.length ? newStr : recursiveRemove(pattern, newStr);\n        }\n\n        if (removeScriptAndStyle) {\n            htmlStr = recursiveRemove(/<script[^>]*>(\\s|\\S)*?<\\/script[^>]*>/gi, htmlStr);\n            htmlStr = recursiveRemove(/<style[^>]*>(\\s|\\S)*?<\\/style[^>]*>/gi, htmlStr);\n        }\n        return recursiveRemove(/<[^>]+>/g, htmlStr);\n    }\n\n\n    /**\n     * Escapes HTML tags in a string to stop them being rendered.\n     * https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet\n     *\n     * Null bytes are a special case and are converted to a character from the Unicode\n     * Private Use Area, which CyberChef will display as a control character picture.\n     * This is done due to null bytes not being rendered or stored correctly in HTML\n     * DOM building.\n     *\n     * @param {string} str\n     * @returns string\n     *\n     * @example\n     * // return \"A &lt;script&gt; tag\"\n     * Utils.escapeHtml(\"A <script> tag\");\n     */\n    static escapeHtml(str) {\n        const HTML_CHARS = {\n            \"&\": \"&amp;\",\n            \"<\": \"&lt;\",\n            \">\": \"&gt;\",\n            '\"': \"&quot;\",\n            \"'\": \"&#x27;\", // &apos; not recommended because it's not in the HTML spec\n            \"`\": \"&#x60;\",\n            \"\\u0000\": \"\\ue000\"\n        };\n\n        return str ? str.replace(/[&<>\"'`\\u0000]/g, function (match) {\n            return HTML_CHARS[match];\n        }) : str;\n    }\n\n\n    /**\n     * Unescapes HTML tags in a string to make them render again.\n     *\n     * @param {string} str\n     * @returns string\n     *\n     * @example\n     * // return \"A <script> tag\"\n     * Utils.unescapeHtml(\"A &lt;script&gt; tag\");\n     */\n    static unescapeHtml(str) {\n        const HTML_CHARS = {\n            \"&amp;\":  \"&\",\n            \"&lt;\":   \"<\",\n            \"&gt;\":   \">\",\n            \"&quot;\": '\"',\n            \"&#x27;\": \"'\",\n            \"&#x2F;\": \"/\",\n            \"&#x60;\": \"`\",\n            \"\\ue000\": \"\\u0000\"\n        };\n\n        return str.replace(/(&#?x?[a-z0-9]{2,4};|\\ue000)/ig, function (match) {\n            return HTML_CHARS[match] || match;\n        });\n    }\n\n\n    /**\n     * Converts a string to its title case equivalent.\n     *\n     * @param {string} str\n     * @returns string\n     *\n     * @example\n     * // return \"A Tiny String\"\n     * Utils.toTitleCase(\"a tIny String\");\n     */\n    static toTitleCase(str) {\n        return str.replace(/\\w\\S*/g, function(txt) {\n            return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();\n        });\n    }\n\n\n    /**\n     * Encodes a URI fragment (#) or query (?) using a minimal amount of percent-encoding.\n     *\n     * RFC 3986 defines legal characters for the fragment and query parts of a URL to be as follows:\n     *\n     * fragment      = *( pchar / \"/\" / \"?\" )\n     * query         = *( pchar / \"/\" / \"?\" )\n     * pchar         = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\n     * unreserved    = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n     * pct-encoded   = \"%\" HEXDIG HEXDIG\n     * sub-delims    = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\"\n     *                  / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n     *\n     * Meaning that the list of characters that need not be percent-encoded are alphanumeric plus:\n     * -._~!$&'()*+,;=:@/?\n     *\n     * & and = are still escaped as they are used to serialise the key-value pairs in CyberChef\n     * fragments. + is also escaped so as to prevent it being decoded to a space.\n     *\n     * @param {string} str\n     * @returns {string}\n     */\n    static encodeURIFragment(str) {\n        const LEGAL_CHARS = {\n            \"%2D\": \"-\",\n            \"%2E\": \".\",\n            \"%5F\": \"_\",\n            \"%7E\": \"~\",\n            \"%21\": \"!\",\n            \"%24\": \"$\",\n            // \"%26\": \"&\",\n            \"%27\": \"'\",\n            \"%28\": \"(\",\n            \"%29\": \")\",\n            \"%2A\": \"*\",\n            // \"%2B\": \"+\",\n            \"%2C\": \",\",\n            \"%3B\": \";\",\n            // \"%3D\": \"=\",\n            \"%3A\": \":\",\n            \"%40\": \"@\",\n            \"%2F\": \"/\",\n            \"%3F\": \"?\"\n        };\n        str = encodeURIComponent(str);\n\n        return str.replace(/%[0-9A-F]{2}/g, function (match) {\n            return LEGAL_CHARS[match] || match;\n        });\n    }\n\n\n    /**\n     * Generates a \"pretty\" recipe format from a recipeConfig object.\n     *\n     * \"Pretty\" CyberChef recipe formats are designed to be included in the fragment (#) or query (?)\n     * parts of the URL. They can also be loaded into CyberChef through the 'Load' interface. In order\n     * to make this format as readable as possible, various special characters are used unescaped. This\n     * reduces the amount of percent-encoding included in the URL which is typically difficult to read\n     * and substantially increases the overall length. These characteristics can be quite off-putting\n     * for users.\n     *\n     * @param {Object[]} recipeConfig\n     * @param {boolean} [newline=false] - whether to add a newline after each operation\n     * @returns {string}\n     */\n    static generatePrettyRecipe(recipeConfig, newline = false) {\n        let prettyConfig = \"\",\n            name = \"\",\n            args = \"\",\n            disabled = \"\",\n            bp = \"\";\n\n        recipeConfig.forEach(op => {\n            name = op.op.replace(/ /g, \"_\");\n            args = JSON.stringify(op.args)\n                .slice(1, -1) // Remove [ and ] as they are implied\n                // We now need to switch double-quoted (\") strings to single quotes (') as single quotes\n                // do not need to be percent-encoded.\n                .replace(/'/g, \"\\\\'\") // Escape single quotes\n                .replace(/\"((?:[^\"\\\\]|\\\\.)*)\"/g, \"'$1'\") // Replace opening and closing \" with '\n                .replace(/\\\\\"/g, '\"'); // Unescape double quotes\n\n            disabled = op.disabled ? \"/disabled\": \"\";\n            bp = op.breakpoint ? \"/breakpoint\" : \"\";\n            prettyConfig += `${name}(${args}${disabled}${bp})`;\n            if (newline) prettyConfig += \"\\n\";\n        });\n        return prettyConfig;\n    }\n\n\n    /**\n     * Converts a recipe string to the JSON representation of the recipe.\n     * Accepts either stringified JSON or the bespoke \"pretty\" recipe format.\n     *\n     * @param {string} recipe\n     * @returns {Object[]}\n     */\n    static parseRecipeConfig(recipe) {\n        recipe = recipe.trim();\n        if (recipe.length === 0) return [];\n        if (recipe[0] === \"[\") return JSON.parse(recipe);\n\n        // Parse bespoke recipe format\n        recipe = recipe.replace(/\\n/g, \"\");\n        let m, args;\n        const recipeRegex = /([^(]+)\\(((?:'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|[^)/'])*)(\\/[^)]+)?\\)/g,\n            recipeConfig = [];\n\n        while ((m = recipeRegex.exec(recipe))) {\n            // Translate strings in args back to double-quotes\n            args = m[2] // lgtm [js/incomplete-sanitization]\n                .replace(/\"/g, '\\\\\"') // Escape double quotes\n                .replace(/(^|,|{|:)'/g, '$1\"') // Replace opening ' with \"\n                .replace(/([^\\\\]|(?:\\\\\\\\)+)'(,|:|}|$)/g, '$1\"$2') // Replace closing ' with \"\n                .replace(/\\\\'/g, \"'\"); // Unescape single quotes\n            args = \"[\" + args + \"]\";\n\n            const op = {\n                op: m[1].replace(/_/g, \" \"),\n                args: JSON.parse(args)\n            };\n            if (m[3] && m[3].indexOf(\"disabled\") > 0) op.disabled = true;\n            if (m[3] && m[3].indexOf(\"breakpoint\") > 0) op.breakpoint = true;\n            recipeConfig.push(op);\n        }\n        return recipeConfig;\n    }\n\n\n    /**\n     * Formats a list of files or directories.\n     *\n     * @author tlwr [toby@toby.codes]\n     * @author n1474335 [n1474335@gmail.com]\n     *\n     * @param {File[]} files\n     * @returns {html}\n     */\n    static async displayFilesAsHTML(files) {\n        const formatDirectory = function(file) {\n            const html = `<div class='card' style='white-space: normal;'>\n                    <div class='card-header'>\n                        <h6 class=\"mb-0\">\n                            ${Utils.escapeHtml(file.name)}\n                        </h6>\n                    </div>\n                </div>`;\n            return html;\n        };\n\n        const formatContent = function (buff, type) {\n            if (type.startsWith(\"image\")) {\n                let dataURI = \"data:\";\n                dataURI += type + \";\";\n                dataURI += \"base64,\" + toBase64(buff);\n                return \"<img style='max-width: 100%;' src='\" + dataURI + \"'>\";\n            } else {\n                return `<pre>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</pre>`;\n            }\n        };\n\n        const formatFile = async function(file, i) {\n            const buff = await Utils.readFile(file);\n            const blob = new Blob(\n                [buff],\n                {type: file.type || \"octet/stream\"}\n            );\n            const blobURL = URL.createObjectURL(blob);\n\n            const html = `<div class='card' style='white-space: normal;'>\n                    <div class='card-header' id='heading${i}'>\n                        <h6 class='mb-0'>\n                            <a class='collapsed'\n                                data-toggle='collapse'\n                                href='#collapse${i}'\n                                aria-expanded='false'\n                                aria-controls='collapse${i}'\n                                title=\"Show/hide contents of '${Utils.escapeHtml(file.name)}'\">\n                                ${Utils.escapeHtml(file.name)}</a>\n                            <span class='float-right' style=\"margin-top: -3px\">\n                                ${file.size.toLocaleString()} bytes\n                                <a title=\"Download ${Utils.escapeHtml(file.name)}\"\n                                    href=\"${blobURL}\"\n                                    download=\"${Utils.escapeHtml(file.name)}\"\n                                    data-toggle=\"tooltip\">\n                                    <i class=\"material-icons\" style=\"vertical-align: bottom\">save</i>\n                                </a>\n                                <a title=\"Move to input\"\n                                    href=\"#\"\n                                    blob-url=\"${blobURL}\"\n                                    file-name=\"${Utils.escapeHtml(file.name)}\"\n                                    class=\"extract-file\"\n                                    data-toggle=\"tooltip\">\n                                    <i class=\"material-icons\" style=\"vertical-align: bottom\">open_in_browser</i>\n                                </a>\n                            </span>\n                        </h6>\n                    </div>\n                    <div id='collapse${i}' class='collapse' aria-labelledby='heading${i}' data-parent=\"#files\">\n                        <div class='card-body'>\n                            ${formatContent(buff, file.type)}\n                        </div>\n                    </div>\n                </div>`;\n            return html;\n        };\n\n        let html = `<div style='padding: 5px; white-space: normal;'>\n                ${files.length} file(s) found\n            </div><div id=\"files\" style=\"padding: 20px\">`;\n\n        for (let i = 0; i < files.length; i++) {\n            if (files[i].name.endsWith(\"/\")) {\n                html += formatDirectory(files[i]);\n            } else {\n                html += await formatFile(files[i], i);\n            }\n        }\n\n        return html += \"</div>\";\n    }\n\n\n    /**\n     * Parses URI parameters into a JSON object.\n     *\n     * @param {string} paramStr - The serialised query or hash section of a URI\n     * @returns {object}\n     *\n     * @example\n     * // returns {a: 'abc', b: '123'}\n     * Utils.parseURIParams(\"?a=abc&b=123\")\n     * Utils.parseURIParams(\"#a=abc&b=123\")\n     */\n    static parseURIParams(paramStr) {\n        if (paramStr === \"\") return {};\n\n        // Cut off ? or # and split on &\n        if (paramStr[0] === \"?\" ||\n            paramStr[0] === \"#\") {\n            paramStr = paramStr.substr(1);\n        }\n\n        const params = paramStr.split(\"&\");\n        const result = {};\n\n        for (let i = 0; i < params.length; i++) {\n            const param = params[i].split(\"=\");\n            if (param.length !== 2) {\n                result[params[i]] = true;\n            } else {\n                result[param[0]] = decodeURIComponent(param[1].replace(/\\+/g, \" \"));\n            }\n        }\n\n        return result;\n    }\n\n\n    /**\n     * Reads a File and returns the data as a Uint8Array.\n     *\n     * @param {File | for node: array|arrayBuffer|buffer|string} file\n     * @returns {Uint8Array}\n     *\n     * @example\n     * // returns Uint8Array(5) [104, 101, 108, 108, 111]\n     * await Utils.readFile(new File([\"hello\"], \"test\"))\n     */\n    static readFile(file) {\n\n        if (isNodeEnvironment()) {\n            return Buffer.from(file).buffer;\n\n        } else {\n            return new Promise((resolve, reject) => {\n                const reader = new FileReader();\n                const data = new Uint8Array(file.size);\n                let offset = 0;\n                const CHUNK_SIZE = 10485760; // 10MiB\n\n                const seek = function() {\n                    if (offset >= file.size) {\n                        resolve(data);\n                        return;\n                    }\n                    const slice = file.slice(offset, offset + CHUNK_SIZE);\n                    reader.readAsArrayBuffer(slice);\n                };\n\n                reader.onload = function(e) {\n                    data.set(new Uint8Array(reader.result), offset);\n                    offset += CHUNK_SIZE;\n                    seek();\n                };\n\n                reader.onerror = function(e) {\n                    reject(reader.error.message);\n                };\n\n                seek();\n            });\n        }\n    }\n\n    /**\n     * Synchronously read the raw data from a File object.\n     *\n     * Only works in the Node environment\n     *\n     * @param {File} file - a File shim object (see src/node/File.mjs)\n     * @returns {ArrayBuffer} the data from the file in an ArrayBuffer\n     * @throws {TypeError} thrown if the method is called from a browser environment\n     */\n    static readFileSync(file) {\n        if (!isNodeEnvironment()) {\n            throw new TypeError(\"Browser environment cannot support readFileSync\");\n        }\n\n        const arrayBuffer = Uint8Array.from(file.data);\n        return arrayBuffer.buffer;\n    }\n\n\n    /**\n     * Actual modulo function, since % is actually the remainder function in JS.\n     *\n     * @author Matt C [matt@artemisbot.uk]\n     * @param {number} x\n     * @param {number} y\n     * @returns {number}\n     */\n    static mod(x, y) {\n        return ((x % y) + y) % y;\n    }\n\n\n    /**\n     * Finds the greatest common divisor of two numbers.\n     *\n     * @author Matt C [matt@artemisbot.uk]\n     * @param {number} x\n     * @param {number} y\n     * @returns {number}\n     */\n    static gcd(x, y) {\n        if (!y) {\n            return x;\n        }\n        return Utils.gcd(y, x % y);\n    }\n\n\n    /**\n     * Finds the modular inverse of two values.\n     *\n     * @author Matt C [matt@artemisbot.uk]\n     * @param {number} x\n     * @param {number} y\n     * @returns {number}\n     */\n    static modInv(x, y) {\n        x %= y;\n        for (let i = 1; i < y; i++) {\n            if ((x * i) % 26 === 1) {\n                return i;\n            }\n        }\n    }\n\n\n    /**\n     * A mapping of names of delimiter characters to their symbols.\n     *\n     * @param {string} token\n     * @returns {string}\n     */\n    static charRep(token) {\n        return {\n            \"Space\":         \" \",\n            \"Percent\":       \"%\",\n            \"Comma\":         \",\",\n            \"Semi-colon\":    \";\",\n            \"Colon\":         \":\",\n            \"Tab\":           \"\\t\",\n            \"Line feed\":     \"\\n\",\n            \"CRLF\":          \"\\r\\n\",\n            \"Forward slash\": \"/\",\n            \"Backslash\":     \"\\\\\",\n            \"0x\":            \"0x\",\n            \"\\\\x\":           \"\\\\x\",\n            \"Nothing (separate chars)\": \"\",\n            \"None\":          \"\",\n        }[token];\n    }\n\n\n    /**\n     * A mapping of names of delimiter characters to regular expressions which can select them.\n     *\n     * @param {string} token\n     * @returns {RegExp}\n     */\n    static regexRep(token) {\n        return {\n            \"Space\":         /\\s+/g,\n            \"Percent\":       /%/g,\n            \"Comma\":         /,/g,\n            \"Semi-colon\":    /;/g,\n            \"Colon\":         /:/g,\n            \"Line feed\":     /\\n/g,\n            \"CRLF\":          /\\r\\n/g,\n            \"Forward slash\": /\\//g,\n            \"Backslash\":     /\\\\/g,\n            \"0x with comma\": /,?0x/g,\n            \"0x\":            /0x/g,\n            \"\\\\x\":           /\\\\x/g,\n            \"None\":          /\\s+/g // Included here to remove whitespace when there shouldn't be any\n        }[token];\n    }\n\n    /**\n     * Iterate object in chunks of given size.\n     *\n     * @param {Iterable} iterable\n     * @param {number} chunksize\n     */\n    static* chunked(iterable, chunksize) {\n        const iterator = iterable[Symbol.iterator]();\n        while (true) {\n            const res = [];\n            for (let i = 0; i < chunksize; i++) {\n                const next = iterator.next();\n                if (next.done) {\n                    break;\n                }\n                res.push(next.value);\n            }\n            if (res.length) {\n                yield res;\n            } else {\n                return;\n            }\n        }\n    }\n}\n\n/**\n * Check whether the code is running in a Node.js environment\n * @returns {boolean}\n */\nexport function isNodeEnvironment() {\n    return typeof process !== \"undefined\" && process.versions != null && process.versions.node != null;\n}\n\n/**\n * Check whether the code is running in a web environment\n * @returns {boolean}\n*/\nexport function isWebEnvironment() {\n    return typeof window === \"object\";\n}\n\n/**\n * Check whether the code is running in a worker\n * @returns {boolean}\n*/\nexport function isWorkerEnvironment() {\n    return typeof importScripts === \"function\";\n}\n\nexport default Utils;\n\n\n/**\n * Removes all duplicates from an array.\n *\n * @returns {Array}\n *\n * @example\n * // returns [3,6,4,8,2]\n * [3,6,4,8,4,2,3].unique();\n *\n * // returns [\"One\", \"Two\", \"Three\"]\n * [\"One\", \"Two\", \"Three\", \"One\"].unique();\n */\nArray.prototype.unique = function() {\n    const u = {}, a = [];\n    for (let i = 0, l = this.length; i < l; i++) {\n        if (Object.prototype.hasOwnProperty.call(u, this[i])) {\n            continue;\n        }\n        a.push(this[i]);\n        u[this[i]] = 1;\n    }\n    return a;\n};\n\n\n/**\n * Returns the largest value in the array.\n *\n * @returns {number}\n *\n * @example\n * // returns 7\n * [4,2,5,3,7].max();\n */\nArray.prototype.max = function() {\n    return Math.max.apply(null, this);\n};\n\n\n/**\n * Returns the smallest value in the array.\n *\n * @returns {number}\n *\n * @example\n * // returns 2\n * [4,2,5,3,7].min();\n */\nArray.prototype.min = function() {\n    return Math.min.apply(null, this);\n};\n\n\n/**\n * Sums all the values in an array.\n *\n * @returns {number}\n *\n * @example\n * // returns 21\n * [4,2,5,3,7].sum();\n */\nArray.prototype.sum = function() {\n    return this.reduce(function (a, b) {\n        return a + b;\n    }, 0);\n};\n\n\n/**\n * Determine whether two arrays are equal or not.\n *\n * @param {Object[]} other\n * @returns {boolean}\n *\n * @example\n * // returns true\n * [1,2,3].equals([1,2,3]);\n *\n * // returns false\n * [1,2,3].equals([3,2,1]);\n */\nArray.prototype.equals = function(other) {\n    if (!other) return false;\n    let i = this.length;\n    if (i !== other.length) return false;\n    while (i--) {\n        if (this[i] !== other[i]) return false;\n    }\n    return true;\n};\n\n\n/**\n * Counts the number of times a char appears in a string.\n *\n * @param {char} chr\n * @returns {number}\n *\n * @example\n * // returns 2\n * \"Hello\".count(\"l\");\n */\nString.prototype.count = function(chr) {\n    return this.split(chr).length - 1;\n};\n\n\n/**\n * Wrapper for self.sendStatusMessage to handle different environments.\n *\n * @param {string} msg\n */\nexport function sendStatusMessage(msg) {\n    if (isWorkerEnvironment())\n        self.sendStatusMessage(msg);\n    else if (isWebEnvironment())\n        app.alert(msg, 10000);\n    else if (isNodeEnvironment() && !global.TESTING)\n        // eslint-disable-next-line no-console\n        console.debug(msg);\n}\n\nconst debounceTimeouts = {};\n\n/**\n * Debouncer to stop functions from being executed multiple times in a\n * short space of time\n * https://davidwalsh.name/javascript-debounce-function\n *\n * @param {function} func - The function to be executed after the debounce time\n * @param {number} wait - The time (ms) to wait before executing the function\n * @param {string} id - Unique ID to reference the timeout for the function\n * @param {object} scope - The object to bind to the debounced function\n * @param {array} args - Array of arguments to be passed to func\n * @returns {function}\n */\nexport function debounce(func, wait, id, scope, args) {\n    return function() {\n        const later = function() {\n            func.apply(scope, args);\n        };\n        clearTimeout(debounceTimeouts[id]);\n        debounceTimeouts[id] = setTimeout(later, wait);\n    };\n}\n\n\n/*\n * Polyfills\n */\n\n// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart\nif (!String.prototype.padStart) {\n    String.prototype.padStart = function padStart(targetLength, padString) {\n        targetLength = targetLength>>0; // floor if number or convert non-number to 0;\n        padString = String((typeof padString !== \"undefined\" ? padString : \" \"));\n        if (this.length > targetLength) {\n            return String(this);\n        } else {\n            targetLength = targetLength-this.length;\n            if (targetLength > padString.length) {\n                padString += padString.repeat(targetLength/padString.length); // append to original to ensure we are longer than needed\n            }\n            return padString.slice(0, targetLength) + String(this);\n        }\n    };\n}\n\n\n// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd\nif (!String.prototype.padEnd) {\n    String.prototype.padEnd = function padEnd(targetLength, padString) {\n        targetLength = targetLength>>0; // floor if number or convert non-number to 0;\n        padString = String((typeof padString !== \"undefined\" ? padString : \" \"));\n        if (this.length > targetLength) {\n            return String(this);\n        } else {\n            targetLength = targetLength-this.length;\n            if (targetLength > padString.length) {\n                padString += padString.repeat(targetLength/padString.length); // append to original to ensure we are longer than needed\n            }\n            return String(this) + padString.slice(0, targetLength);\n        }\n    };\n}\n"
  },
  {
    "path": "src/core/config/Categories.json",
    "content": "[\n    {\n        \"name\": \"Favourites\",\n        \"ops\": []\n    },\n    {\n        \"name\": \"Data format\",\n        \"ops\": [\n            \"To Hexdump\",\n            \"From Hexdump\",\n            \"To Hex\",\n            \"From Hex\",\n            \"To Charcode\",\n            \"From Charcode\",\n            \"To Decimal\",\n            \"From Decimal\",\n            \"To Float\",\n            \"From Float\",\n            \"To Binary\",\n            \"From Binary\",\n            \"To Octal\",\n            \"From Octal\",\n            \"To Base32\",\n            \"From Base32\",\n            \"To Base45\",\n            \"From Base45\",\n            \"To Base58\",\n            \"From Base58\",\n            \"To Bech32\",\n            \"From Bech32\",\n            \"To Base62\",\n            \"From Base62\",\n            \"To Base64\",\n            \"From Base64\",\n            \"Show Base64 offsets\",\n            \"To Base92\",\n            \"From Base92\",\n            \"To Base85\",\n            \"From Base85\",\n            \"To Base\",\n            \"From Base\",\n            \"To BCD\",\n            \"From BCD\",\n            \"Text-Integer Conversion\",\n            \"To HTML Entity\",\n            \"From HTML Entity\",\n            \"URL Encode\",\n            \"URL Decode\",\n            \"Escape Unicode Characters\",\n            \"Unescape Unicode Characters\",\n            \"Normalise Unicode\",\n            \"To Quoted Printable\",\n            \"From Quoted Printable\",\n            \"To Punycode\",\n            \"From Punycode\",\n            \"AMF Encode\",\n            \"AMF Decode\",\n            \"To Hex Content\",\n            \"From Hex Content\",\n            \"PEM to Hex\",\n            \"Hex to PEM\",\n            \"Parse ASN.1 hex string\",\n            \"Change IP format\",\n            \"Encode text\",\n            \"Decode text\",\n            \"Text Encoding Brute Force\",\n            \"Swap endianness\",\n            \"To MessagePack\",\n            \"From MessagePack\",\n            \"To Braille\",\n            \"From Braille\",\n            \"Parse TLV\",\n            \"CSV to JSON\",\n            \"JSON to CSV\",\n            \"Avro to JSON\",\n            \"CBOR Encode\",\n            \"CBOR Decode\",\n            \"YAML to JSON\",\n            \"JSON to YAML\",\n            \"Caret/M-decode\",\n            \"Rison Encode\",\n            \"Rison Decode\",\n            \"To Modhex\",\n            \"From Modhex\",\n            \"MIME Decoding\"\n        ]\n    },\n    {\n        \"name\": \"Encryption / Encoding\",\n        \"ops\": [\n            \"AES Encrypt\",\n            \"AES Decrypt\",\n            \"Blowfish Encrypt\",\n            \"Blowfish Decrypt\",\n            \"DES Encrypt\",\n            \"DES Decrypt\",\n            \"Triple DES Encrypt\",\n            \"Triple DES Decrypt\",\n            \"Fernet Encrypt\",\n            \"Fernet Decrypt\",\n            \"LS47 Encrypt\",\n            \"LS47 Decrypt\",\n            \"RC2 Encrypt\",\n            \"RC2 Decrypt\",\n            \"RC4\",\n            \"RC4 Drop\",\n            \"ChaCha\",\n            \"Salsa20\",\n            \"XSalsa20\",\n            \"Rabbit\",\n            \"SM4 Encrypt\",\n            \"SM4 Decrypt\",\n            \"RC6 Encrypt\",\n            \"RC6 Decrypt\",\n            \"GOST Encrypt\",\n            \"GOST Decrypt\",\n            \"GOST Sign\",\n            \"GOST Verify\",\n            \"GOST Key Wrap\",\n            \"GOST Key Unwrap\",\n            \"ROT13\",\n            \"ROT13 Brute Force\",\n            \"ROT47\",\n            \"ROT47 Brute Force\",\n            \"ROT8000\",\n            \"XOR\",\n            \"XOR Brute Force\",\n            \"Vigenère Encode\",\n            \"Vigenère Decode\",\n            \"XXTEA Encrypt\",\n            \"XXTEA Decrypt\",\n            \"To Morse Code\",\n            \"From Morse Code\",\n            \"Bacon Cipher Encode\",\n            \"Bacon Cipher Decode\",\n            \"Bifid Cipher Encode\",\n            \"Bifid Cipher Decode\",\n            \"Caesar Box Cipher\",\n            \"Affine Cipher Encode\",\n            \"Affine Cipher Decode\",\n            \"A1Z26 Cipher Encode\",\n            \"A1Z26 Cipher Decode\",\n            \"Rail Fence Cipher Encode\",\n            \"Rail Fence Cipher Decode\",\n            \"Atbash Cipher\",\n            \"CipherSaber2 Encrypt\",\n            \"CipherSaber2 Decrypt\",\n            \"Cetacean Cipher Encode\",\n            \"Cetacean Cipher Decode\",\n            \"Substitute\",\n            \"Derive PBKDF2 key\",\n            \"Derive EVP key\",\n            \"Derive HKDF key\",\n            \"Bcrypt\",\n            \"Scrypt\",\n            \"JWT Sign\",\n            \"JWT Verify\",\n            \"JWT Decode\",\n            \"Citrix CTX1 Encode\",\n            \"Citrix CTX1 Decode\",\n            \"AES Key Wrap\",\n            \"AES Key Unwrap\",\n            \"Pseudo-Random Number Generator\",\n            \"Enigma\",\n            \"Bombe\",\n            \"Multiple Bombe\",\n            \"Typex\",\n            \"Lorenz\",\n            \"Colossus\",\n            \"SIGABA\",\n            \"Flask Session Decode\",\n            \"Flask Session Sign\",\n            \"Flask Session Verify\"\n        ]\n    },\n    {\n        \"name\": \"Public Key\",\n        \"ops\": [\n            \"Parse X.509 certificate\",\n            \"Parse X.509 CRL\",\n            \"Parse ASN.1 hex string\",\n            \"PEM to Hex\",\n            \"Hex to PEM\",\n            \"Hex to Object Identifier\",\n            \"Object Identifier to Hex\",\n            \"PEM to JWK\",\n            \"JWK to PEM\",\n            \"Generate PGP Key Pair\",\n            \"PGP Encrypt\",\n            \"PGP Decrypt\",\n            \"PGP Verify\",\n            \"PGP Encrypt and Sign\",\n            \"PGP Decrypt and Verify\",\n            \"Generate RSA Key Pair\",\n            \"RSA Sign\",\n            \"RSA Verify\",\n            \"RSA Encrypt\",\n            \"RSA Decrypt\",\n            \"Generate ECDSA Key Pair\",\n            \"ECDSA Signature Conversion\",\n            \"ECDSA Sign\",\n            \"ECDSA Verify\",\n            \"Parse SSH Host Key\",\n            \"Parse CSR\",\n            \"Public Key from Certificate\",\n            \"Public Key from Private Key\",\n            \"SM2 Encrypt\",\n            \"SM2 Decrypt\"\n        ]\n    },\n    {\n        \"name\": \"Arithmetic / Logic\",\n        \"ops\": [\n            \"Set Union\",\n            \"Set Intersection\",\n            \"Set Difference\",\n            \"Symmetric Difference\",\n            \"Cartesian Product\",\n            \"Power Set\",\n            \"XOR\",\n            \"XOR Brute Force\",\n            \"OR\",\n            \"NOT\",\n            \"AND\",\n            \"ADD\",\n            \"SUB\",\n            \"Sum\",\n            \"Subtract\",\n            \"Multiply\",\n            \"Divide\",\n            \"Modular Exponentiation\",\n            \"Modular Inverse\",\n            \"Extended GCD\",\n            \"Mean\",\n            \"Median\",\n            \"Standard Deviation\",\n            \"Bit shift left\",\n            \"Bit shift right\",\n            \"Rotate left\",\n            \"Rotate right\",\n            \"ROT13\",\n            \"ROT8000\"\n        ]\n    },\n    {\n        \"name\": \"Networking\",\n        \"ops\": [\n            \"HTTP request\",\n            \"DNS over HTTPS\",\n            \"Strip HTTP headers\",\n            \"Dechunk HTTP response\",\n            \"Parse User Agent\",\n            \"Parse IP range\",\n            \"Parse IPv6 address\",\n            \"IPv6 Transition Addresses\",\n            \"Parse IPv4 header\",\n            \"Strip IPv4 header\",\n            \"Parse TCP\",\n            \"Strip TCP header\",\n            \"Parse TLS record\",\n            \"Parse UDP\",\n            \"Strip UDP header\",\n            \"Parse SSH Host Key\",\n            \"Parse URI\",\n            \"URL Encode\",\n            \"URL Decode\",\n            \"Protobuf Decode\",\n            \"Protobuf Encode\",\n            \"VarInt Encode\",\n            \"VarInt Decode\",\n            \"JA3 Fingerprint\",\n            \"JA3S Fingerprint\",\n            \"JA4 Fingerprint\",\n            \"JA4Server Fingerprint\",\n            \"HASSH Client Fingerprint\",\n            \"HASSH Server Fingerprint\",\n            \"Format MAC addresses\",\n            \"Change IP format\",\n            \"Group IP addresses\",\n            \"Encode NetBIOS Name\",\n            \"Decode NetBIOS Name\",\n            \"Defang URL\",\n            \"Fang URL\",\n            \"Defang IP Addresses\"\n        ]\n    },\n    {\n        \"name\": \"Language\",\n        \"ops\": [\n            \"Encode text\",\n            \"Decode text\",\n            \"Unicode Text Format\",\n            \"Remove Diacritics\",\n            \"Unescape Unicode Characters\",\n            \"Convert to NATO alphabet\",\n            \"Convert Leet Speak\"\n        ]\n    },\n    {\n        \"name\": \"Utils\",\n        \"ops\": [\n            \"Diff\",\n            \"Remove whitespace\",\n            \"Remove null bytes\",\n            \"To Upper case\",\n            \"To Lower case\",\n            \"Swap case\",\n            \"Alternating Caps\",\n            \"To Case Insensitive Regex\",\n            \"From Case Insensitive Regex\",\n            \"Add line numbers\",\n            \"Remove line numbers\",\n            \"Get All Casings\",\n            \"To Table\",\n            \"Reverse\",\n            \"Sort\",\n            \"Shuffle\",\n            \"Unique\",\n            \"Split\",\n            \"Filter\",\n            \"Head\",\n            \"Tail\",\n            \"Count occurrences\",\n            \"Expand alphabet range\",\n            \"Drop bytes\",\n            \"Take bytes\",\n            \"Pad lines\",\n            \"Find / Replace\",\n            \"Regular expression\",\n            \"Fuzzy Match\",\n            \"Offset checker\",\n            \"Hamming Distance\",\n            \"Levenshtein Distance\",\n            \"Convert distance\",\n            \"Convert area\",\n            \"Convert mass\",\n            \"Convert speed\",\n            \"Convert data units\",\n            \"Convert co-ordinate format\",\n            \"Show on map\",\n            \"Parse UNIX file permissions\",\n            \"Parse ObjectID timestamp\",\n            \"Swap endianness\",\n            \"Parse colour code\",\n            \"Escape string\",\n            \"Unescape string\",\n            \"Pseudo-Random Number Generator\",\n            \"Sleep\",\n            \"File Tree\",\n            \"Take nth bytes\",\n            \"Drop nth bytes\"\n        ]\n    },\n    {\n        \"name\": \"Date / Time\",\n        \"ops\": [\n            \"Parse DateTime\",\n            \"Translate DateTime Format\",\n            \"From UNIX Timestamp\",\n            \"To UNIX Timestamp\",\n            \"Windows Filetime to UNIX Timestamp\",\n            \"UNIX Timestamp to Windows Filetime\",\n            \"DateTime Delta\",\n            \"Extract dates\",\n            \"Get Time\",\n            \"Sleep\"\n        ]\n    },\n    {\n        \"name\": \"Extractors\",\n        \"ops\": [\n            \"Strings\",\n            \"Extract IP addresses\",\n            \"Extract email addresses\",\n            \"Extract MAC addresses\",\n            \"Extract URLs\",\n            \"Extract domains\",\n            \"Extract file paths\",\n            \"Extract dates\",\n            \"Extract hashes\",\n            \"Regular expression\",\n            \"XPath expression\",\n            \"JPath expression\",\n            \"Jsonata Query\",\n            \"CSS selector\",\n            \"Extract EXIF\",\n            \"Extract ID3\",\n            \"Extract Audio Metadata\",\n            \"Extract Files\",\n            \"RAKE\",\n            \"Template\"\n        ]\n    },\n    {\n        \"name\": \"Compression\",\n        \"ops\": [\n            \"Raw Deflate\",\n            \"Raw Inflate\",\n            \"Zlib Deflate\",\n            \"Zlib Inflate\",\n            \"Gzip\",\n            \"Gunzip\",\n            \"Zip\",\n            \"Unzip\",\n            \"Bzip2 Decompress\",\n            \"Bzip2 Compress\",\n            \"Tar\",\n            \"Untar\",\n            \"LZString Decompress\",\n            \"LZString Compress\",\n            \"LZMA Decompress\",\n            \"LZMA Compress\",\n            \"LZ4 Decompress\",\n            \"LZ4 Compress\",\n            \"LZNT1 Decompress\"\n        ]\n    },\n    {\n        \"name\": \"Hashing\",\n        \"ops\": [\n            \"Analyse hash\",\n            \"Generate all checksums\",\n            \"Generate all hashes\",\n            \"MD2\",\n            \"MD4\",\n            \"MD5\",\n            \"MD6\",\n            \"SHA0\",\n            \"SHA1\",\n            \"SHA2\",\n            \"SHA3\",\n            \"SM3\",\n            \"Keccak\",\n            \"Shake\",\n            \"RIPEMD\",\n            \"HAS-160\",\n            \"Whirlpool\",\n            \"Snefru\",\n            \"BLAKE2b\",\n            \"BLAKE2s\",\n            \"BLAKE3\",\n            \"GOST Hash\",\n            \"Streebog\",\n            \"SSDEEP\",\n            \"CTPH\",\n            \"Compare SSDEEP hashes\",\n            \"Compare CTPH hashes\",\n            \"HMAC\",\n            \"CMAC\",\n            \"Bcrypt\",\n            \"Bcrypt compare\",\n            \"Bcrypt parse\",\n            \"Argon2\",\n            \"Argon2 compare\",\n            \"Scrypt\",\n            \"NT Hash\",\n            \"LM Hash\",\n            \"MurmurHash3\",\n            \"Fletcher-8 Checksum\",\n            \"Fletcher-16 Checksum\",\n            \"Fletcher-32 Checksum\",\n            \"Fletcher-64 Checksum\",\n            \"Adler-32 Checksum\",\n            \"Luhn Checksum\",\n            \"CRC Checksum\",\n            \"TCP/IP Checksum\",\n            \"XOR Checksum\"\n        ]\n    },\n    {\n        \"name\": \"Code tidy\",\n        \"ops\": [\n            \"Syntax highlighter\",\n            \"Generic Code Beautify\",\n            \"JavaScript Parser\",\n            \"JavaScript Beautify\",\n            \"JavaScript Minify\",\n            \"JSON Beautify\",\n            \"JSON Minify\",\n            \"XML Beautify\",\n            \"XML Minify\",\n            \"SQL Beautify\",\n            \"SQL Minify\",\n            \"CSS Beautify\",\n            \"CSS Minify\",\n            \"XPath expression\",\n            \"JPath expression\",\n            \"Jq\",\n            \"CSS selector\",\n            \"PHP Deserialize\",\n            \"PHP Serialize\",\n            \"Microsoft Script Decoder\",\n            \"Strip HTML tags\",\n            \"Diff\",\n            \"To Snake case\",\n            \"To Camel case\",\n            \"To Kebab case\",\n            \"BSON serialise\",\n            \"BSON deserialise\",\n            \"To MessagePack\",\n            \"From MessagePack\",\n            \"Render Markdown\"\n        ]\n    },\n    {\n        \"name\": \"Forensics\",\n        \"ops\": [\n            \"Detect File Type\",\n            \"Scan for Embedded Files\",\n            \"Extract Files\",\n            \"YARA Rules\",\n            \"Remove EXIF\",\n            \"Extract EXIF\",\n            \"Extract RGBA\",\n            \"View Bit Plane\",\n            \"Randomize Colour Palette\",\n            \"Extract LSB\",\n            \"ELF Info\",\n            \"Extract Audio Metadata\"\n        ]\n    },\n    {\n        \"name\": \"Multimedia\",\n        \"ops\": [\n            \"Render Image\",\n            \"Play Media\",\n            \"Generate Image\",\n            \"Optical Character Recognition\",\n            \"Remove EXIF\",\n            \"Extract EXIF\",\n            \"Split Colour Channels\",\n            \"Rotate Image\",\n            \"Resize Image\",\n            \"Blur Image\",\n            \"Dither Image\",\n            \"Invert Image\",\n            \"Flip Image\",\n            \"Crop Image\",\n            \"Image Brightness / Contrast\",\n            \"Image Opacity\",\n            \"Image Filter\",\n            \"Contain Image\",\n            \"Cover Image\",\n            \"Image Hue/Saturation/Lightness\",\n            \"Sharpen Image\",\n            \"Normalise Image\",\n            \"Convert Image Format\",\n            \"Add Text To Image\",\n            \"Hex Density chart\",\n            \"Scatter chart\",\n            \"Series chart\",\n            \"Heatmap chart\",\n            \"Extract Audio Metadata\"\n        ]\n    },\n    {\n        \"name\": \"Other\",\n        \"ops\": [\n            \"Entropy\",\n            \"Frequency distribution\",\n            \"Index of Coincidence\",\n            \"Chi Square\",\n            \"P-list Viewer\",\n            \"Disassemble x86\",\n            \"Disassemble ARM\",\n            \"Pseudo-Random Number Generator\",\n            \"Pseudo-Random Integer Generator\",\n            \"Generate De Bruijn Sequence\",\n            \"Generate UUID\",\n            \"Analyse UUID\",\n            \"Generate TOTP\",\n            \"Generate HOTP\",\n            \"Generate QR Code\",\n            \"Parse QR Code\",\n            \"Haversine distance\",\n            \"HTML To Text\",\n            \"Generate Lorem Ipsum\",\n            \"Numberwang\",\n            \"XKCD Random Number\"\n        ]\n    },\n    {\n        \"name\": \"Flow control\",\n        \"ops\": [\n            \"Magic\",\n            \"Fork\",\n            \"Subsection\",\n            \"Merge\",\n            \"Register\",\n            \"Label\",\n            \"Jump\",\n            \"Conditional Jump\",\n            \"Return\",\n            \"Comment\"\n        ]\n    }\n]\n"
  },
  {
    "path": "src/core/config/scripts/generateConfig.mjs",
    "content": "/**\n * This script automatically generates OperationConfig.json, containing metadata\n * for each operation in the src/core/operations directory.\n * It also generates modules in the src/core/config/modules directory to separate\n * out operations into logical collections.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/* eslint no-console: [\"off\"] */\n\nimport path from \"path\";\nimport fs  from \"fs\";\nimport process from \"process\";\nimport * as Ops from \"../../operations/index.mjs\";\n\nconst dir = path.join(process.cwd() + \"/src/core/config/\");\nif (!fs.existsSync(dir)) {\n    console.log(\"\\nCWD: \" + process.cwd());\n    console.log(\"Error: generateConfig.mjs should be run from the project root\");\n    console.log(\"Example> node --experimental-modules src/core/config/scripts/generateConfig.mjs\");\n    process.exit(1);\n}\n\n\nconst operationConfig = {},\n    modules = {};\n\n/**\n * Generate operation config and module lists.\n */\nfor (const opObj in Ops) {\n    const op = new Ops[opObj]();\n\n    operationConfig[op.name] = {\n        module:      op.module,\n        description: op.description,\n        infoURL:     op.infoURL,\n        inputType:   op.inputType,\n        outputType:  op.presentType,\n        flowControl: op.flowControl,\n        manualBake:  op.manualBake,\n        args:        op.args,\n        checks:      op.checks\n    };\n\n    if (!(op.module in modules))\n        modules[op.module] = {};\n    modules[op.module][op.name] = opObj;\n}\n\n\n/**\n * Write OperationConfig.\n */\nfs.writeFileSync(\n    path.join(dir, \"OperationConfig.json\"),\n    JSON.stringify(operationConfig, null, 4)\n);\nconsole.log(\"Written OperationConfig.json\");\n\n\n/**\n * Write modules.\n */\nif (!fs.existsSync(path.join(dir, \"modules/\"))) {\n    fs.mkdirSync(path.join(dir, \"modules/\"));\n}\n\nfor (const module in modules) {\n    let code = `/**\n* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs\n*\n* @author n1474335 [n1474335@gmail.com]\n* @copyright Crown Copyright ${new Date().getUTCFullYear()}\n* @license Apache-2.0\n*/\n`;\n\n    for (const opName in modules[module]) {\n        const objName = modules[module][opName];\n        code += `import ${objName} from \"../../operations/${objName}.mjs\";\\n`;\n    }\n\n    code += `\nconst OpModules = typeof self === \"undefined\" ? {} : self.OpModules || {};\n\nOpModules.${module} = {\n`;\n    for (const opName in modules[module]) {\n        const objName = modules[module][opName];\n        code += `    \"${opName}\": ${objName},\\n`;\n    }\n\n    code += `};\n\nexport default OpModules;\n`;\n    fs.writeFileSync(\n        path.join(dir, `modules/${module}.mjs`),\n        code\n    );\n    console.log(`Written ${module} module`);\n}\n\n\n/**\n * Write OpModules wrapper.\n */\nlet opModulesCode = `/**\n* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs\n*\n* Imports all modules for builds which do not load modules separately.\n*\n* @author n1474335 [n1474335@gmail.com]\n* @copyright Crown Copyright ${new Date().getUTCFullYear()}\n* @license Apache-2.0\n*/\n`;\n\nfor (const module in modules) {\n    opModulesCode += `import ${module}Module from \"./${module}.mjs\";\\n`;\n}\n\nopModulesCode += `\nconst OpModules = {};\n\nObject.assign(\n    OpModules,\n`;\n\nfor (const module in modules) {\n    opModulesCode += `    ${module}Module,\\n`;\n}\n\nopModulesCode += `);\n\nexport default OpModules;\n`;\n\nfs.writeFileSync(\n    path.join(dir, \"modules/OpModules.mjs\"),\n    opModulesCode\n);\nconsole.log(\"Written OpModules.mjs\");\n"
  },
  {
    "path": "src/core/config/scripts/generateOpsIndex.mjs",
    "content": "/**\n * This script automatically generates src/core/operations/index.mjs, containing\n * imports for all operations in src/core/operations.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/* eslint no-console: [\"off\"] */\n\nimport path from \"path\";\nimport fs  from \"fs\";\nimport process from \"process\";\n\nconst dir = path.join(process.cwd() + \"/src/core/config/\");\nif (!fs.existsSync(dir)) {\n    console.log(\"\\nCWD: \" + process.cwd());\n    console.log(\"Error: generateOpsIndex.mjs should be run from the project root\");\n    console.log(\"Example> node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs\");\n    process.exit(1);\n}\n\n// Find all operation files\nconst opObjs = [];\nfs.readdirSync(path.join(dir, \"../operations\")).forEach(file => {\n    if (!file.endsWith(\".mjs\") || file === \"index.mjs\") return;\n    opObjs.push(file.split(\".mjs\")[0]);\n});\n\n// Construct index file\nlet code = `/**\n * THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright ${new Date().getUTCFullYear()}\n * @license Apache-2.0\n */\n`;\n\nopObjs.forEach(obj => {\n    code += `import ${obj} from \"./${obj}.mjs\";\\n`;\n});\n\ncode += `\nexport {\n`;\n\nopObjs.forEach(obj => {\n    code += `    ${obj},\\n`;\n});\n\ncode += \"};\\n\";\n\n// Write file\nfs.writeFileSync(\n    path.join(dir, \"../operations/index.mjs\"),\n    code\n);\nconsole.log(\"Written operation index.\");\n"
  },
  {
    "path": "src/core/config/scripts/newMinorVersion.mjs",
    "content": "/**\n * This script updates the CHANGELOG when a new minor version is created.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\n/* eslint no-console: [\"off\"] */\n/* eslint jsdoc/require-jsdoc: [\"off\"] */\n\nimport path from \"path\";\nimport fs from \"fs\";\nimport process from \"process\";\nimport { execSync } from \"child_process\";\n\nconst ignoredAuthors = [\"github-advanced-security[bot]\", \"dependabot[bot]\"];\n\nasync function main() {\n    const dir = path.join(process.cwd() + \"/src/core/config/\");\n    if (!fs.existsSync(dir)) {\n        console.log(\"\\nCWD: \" + process.cwd());\n        console.log(\n            \"Error: newMinorVersion.mjs should be run from the project root\",\n        );\n        console.log(\n            \"Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs\",\n        );\n        process.exit(1);\n    }\n\n    let changelogData = fs.readFileSync(\n        path.join(process.cwd(), \"CHANGELOG.md\"),\n        \"utf8\",\n    );\n    const lastVersion = changelogData.match(\n        /## Details\\s+### \\[(\\d+)\\.(\\d+)\\.(\\d+)\\]/,\n    );\n    const newVersion = [\n        parseInt(lastVersion[1], 10),\n        parseInt(lastVersion[2], 10) + 1,\n        0,\n    ];\n\n    let knownContributors = changelogData.match(/^\\[@([^\\]]+)\\]/gm);\n    knownContributors = knownContributors.map((c) => c.slice(2, -1));\n\n    const date = new Date().toISOString().split(\"T\")[0];\n\n    const lastVersionSha = execSync(\n        `git rev-list -n 1 v${lastVersion[1]}.${lastVersion[2]}.${lastVersion[3]}`,\n        {\n            encoding: \"utf8\",\n        },\n    ).trim();\n    if (lastVersionSha.length !== 40) {\n        throw new Error(\n            `Unexpected output from git rev-list: ${lastVersionSha}`,\n        );\n    }\n\n    const features = [];\n\n    const commits = await (\n        await fetch(`https://api.github.com/repos/gchq/cyberchef/commits`)\n    ).json();\n    let foundLast = false;\n    for (const commit of commits) {\n        if (commit.sha === lastVersionSha) {\n            foundLast = true;\n            break;\n        } else {\n            const feature = {\n                message: \"\",\n                authors: [],\n                id: \"\",\n            };\n\n            const msgparts = commit.commit.message.split(\"\\n\\n\");\n            feature.message = msgparts[0];\n            const prIdMatch = feature.message.match(/\\(#(\\d+)\\)$/);\n            if (prIdMatch !== null) {\n                feature.message = feature.message\n                    .replace(prIdMatch[0], \"\")\n                    .trim();\n                feature.id = prIdMatch[1];\n            }\n\n            if (!ignoredAuthors.includes(commit.author.login)) {\n                feature.authors.push(commit.author.login);\n            }\n\n            if (msgparts.length > 1) {\n                msgparts[1]\n                    .split(\"\\n\")\n                    .filter((line) => line.startsWith(\"Co-authored-by: \"))\n                    .forEach((line) => {\n                        let coAuthor = line.slice(\"Co-authored-by: \".length);\n                        if (coAuthor.indexOf(\">\") !== -1) {\n                            const email = coAuthor.slice(\n                                coAuthor.indexOf(\"<\") + 1,\n                                coAuthor.indexOf(\">\"),\n                            );\n                            if (email.endsWith(\"@users.noreply.github.com\")) {\n                                coAuthor = email.slice(\n                                    email.indexOf(\"+\") + 1,\n                                    -\"@users.noreply.github.com\".length,\n                                );\n                            } else {\n                                throw new Error(\n                                    \"Could not get ID of co-author: \" +\n                                        coAuthor,\n                                );\n                            }\n                        } else {\n                            throw new Error(\n                                \"Could not get email of co-author: \" + coAuthor,\n                            );\n                        }\n                        if (!ignoredAuthors.includes(coAuthor)) {\n                            feature.authors.push(coAuthor);\n                        }\n                    });\n            }\n\n            features.push(feature);\n        }\n    }\n    if (!foundLast) {\n        throw new Error(\n            `Could not find last version commit: ${lastVersionSha} - need to add paging functionality`,\n        );\n    }\n\n    let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\\n`;\n\n    const authors = [];\n    const prIDs = [];\n    const commitIDs = [];\n\n    features.forEach((feature) => {\n        const id =\n            feature.id.length > 10 ? feature.id.slice(0, 7) : \"#\" + feature.id;\n        message += `- ${feature.message} ${feature.authors.map((a) => `[@${a}]`).join(\" \")} | [${id}]\\n`;\n\n        feature.authors.forEach((author) => {\n            if (!knownContributors.includes(author)) {\n                knownContributors.push(author);\n                authors.push(`[@${author}]: https://github.com/${author}`);\n            }\n        });\n\n        if (feature.id.length > 10) {\n            commitIDs.push(\n                `[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`,\n            );\n        } else {\n            prIDs.push(\n                `[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`,\n            );\n        }\n    });\n\n    // Message\n    changelogData = changelogData.replace(\n        /## Details\\n\\n/,\n        \"## Details\\n\\n\" + message + \"\\n\",\n    );\n\n    // Tag\n    const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\\n`;\n    changelogData = changelogData.replace(\n        /\\n\\n(\\[\\d+\\.\\d+\\.\\d+\\]: https)/,\n        \"\\n\\n\" + newTag + \"$1\",\n    );\n\n    // Author\n    authors.forEach((author) => {\n        changelogData = changelogData.replace(\n            /(\\n\\[@[^\\]]+\\]: https:\\/\\/github\\.com\\/[^\\n]+\\n)\\n/,\n            \"$1\" + author + \"\\n\\n\",\n        );\n    });\n\n    // Commit IDs\n    commitIDs.forEach((commitID) => {\n        changelogData = changelogData.replace(\n            /(\\n\\[[^\\].]+\\]: https:\\/\\/github.com\\/gchq\\/CyberChef\\/commit\\/[^\\n]+\\n)\\n/,\n            \"$1\" + commitID + \"\\n\\n\",\n        );\n    });\n\n    // PR IDs\n    prIDs.forEach((prID) => {\n        changelogData = changelogData.replace(\n            /(\\n\\[#[^\\]]+\\]: https:\\/\\/github.com\\/gchq\\/CyberChef\\/(?:pull|issues)\\/[^\\n]+\\n)\\n*$/,\n            \"$1\" + prID + \"\\n\\n\",\n        );\n    });\n\n    fs.writeFileSync(path.join(process.cwd(), \"CHANGELOG.md\"), changelogData);\n}\nmain().catch(console.error);\n"
  },
  {
    "path": "src/core/config/scripts/newOperation.mjs",
    "content": "/**\n * Interactive script for generating a new operation template.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/* eslint no-console: [\"off\"] */\n\nimport prompt from \"prompt\";\nimport colors from \"colors\";\nimport process from \"process\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport EscapeString from \"../../operations/EscapeString.mjs\";\n\n\nconst dir = path.join(process.cwd() + \"/src/core/operations/\");\nif (!fs.existsSync(dir)) {\n    console.log(\"\\nCWD: \" + process.cwd());\n    console.log(\"Error: newOperation.mjs should be run from the project root\");\n    console.log(\"Example> node --experimental-modules src/core/config/scripts/newOperation.mjs\");\n    process.exit(1);\n}\n\nconst ioTypes = [\"string\", \"byteArray\", \"number\", \"html\", \"ArrayBuffer\", \"BigNumber\", \"JSON\", \"File\", \"List<File>\"];\n\nconst schema = {\n    properties: {\n        opName: {\n            description: \"The operation name should be short but descriptive.\",\n            example: \"URL Decode\",\n            prompt: \"Operation name\",\n            type: \"string\",\n            pattern: /^[\\w\\s-/().]+$/,\n            required: true,\n            message: \"Operation names should consist of letters, numbers or the following symbols: _-/().\"\n        },\n        module: {\n            description: `Modules are used to group operations that rely on large libraries. Any operation that is not in the Default module will be loaded in dynamically when it is first called. All operations in the same module will also be loaded at this time. This system prevents the CyberChef web app from getting too bloated and taking a long time to load initially.\nIf your operation does not rely on a library, just leave this blank and it will be added to the Default module. If it relies on the same library as other operations, enter the name of the module those operations are in. If it relies on a new large library, enter a new module name (capitalise the first letter).`,\n            example: \"Crypto\",\n            prompt: \"Module\",\n            type: \"string\",\n            pattern: /^[A-Z][A-Za-z\\d]+$/,\n            message: \"Module names should start with a capital letter and not contain any spaces or symbols.\",\n            default: \"Default\"\n        },\n        description: {\n            description: \"The description should explain what the operation is and how it works. It can describe how the arguments should be entered and give examples of expected input and output. HTML markup is supported. Use <code> tags for examples. The description is scanned during searches, so include terms that are likely to be searched for when someone is looking for your operation.\",\n            example: \"Converts URI/URL percent-encoded characters back to their raw values.<br><br>e.g. <code>%3d</code> becomes <code>=</code>\",\n            prompt: \"Description\",\n            type: \"string\"\n        },\n        infoURL: {\n            description: \"An optional URL for an external site can be added to give more information about the operation. Wikipedia links are often suitable. If linking to Wikipedia, use an international link (e.g. https://wikipedia.org/...) rather than a localised link (e.g. https://en.wikipedia.org/...).\",\n            example: \"https://wikipedia.org/wiki/Percent-encoding\",\n            prompt: \"Information URL\",\n            type: \"string\",\n        },\n        inputType: {\n            description: `The input type defines how the input data will be presented to your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(\", \")}.`,\n            example: \"string\",\n            prompt: \"Input type\",\n            type: \"string\",\n            pattern: new RegExp(`^(${ioTypes.join(\"|\")})$`),\n            required: true,\n            message: `The input type should be one of: ${ioTypes.join(\", \")}.`\n        },\n        outputType: {\n            description: `The output type tells CyberChef what sort of data you are returning from your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(\", \")}.`,\n            example: \"string\",\n            prompt: \"Output type\",\n            type: \"string\",\n            pattern: new RegExp(`^(${ioTypes.join(\"|\")})$`),\n            required: true,\n            message: `The output type should be one of: ${ioTypes.join(\", \")}.`\n        },\n        highlight: {\n            description: \"If your operation does not change the length of the input in any way, we can enable highlighting. If it does change the length in a predictable way, we may still be able to enable highlighting and calculate the correct offsets. If this is not possible, we will disable highlighting for this operation.\",\n            example: \"true/false\",\n            prompt: \"Enable highlighting\",\n            type: \"boolean\",\n            default: \"false\",\n            message: \"Enter true or false to specify if highlighting should be enabled.\"\n        },\n        authorName: {\n            description: \"Your name or username will be added to the @author tag for this operation.\",\n            example: \"n1474335\",\n            prompt: \"Username\",\n            type: \"string\"\n        },\n        authorEmail: {\n            description: \"Your email address will also be added to the @author tag for this operation.\",\n            example: \"n1474335@gmail.com\",\n            prompt: \"Email\",\n            type: \"string\"\n        }\n    }\n};\n\n// Build schema\nfor (const prop in schema.properties) {\n    const p = schema.properties[prop];\n    p.description = \"\\n\" + colors.white(p.description) + colors.cyan(\"\\nExample: \" + p.example) + \"\\n\" + colors.green(p.prompt);\n}\n\nconsole.log(\"\\n\\nThis script will generate a new operation template based on the information you provide. These values can be changed manually later.\".yellow);\n\nprompt.message = \"\";\nprompt.delimiter = \":\".green;\n\nprompt.start();\n\nprompt.get(schema, (err, result) => {\n    if (err) {\n        console.log(\"\\nExiting build script.\");\n        process.exit(0);\n    }\n\n    const moduleName = result.opName.replace(/\\w\\S*/g, txt => {\n        return txt.charAt(0).toUpperCase() + txt.substr(1);\n    }).replace(/[\\s-()./]/g, \"\");\n\n\n    const template = `/**\n * @author ${result.authorName} [${result.authorEmail}]\n * @copyright Crown Copyright ${(new Date()).getFullYear()}\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * ${result.opName} operation\n */\nclass ${moduleName} extends Operation {\n\n    /**\n     * ${moduleName} constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"${result.opName}\";\n        this.module = \"${result.module}\";\n        this.description = \"${(new EscapeString).run(result.description, [\"Special chars\", \"Double\"])}\";\n        this.infoURL = \"${result.infoURL}\"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)\n        this.inputType = \"${result.inputType}\";\n        this.outputType = \"${result.outputType}\";\n        this.args = [\n            /* Example arguments. See the project wiki for full details.\n            {\n                name: \"First arg\",\n                type: \"string\",\n                value: \"Don't Panic\"\n            },\n            {\n                name: \"Second arg\",\n                type: \"number\",\n                value: 42\n            }\n            */\n        ];\n    }\n\n    /**\n     * @param {${result.inputType}} input\n     * @param {Object[]} args\n     * @returns {${result.outputType}}\n     */\n    run(input, args) {\n        // const [firstArg, secondArg] = args;\n\n        throw new OperationError(\"Test\");\n    }\n${result.highlight ? `\n    /**\n     * Highlight ${result.opName}\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight ${result.opName} in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n` : \"\"}\n}\n\nexport default ${moduleName};\n`;\n\n    // console.log(template);\n\n    const filename = path.join(dir, `./${moduleName}.mjs`);\n    if (fs.existsSync(filename)) {\n        console.log(`${filename} already exists. It has NOT been overwritten.`.red);\n        console.log(\"Choose a different operation name to avoid conflicts.\");\n        process.exit(0);\n    }\n    fs.writeFileSync(filename, template);\n\n    console.log(`\\nOperation template written to ${colors.green(filename)}`);\n    console.log(`\\nNext steps:\n1. Add your operation to ${colors.green(\"src/core/config/Categories.json\")}\n2. Write your operation code.\n3. Write tests in ${colors.green(\"tests/operations/tests/\")}\n4. Run ${colors.cyan(\"npm run lint\")} and ${colors.cyan(\"npm run test\")}\n5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);\n\n});\n\n"
  },
  {
    "path": "src/core/dishTypes/DishBigNumber.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport DishType from \"./DishType.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport BigNumber from \"bignumber.js\";\n\n/**\n * translation methods for BigNumber Dishes\n */\nclass DishBigNumber extends DishType {\n\n    /**\n     * convert the given value to a ArrayBuffer\n     * @param {BigNumber} value\n     */\n    static toArrayBuffer() {\n        DishBigNumber.checkForValue(this.value);\n        this.value = BigNumber.isBigNumber(this.value) ? Utils.strToArrayBuffer(this.value.toFixed()) : new ArrayBuffer;\n    }\n\n    /**\n     * convert the given value from a ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        DishBigNumber.checkForValue(this.value);\n        try {\n            this.value = new BigNumber(Utils.arrayBufferToStr(this.value));\n        } catch (err) {\n            this.value = new BigNumber(NaN);\n        }\n    }\n}\n\nexport default DishBigNumber;\n"
  },
  {
    "path": "src/core/dishTypes/DishByteArray.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport DishType from \"./DishType.mjs\";\n\n/**\n * Translation methods for ArrayBuffer Dishes\n */\nclass DishByteArray extends DishType {\n\n    /**\n     * convert the given value to a ArrayBuffer\n     */\n    static toArrayBuffer() {\n        DishByteArray.checkForValue(this.value);\n        this.value = new Uint8Array(this.value).buffer;\n    }\n\n    /**\n     * convert the given value from a ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        DishByteArray.checkForValue(this.value);\n        this.value = Array.prototype.slice.call(new Uint8Array(this.value));\n    }\n}\n\nexport default DishByteArray;\n"
  },
  {
    "path": "src/core/dishTypes/DishFile.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport DishType from \"./DishType.mjs\";\nimport Utils, { isNodeEnvironment } from \"../Utils.mjs\";\n\n/**\n * Translation methods for file Dishes\n */\nclass DishFile extends DishType {\n\n    /**\n     * convert the given value to an ArrayBuffer\n     * @param {File} value\n     */\n    static toArrayBuffer() {\n        DishFile.checkForValue(this.value);\n        if (isNodeEnvironment()) {\n            this.value = Utils.readFileSync(this.value);\n        } else {\n            return new Promise((resolve, reject) => {\n                Utils.readFile(this.value)\n                    .then(v => this.value = v.buffer)\n                    .then(resolve)\n                    .catch(reject);\n            });\n        }\n    }\n\n    /**\n     * convert the given value from an ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        DishFile.checkForValue(this.value);\n        this.value = new File(this.value, \"unknown\");\n    }\n}\n\nexport default DishFile;\n"
  },
  {
    "path": "src/core/dishTypes/DishHTML.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport DishString from \"./DishString.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Translation methods for HTML Dishes\n */\nclass DishHTML extends DishString {\n\n    /**\n     * convert the given value to a ArrayBuffer\n     * @param {String} value\n     */\n    static toArrayBuffer() {\n        DishHTML.checkForValue(this.value);\n        this.value = this.value ? Utils.strToArrayBuffer(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : new ArrayBuffer;\n    }\n\n}\n\nexport default DishHTML;\n"
  },
  {
    "path": "src/core/dishTypes/DishJSON.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport DishType from \"./DishType.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Translation methods for JSON dishes\n */\nclass DishJSON extends DishType {\n\n    /**\n     * convert the given value to a ArrayBuffer\n     */\n    static toArrayBuffer() {\n        DishJSON.checkForValue(this.value);\n        this.value = this.value !== undefined ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;\n    }\n\n    /**\n     * convert the given value from a ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        DishJSON.checkForValue(this.value);\n        this.value = JSON.parse(Utils.arrayBufferToStr(this.value));\n    }\n}\n\nexport default DishJSON;\n"
  },
  {
    "path": "src/core/dishTypes/DishListFile.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport DishType from \"./DishType.mjs\";\nimport Utils, { isNodeEnvironment } from \"../Utils.mjs\";\n\n\n/**\n * Translation methods for ListFile Dishes\n */\nclass DishListFile extends DishType {\n\n    /**\n     * convert the given value to a ArrayBuffer\n     */\n    static async toArrayBuffer() {\n        DishListFile.checkForValue(this.value);\n\n        if (isNodeEnvironment()) {\n            this.value = this.value.map(file => Uint8Array.from(file.data));\n        } else {\n            this.value = await DishListFile.concatenateTypedArraysWithTypedElements(...this.value);\n        }\n    }\n\n    /**\n     * convert the given value from a ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        DishListFile.checkForValue(this.value);\n        this.value = [new File(this.value, \"unknown\")];\n    }\n\n    /**\n     * Concatenates a list of typed elements together.\n     *\n     * @param {Uint8Array[]} arrays\n     * @returns {Uint8Array}\n     */\n    static async concatenateTypedArraysWithTypedElements(...arrays) {\n        let totalLength = 0;\n        for (const arr of arrays) {\n            totalLength += arr.size;\n        }\n        const myArray = new Uint8Array(totalLength);\n\n        let offset = 0;\n        for (const arr of arrays) {\n            const data = await Utils.readFile(arr);\n            myArray.set(data, offset);\n            offset += data.length;\n        }\n        return myArray;\n    }\n\n    /**\n     * Concatenates a list of Uint8Arrays together\n     *\n     * @param {Uint8Array[]} arrays\n     * @returns {Uint8Array}\n     */\n    static concatenateTypedArrays(...arrays) {\n        let totalLength = 0;\n        for (const arr of arrays) {\n            totalLength += arr.length;\n        }\n        const result = new Uint8Array(totalLength);\n        let offset = 0;\n        for (const arr of arrays) {\n            result.set(arr, offset);\n            offset += arr.length;\n        }\n        return result;\n    }\n}\n\nexport default DishListFile;\n"
  },
  {
    "path": "src/core/dishTypes/DishNumber.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n\nimport DishType from \"./DishType.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Translation methods for number dishes\n */\nclass DishNumber extends DishType {\n\n    /**\n     * convert the given value to a ArrayBuffer\n     */\n    static toArrayBuffer() {\n        DishNumber.checkForValue(this.value);\n        this.value = typeof this.value === \"number\" ? Utils.strToArrayBuffer(this.value.toString()) : new ArrayBuffer;\n    }\n\n    /**\n     * convert the given value from a ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        DishNumber.checkForValue(this.value);\n        this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value)) : 0;\n    }\n}\n\nexport default DishNumber;\n"
  },
  {
    "path": "src/core/dishTypes/DishString.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n\nimport DishType from \"./DishType.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Translation methods for string dishes\n */\nclass DishString extends DishType {\n\n    /**\n     * convert the given value to a ArrayBuffer\n     */\n    static toArrayBuffer() {\n        DishString.checkForValue(this.value);\n        this.value = this.value ? Utils.strToArrayBuffer(this.value) : new ArrayBuffer;\n    }\n\n    /**\n     * convert the given value from a ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        DishString.checkForValue(this.value);\n        this.value = this.value ? Utils.arrayBufferToStr(this.value) : \"\";\n    }\n}\n\nexport default DishString;\n"
  },
  {
    "path": "src/core/dishTypes/DishType.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n\n/**\n * Abstract class for dish translation methods\n */\nclass DishType {\n\n    /**\n     * Warn translations dont work without value from bind\n     */\n    static checkForValue(value) {\n        if (value === undefined) {\n            throw new Error(\"only use translation methods with .bind\");\n        }\n    }\n\n    /**\n     * convert the given value to a ArrayBuffer\n     * @param {*} value\n     */\n    static toArrayBuffer() {\n        throw new Error(\"toArrayBuffer has not been implemented\");\n    }\n\n    /**\n     * convert the given value from a ArrayBuffer\n     */\n    static fromArrayBuffer() {\n        throw new Error(\"fromArrayBuffer has not been implemented\");\n    }\n}\n\nexport default DishType;\n"
  },
  {
    "path": "src/core/dishTypes/index.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n\nimport DishByteArray from \"./DishByteArray.mjs\";\nimport DishBigNumber from \"./DishBigNumber.mjs\";\nimport DishFile from \"./DishFile.mjs\";\nimport DishHTML from \"./DishHTML.mjs\";\nimport DishJSON from \"./DishJSON.mjs\";\nimport DishListFile from \"./DishListFile.mjs\";\nimport DishNumber from \"./DishNumber.mjs\";\nimport DishString from \"./DishString.mjs\";\n\nexport {\n    DishByteArray,\n    DishBigNumber,\n    DishFile,\n    DishHTML,\n    DishJSON,\n    DishListFile,\n    DishNumber,\n    DishString,\n};\n"
  },
  {
    "path": "src/core/errors/DishError.mjs",
    "content": "/**\n * Custom error type for handling Dish type errors.\n * i.e. where the Dish cannot be successfully translated between types\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nclass DishError extends Error {\n    /**\n     * Standard error constructor. Adds no new behaviour.\n     *\n     * @param args - Standard error args\n     */\n    constructor(...args) {\n        super(...args);\n\n        this.type = \"DishError\";\n\n        if (Error.captureStackTrace) {\n            Error.captureStackTrace(this, DishError);\n        }\n    }\n}\n\nexport default DishError;\n"
  },
  {
    "path": "src/core/errors/ExcludedOperationError.mjs",
    "content": "/**\n * Custom error type for handling operation that isnt included in node.js API\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nclass ExcludedOperationError extends Error {\n    /**\n     * Standard error constructor. Adds no new behaviour.\n     *\n     * @param args - Standard error args\n     */\n    constructor(...args) {\n        super(...args);\n\n        this.type = \"ExcludedOperationError\";\n\n        if (Error.captureStackTrace) {\n            Error.captureStackTrace(this, ExcludedOperationError);\n        }\n    }\n}\n\nexport default ExcludedOperationError;\n"
  },
  {
    "path": "src/core/errors/OperationError.mjs",
    "content": "/**\n * Custom error type for handling operation input errors.\n * i.e. where the operation can handle the error and print a message to the screen.\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nclass OperationError extends Error {\n    /**\n     * Standard error constructor. Adds no new behaviour.\n     *\n     * @param args - Standard error args\n     */\n    constructor(...args) {\n        super(...args);\n\n        this.type = \"OperationError\";\n\n        if (Error.captureStackTrace) {\n            Error.captureStackTrace(this, OperationError);\n        }\n    }\n}\n\nexport default OperationError;\n"
  },
  {
    "path": "src/core/errors/index.mjs",
    "content": "import OperationError from \"./OperationError.mjs\";\nimport DishError from \"./DishError.mjs\";\nimport ExcludedOperationError from \"./ExcludedOperationError.mjs\";\n\nexport {\n    OperationError,\n    DishError,\n    ExcludedOperationError,\n};\n"
  },
  {
    "path": "src/core/lib/Arithmetic.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmailcom]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport BigNumber from \"bignumber.js\";\n\n\n/**\n * Converts a string array to a number array.\n *\n * @param {string[]} input\n * @param {string} delim\n * @returns {BigNumber[]}\n */\nexport function createNumArray(input, delim) {\n    delim = Utils.charRep(delim || \"Space\");\n    const splitNumbers = input.split(delim);\n    const numbers = [];\n    let num;\n\n    splitNumbers.map((number) => {\n        try {\n            num = BigNumber(number.trim());\n            if (!num.isNaN()) {\n                numbers.push(num);\n            }\n        } catch (err) {\n            // This line is not a valid number\n        }\n    });\n    return numbers;\n}\n\n\n/**\n * Adds an array of numbers and returns the value.\n *\n * @param {BigNumber[]} data\n * @returns {BigNumber}\n */\nexport function sum(data) {\n    if (data.length > 0) {\n        return data.reduce((acc, curr) => acc.plus(curr));\n    }\n}\n\n\n/**\n * Subtracts an array of numbers and returns the value.\n *\n * @param {BigNumber[]} data\n * @returns {BigNumber}\n */\nexport function sub(data) {\n    if (data.length > 0) {\n        return data.reduce((acc, curr) => acc.minus(curr));\n    }\n}\n\n\n/**\n * Multiplies an array of numbers and returns the value.\n *\n * @param {BigNumber[]} data\n * @returns {BigNumber}\n */\nexport function multi(data) {\n    if (data.length > 0) {\n        return data.reduce((acc, curr) => acc.times(curr));\n    }\n}\n\n\n/**\n * Divides an array of numbers and returns the value.\n *\n * @param {BigNumber[]} data\n * @returns {BigNumber}\n */\nexport function div(data) {\n    if (data.length > 0) {\n        return data.reduce((acc, curr) => acc.div(curr));\n    }\n}\n\n\n/**\n * Computes mean of a number array and returns the value.\n *\n * @param {BigNumber[]} data\n * @returns {BigNumber}\n */\nexport function mean(data) {\n    if (data.length > 0) {\n        return sum(data).div(data.length);\n    }\n}\n\n\n/**\n * Computes median of a number array and returns the value.\n *\n * @param {BigNumber[]} data\n * @returns {BigNumber}\n */\nexport function median(data) {\n    if ((data.length % 2) === 0 && data.length > 0) {\n        data.sort(function(a, b) {\n            return a.minus(b);\n        });\n        const first = data[Math.floor(data.length / 2)];\n        const second = data[Math.floor(data.length / 2) - 1];\n        return mean([first, second]);\n    } else {\n        return data[Math.floor(data.length / 2)];\n    }\n}\n\n\n/**\n * Computes standard deviation of a number array and returns the value.\n *\n * @param {BigNumber[]} data\n * @returns {BigNumber}\n */\nexport function stdDev(data) {\n    if (data.length > 0) {\n        const avg = mean(data);\n        let devSum = new BigNumber(0);\n        data.map((datum) => {\n            devSum = devSum.plus(datum.minus(avg).pow(2));\n        });\n        return devSum.div(data.length).sqrt();\n    }\n}\n"
  },
  {
    "path": "src/core/lib/AudioBytes.mjs",
    "content": "/**\n * Byte-reading and text-decoding utilities for audio metadata parsing.\n *\n * @author d0s1nt [d0s1nt@cyberchefaudio]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\n/** @returns {string} 4-byte ASCII at offset, or \"\" if out of bounds. */\nexport function ascii4(b, off) {\n    if (off + 4 > b.length) return \"\";\n    return String.fromCharCode(b[off], b[off + 1], b[off + 2], b[off + 3]);\n}\n\n/** @returns {number} Byte offset of ASCII needle `s`, or -1. */\nexport function indexOfAscii(b, s, start, end) {\n    const limit = Math.max(0, Math.min(end, b.length) - s.length);\n    for (let i = start; i <= limit; i++) {\n        let ok = true;\n        for (let j = 0; j < s.length; j++) {\n            if (b[i + j] !== s.charCodeAt(j)) {\n                ok = false;\n                break;\n            }\n        }\n        if (ok) return i;\n    }\n    return -1;\n}\n\n/** @returns {number} Unsigned 32-bit big-endian read. */\nexport function u32be(bytes, off) {\n    return ((bytes[off] << 24) >>> 0) | (bytes[off + 1] << 16) | (bytes[off + 2] << 8) | bytes[off + 3];\n}\n\n/** @returns {number} Unsigned 32-bit little-endian read. */\nexport function u32le(bytes, off) {\n    return (bytes[off] | (bytes[off + 1] << 8) | (bytes[off + 2] << 16) | (bytes[off + 3] << 24)) >>> 0;\n}\n\n/** @returns {number} Unsigned 16-bit little-endian read. */\nexport function u16le(bytes, off) {\n    return bytes[off] | (bytes[off + 1] << 8);\n}\n\n/** @returns {BigInt} Unsigned 64-bit little-endian read. */\nexport function u64le(bytes, off) {\n    return BigInt(u32le(bytes, off)) | (BigInt(u32le(bytes, off + 4)) << 32n);\n}\n\n/** @returns {number} Decoded ID3v2 synchsafe integer from four 7-bit bytes. */\nexport function synchsafeToInt(b0, b1, b2, b3) {\n    return ((b0 & 0x7f) << 21) | ((b1 & 0x7f) << 14) | ((b2 & 0x7f) << 7) | (b3 & 0x7f);\n}\n\n/** @returns {string} Decoded UTF-16LE byte range, nulls stripped. */\nexport function decodeUtf16LE(b, off, len) {\n    if (len <= 0 || off + len > b.length) return \"\";\n    try {\n        return new TextDecoder(\"utf-16le\").decode(b.slice(off, off + len)).replace(/\\u0000/g, \"\").trim();\n    } catch {\n        return \"\";\n    }\n}\n\n/** @returns {{valueBytes: Uint8Array, next: number}} Bytes until null terminator, UTF-16 aware. */\nexport function readNullTerminated(bytes, start, encoding) {\n    const isUtf16 = encoding === 1 || encoding === 2;\n    if (!isUtf16) {\n        let i = start;\n        while (i < bytes.length && bytes[i] !== 0x00) i++;\n        return { valueBytes: bytes.slice(start, i), next: i + 1 };\n    }\n    let i = start;\n    while (i + 1 < bytes.length && !(bytes[i] === 0x00 && bytes[i + 1] === 0x00)) i += 2;\n    return { valueBytes: bytes.slice(start, i), next: i + 2 };\n}\n\nconst ID3_ENCODINGS = [\"iso-8859-1\", \"utf-16\", \"utf-16be\", \"utf-8\"];\n\n/** @returns {string} Text decoded using ID3v2 encoding byte (0=latin1, 1=utf16, 2=utf16be, 3=utf8). */\nexport function decodeText(bytes, encoding) {\n    if (!bytes || bytes.length === 0) return \"\";\n    try {\n        return new TextDecoder(ID3_ENCODINGS[encoding] || \"utf-16\").decode(bytes);\n    } catch {\n        return safeUtf8(bytes);\n    }\n}\n\n/** @returns {string} UTF-8 decode with replacement (never throws). */\nexport function safeUtf8(bytes) {\n    try {\n        return new TextDecoder(\"utf-8\", { fatal: false }).decode(bytes);\n    } catch {\n        return \"\";\n    }\n}\n\n/** @returns {string} ISO-8859-1 decode, nulls stripped, trimmed. */\nexport function decodeLatin1Trim(bytes) {\n    return decodeText(bytes, 0).replace(/\\u0000/g, \"\").trim();\n}\n"
  },
  {
    "path": "src/core/lib/AudioMetaSchema.mjs",
    "content": "/**\n * Report skeleton and container detection for audio metadata extraction.\n *\n * @author d0s1nt [d0s1nt@cyberchefaudio]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\n/* eslint-disable camelcase */\n\nimport { ascii4, indexOfAscii } from \"./AudioBytes.mjs\";\n\n/** Builds the empty report skeleton ready for a format parser to populate. */\nexport function makeEmptyReport(filename, byteLength, container) {\n    return {\n        schema_version: \"audio-meta-1.0\",\n        artifact: {\n            filename,\n            byte_length: byteLength,\n            container: { type: container.type, brand: container.brand || null, mime: container.mime || null },\n        },\n        detections: { metadata_systems: [], provenance_systems: [] },\n        tags: {\n            common: {\n                title: null, artist: null, album: null, date: null, track: null,\n                genre: null, comment: null, composer: null, copyright: null, language: null,\n            },\n            raw: {},\n        },\n        embedded: [],\n        provenance: {\n            c2pa: {\n                present: false,\n                embedding: [],\n                manifest_store: { active_manifest_urn: null, instance_id: null, claim_generator: null },\n                assertions: [],\n                signature: {\n                    algorithm: null, signing_time: null,\n                    certificate: { subject_cn: null, issuer_cn: null, serial_number: null },\n                },\n                validation: { validation_state: \"Unknown\", reasons: [], details_raw: null },\n            },\n        },\n        errors: [],\n    };\n}\n\n/** Detects the audio container format from magic bytes. */\nexport function sniffContainer(b) {\n    if (b.length >= 3 && b[0] === 0x49 && b[1] === 0x44 && b[2] === 0x33)\n        return { type: \"mp3\", mime: \"audio/mpeg\" };\n    if (b.length >= 2 && b[0] === 0xff && (b[1] & 0xe0) === 0xe0) {\n        if ((b[1] & 0x06) === 0x00) return { type: \"aac\", mime: \"audio/aac\" };\n        return { type: \"mp3\", mime: \"audio/mpeg\" };\n    }\n    if (b.length >= 8 && b[0] === 0x0b && b[1] === 0x77)\n        return { type: \"ac3\", mime: \"audio/ac3\" };\n    if (b.length >= 16 &&\n        b[0] === 0x30 && b[1] === 0x26 && b[2] === 0xb2 && b[3] === 0x75 &&\n        b[4] === 0x8e && b[5] === 0x66 && b[6] === 0xcf && b[7] === 0x11)\n        return { type: \"wma\", mime: \"audio/x-ms-wma\" };\n    if (b.length >= 12 && ascii4(b, 0) === \"RIFF\" && ascii4(b, 8) === \"WAVE\")\n        return { type: \"wav\", mime: \"audio/wav\" };\n    if (b.length >= 12 && ascii4(b, 0) === \"BW64\" && ascii4(b, 8) === \"WAVE\")\n        return { type: \"bw64\", mime: \"audio/wav\" };\n    if (b.length >= 4 && ascii4(b, 0) === \"fLaC\")\n        return { type: \"flac\", mime: \"audio/flac\" };\n    if (b.length >= 4 && ascii4(b, 0) === \"OggS\") {\n        const idx = indexOfAscii(b, \"OpusHead\", 0, Math.min(b.length, 65536));\n        return idx >= 0 ? { type: \"opus\", mime: \"audio/ogg\" } : { type: \"ogg\", mime: \"audio/ogg\" };\n    }\n    if (b.length >= 12 && ascii4(b, 4) === \"ftyp\") {\n        const brand = ascii4(b, 8);\n        const isM4A = brand === \"M4A \" || brand === \"M4B \" || brand === \"M4P \";\n        return { type: isM4A ? \"m4a\" : \"mp4\", mime: isM4A ? \"audio/mp4\" : \"video/mp4\", brand };\n    }\n    if (b.length >= 12 && ascii4(b, 0) === \"FORM\") {\n        const formType = ascii4(b, 8);\n        if (formType === \"AIFF\" || formType === \"AIFC\") return { type: \"aiff\", mime: \"audio/aiff\", brand: formType };\n    }\n    return { type: \"unknown\", mime: null };\n}\n"
  },
  {
    "path": "src/core/lib/AudioParsers.mjs",
    "content": "/**\n * Format-specific audio metadata parsers.\n *\n * @author d0s1nt [d0s1nt@cyberchefaudio]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\n/* eslint-disable camelcase */\n\nimport {\n    ascii4, indexOfAscii,\n    u32be, u32le, u16le, u64le, synchsafeToInt,\n    decodeUtf16LE, readNullTerminated, decodeText,\n    safeUtf8, decodeLatin1Trim,\n} from \"./AudioBytes.mjs\";\n\n/** Parses MP3 metadata: ID3v2 frames, ID3v1 footer, APEv2 tags. */\nexport function parseMp3(b, report) {\n    processId3v2(b, report);\n    processId3v1(b, report);\n\n    const ape = parseApeV2BestEffort(b);\n    if (ape) {\n        report.detections.metadata_systems.push(\"apev2\");\n        report.tags.raw.apev2 = ape;\n    }\n}\n\n/** Iterates ID3v2 frames and populates the report. */\nfunction processId3v2(b, report) {\n    report.detections.metadata_systems.push(\"id3v2\");\n\n    const id3 = parseId3v2(b);\n    report.tags.raw.id3v2 = id3 ? { header: id3.header, frames: [] } : null;\n\n    if (id3) {\n        for (const f of id3.frames) {\n            const entry = { id: f.id, size: f.size, description: ID3_FRAME_DESCRIPTIONS[f.id] || null };\n\n            if (f.id[0] === \"T\" && f.id !== \"TXXX\") {\n                const text = f.data?.length >= 1 ?\n                    decodeText(f.data.slice(1), f.data[0]).replace(/\\u0000/g, \"\").trim() :\n                    \"\";\n                entry.decoded = text;\n                if (f.id === \"TLEN\") {\n                    const ms = normalizeTlen(text);\n                    if (ms !== null) entry.normalized_ms = ms;\n                }\n                mapCommonId3(report, f.id, text);\n            } else if (f.id === \"TXXX\") {\n                const txxx = decodeTxxx(f.data);\n                entry.decoded = txxx;\n                if (!report.tags.raw.id3v2.txxx) report.tags.raw.id3v2.txxx = [];\n                report.tags.raw.id3v2.txxx.push(txxx);\n            } else if (f.id === \"COMM\") {\n                const comm = decodeCommFrame(f.data);\n                entry.decoded = comm;\n                if (comm?.text && !report.tags.common.comment) report.tags.common.comment = comm.text;\n            } else if (f.id === \"GEOB\") {\n                processGeobFrame(f, entry, report);\n            }\n\n            report.tags.raw.id3v2.frames.push(entry);\n        }\n    } else {\n        report.detections.metadata_systems = report.detections.metadata_systems.filter((x) => x !== \"id3v2\");\n    }\n}\n\n/** Parses GEOB frame contents, populates entry, embedded objects, and C2PA provenance. */\nfunction processGeobFrame(f, entry, report) {\n    const d = f.data, enc = d[0];\n    let off = 1;\n    const mime = readNullTerminated(d, off, 0);\n    const mimeType = decodeLatin1Trim(mime.valueBytes);\n    off = mime.next;\n    const file = readNullTerminated(d, off, enc);\n    const filename = decodeText(file.valueBytes, enc).replace(/\\u0000/g, \"\").trim();\n    off = file.next;\n    const desc = readNullTerminated(d, off, enc);\n    const description = decodeText(desc.valueBytes, enc).replace(/\\u0000/g, \"\").trim();\n    off = desc.next;\n    const objLen = d.length - off;\n\n    entry.geob = { mimeType, filename, description, object_bytes: objLen };\n    const geobId = `geob_${report.embedded.filter((x) => x.source === \"id3v2:GEOB\").length}`;\n    report.embedded.push({\n        id: geobId, source: \"id3v2:GEOB\",\n        content_type: mimeType || null, byte_length: objLen,\n        description: description || null, filename: filename || null,\n    });\n\n    const mt = (mimeType || \"\").toLowerCase();\n    if (mt.includes(\"c2pa\") || mt.includes(\"jumbf\") || mt.includes(\"application/x-c2pa-manifest-store\")) {\n        report.provenance.c2pa.present = true;\n        report.provenance.c2pa.embedding.push({\n            carrier: \"id3v2:GEOB\", content_type: mimeType || null, byte_length: objLen,\n        });\n    }\n}\n\n/** Processes the 128-byte ID3v1 footer tag. */\nfunction processId3v1(b, report) {\n    const id3v1 = parseId3v1(b);\n    if (!id3v1) return;\n\n    report.detections.metadata_systems.push(\"id3v1\");\n    report.tags.raw.id3v1 = id3v1;\n    mapCommon(report, id3v1, ID3V1_TO_COMMON);\n}\n\n/** Parses WAV/BWF/BW64 RIFF chunks: LIST/INFO, bext, iXML, axml, ds64. */\nexport function parseRiffWave(b, report, maxTextBytes) {\n    report.detections.metadata_systems.push(\"riff_info\");\n\n    const chunks = enumerateChunks(b, 12, b.length, 50000);\n    const riff = { chunks: [], info: null, bext: null, ixml: null, axml: null, ds64: null };\n\n    const info = {};\n    for (const c of chunks) {\n        riff.chunks.push({ id: c.id, size: c.size, offset: c.dataOff });\n        processRiffChunk(b, c, riff, info, report, maxTextBytes);\n    }\n\n    riff.info = Object.keys(info).length ? info : null;\n    report.tags.raw.riff = riff;\n    if (riff.info) mapCommon(report, riff.info, RIFF_TO_COMMON);\n}\n\n/** Processes a single RIFF chunk, updating riff state and the report. */\nfunction processRiffChunk(b, c, riff, info, report, maxTextBytes) {\n    if (c.id === \"ds64\") {\n        riff.ds64 = { present: true, size: c.size };\n        if (!report.detections.metadata_systems.includes(\"bw64_ds64\")) report.detections.metadata_systems.push(\"bw64_ds64\");\n        if (report.artifact.container.type === \"wav\") report.artifact.container.type = \"bw64\";\n    }\n\n    if (c.id === \"LIST\" && ascii4(b, c.dataOff) === \"INFO\") {\n        for (const s of enumerateChunks(b, c.dataOff + 4, c.dataOff + c.size, 10000))\n            info[s.id] = decodeLatin1Trim(b.slice(s.dataOff, s.dataOff + s.size));\n    }\n\n    if (c.id === \"bext\") {\n        if (!report.detections.metadata_systems.includes(\"bwf_bext\")) report.detections.metadata_systems.push(\"bwf_bext\");\n        riff.bext = parseBext(b, c.dataOff, c.size);\n    }\n\n    if (c.id === \"iXML\" || c.id === \"axml\") {\n        const key = c.id === \"iXML\" ? \"ixml\" : \"axml\";\n        if (!report.detections.metadata_systems.includes(key)) report.detections.metadata_systems.push(key);\n        const payload = b.slice(c.dataOff, c.dataOff + c.size);\n        riff[key] = { xml: safeUtf8(payload.slice(0, Math.min(payload.length, maxTextBytes))), truncated: payload.length > maxTextBytes };\n        report.embedded.push({\n            id: `${key}_0`, source: `riff:${c.id}`, content_type: \"application/xml\",\n            byte_length: payload.length, description: `${c.id} chunk`, filename: null,\n        });\n    }\n}\n\n/** Parses FLAC metablocks: STREAMINFO, Vorbis Comment, PICTURE. */\nexport function parseFlac(b, report, maxTextBytes) {\n    report.detections.metadata_systems.push(\"flac_metablocks\");\n\n    const blocks = parseFlacMetaBlocks(b);\n    report.tags.raw.flac = { blocks: [] };\n\n    for (const blk of blocks) {\n        report.tags.raw.flac.blocks.push({ type: blk.typeName, length: blk.length });\n\n        if (blk.typeName === \"VORBIS_COMMENT\") {\n            if (!report.detections.metadata_systems.includes(\"vorbis_comments\")) report.detections.metadata_systems.push(\"vorbis_comments\");\n            const vc = parseVorbisComment(blk.data);\n            report.tags.raw.vorbis_comments = vc;\n            mapVorbisCommon(report, vc);\n        } else if (blk.typeName === \"PICTURE\") {\n            const pic = parseFlacPicture(blk.data, maxTextBytes);\n            report.embedded.push({\n                id: `cover_art_${report.embedded.filter((x) => x.id.startsWith(\"cover_art_\")).length}`,\n                source: \"flac:PICTURE\", content_type: pic.mime || null,\n                byte_length: pic.dataLength, description: pic.description || null, filename: null,\n            });\n        }\n    }\n}\n\n/** Parses OGG/Opus Vorbis comments. */\nexport function parseOgg(b, report) {\n    if (!report.detections.metadata_systems.includes(\"ogg_opus_tags\")) report.detections.metadata_systems.push(\"ogg_opus_tags\");\n\n    const scanEnd = Math.min(b.length, 1024 * 1024);\n    let tags = null;\n    const opusTagsIdx = indexOfAscii(b, \"OpusTags\", 0, scanEnd);\n    if (opusTagsIdx >= 0) {\n        report.artifact.container.type = \"opus\";\n        tags = parseVorbisComment(b.slice(opusTagsIdx + 8, scanEnd));\n    } else {\n        const vorbisIdx = indexOfAscii(b, \"\\x03vorbis\", 0, scanEnd);\n        if (vorbisIdx >= 0) tags = parseVorbisComment(b.slice(vorbisIdx + 7, scanEnd));\n    }\n\n    report.tags.raw.ogg = { has_opustags: opusTagsIdx >= 0, has_vorbis_comment: !!tags };\n\n    if (tags) {\n        if (!report.detections.metadata_systems.includes(\"vorbis_comments\")) report.detections.metadata_systems.push(\"vorbis_comments\");\n        report.tags.raw.vorbis_comments = tags;\n        mapVorbisCommon(report, tags);\n    }\n}\n\n/** Best-effort top-level atom scan for MP4/M4A. */\nexport function parseMp4BestEffort(b, report) {\n    report.detections.metadata_systems.push(\"mp4_atoms\");\n    const atoms = [];\n\n    let off = 0;\n    while (off + 8 <= b.length && atoms.length < 2000) {\n        const size = u32be(b, off);\n        const type = ascii4(b, off + 4);\n        if (size < 8) break;\n        atoms.push({ type, size, offset: off });\n        off += size;\n    }\n\n    report.tags.raw.mp4 = {\n        top_level_atoms: atoms.slice(0, 200),\n        hints: {\n            hasMoov: atoms.some((a) => a.type === \"moov\"),\n            hasUdta: atoms.some((a) => a.type === \"udta\"),\n            hasMeta: atoms.some((a) => a.type === \"meta\"),\n            hasIlst: atoms.some((a) => a.type === \"ilst\"),\n        },\n    };\n}\n\n/** Best-effort AIFF/AIFC chunk scanning for NAME, AUTH, ANNO. */\nexport function parseAiffBestEffort(b, report, maxTextBytes) {\n    report.detections.metadata_systems.push(\"aiff_chunks\");\n    let off = 12;\n    const chunks = [];\n    while (off + 8 <= b.length && chunks.length < 2000) {\n        const id = ascii4(b, off);\n        const size = u32be(b, off + 4);\n        const dataOff = off + 8;\n        chunks.push({ id, size, offset: off });\n\n        if ([\"NAME\", \"AUTH\", \"ANNO\", \"(c) \"].includes(id)) {\n            const txt = safeUtf8(b.slice(dataOff, dataOff + Math.min(size, maxTextBytes)));\n            if (!report.tags.raw.aiff) report.tags.raw.aiff = { chunks: [] };\n            report.tags.raw.aiff.chunks.push({ id, value: txt, truncated: size > maxTextBytes });\n        }\n\n        off = dataOff + size + (size % 2);\n    }\n\n    if (!report.tags.raw.aiff) report.tags.raw.aiff = {};\n    report.tags.raw.aiff.chunk_index = chunks.slice(0, 500);\n\n    const nameChunk = report.tags.raw.aiff?.chunks?.find((ch) => ch.id === \"NAME\")?.value;\n    if (nameChunk) report.tags.common.title = report.tags.common.title || nameChunk;\n}\n\nconst AAC_SAMPLE_RATES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];\nconst AAC_PROFILES = [\"Main\", \"LC\", \"SSR\", \"LTP\"];\nconst AAC_CHANNELS = [\"defined in AOT\", \"mono\", \"stereo\", \"3.0\", \"4.0\", \"5.0\", \"5.1\", \"7.1\"];\n\n/** Parses AAC ADTS frame header for audio parameters. */\nexport function parseAacAdts(b, report) {\n    report.detections.metadata_systems.push(\"adts_header\");\n    if (b.length < 7) return;\n\n    const id = (b[1] >> 3) & 0x01;\n    const profile = (b[2] >> 6) & 0x03;\n    const freqIdx = (b[2] >> 2) & 0x0f;\n    const chanCfg = ((b[2] & 0x01) << 2) | ((b[3] >> 6) & 0x03);\n\n    report.tags.raw.aac = {\n        mpeg_version: id === 1 ? \"MPEG-2\" : \"MPEG-4\",\n        profile: AAC_PROFILES[profile] || `Profile ${profile}`,\n        sample_rate: AAC_SAMPLE_RATES[freqIdx] || null,\n        sample_rate_index: freqIdx,\n        channel_configuration: chanCfg,\n        channel_description: AAC_CHANNELS[chanCfg] || null,\n    };\n}\n\nconst AC3_SAMPLE_RATES = [48000, 44100, 32000];\nconst AC3_BITRATES = [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640];\nconst AC3_ACMODES = [\n    \"2.0 (Ch1+Ch2)\", \"1.0 (C)\", \"2.0 (L R)\", \"3.0 (L C R)\",\n    \"2.1 (L R S)\", \"3.1 (L C R S)\", \"2.2 (L R SL SR)\", \"3.2 (L C R SL SR)\",\n];\n\n/** Parses AC3 (Dolby Digital) bitstream info. */\nexport function parseAc3(b, report) {\n    report.detections.metadata_systems.push(\"ac3_bsi\");\n    if (b.length < 8) return;\n\n    const fscod = (b[4] >> 6) & 0x03;\n    const frmsizecod = b[4] & 0x3f;\n    const bsid = (b[5] >> 3) & 0x1f;\n    const bsmod = b[5] & 0x07;\n    const acmod = (b[6] >> 5) & 0x07;\n\n    report.tags.raw.ac3 = {\n        sample_rate: AC3_SAMPLE_RATES[fscod] || null,\n        fscod,\n        bitrate_kbps: AC3_BITRATES[frmsizecod >> 1] || null,\n        frmsizecod, bsid, bsmod, acmod,\n        channel_layout: AC3_ACMODES[acmod] || null,\n    };\n}\n\n/** Parses WMA files (ASF container) for content description metadata. */\nexport function parseWmaAsf(b, report) {\n    report.detections.metadata_systems.push(\"asf_header\");\n    if (b.length < 30) return;\n\n    const headerSize = Number(u64le(b, 16));\n    const numObjects = u32le(b, 24);\n    const headerEnd = Math.min(b.length, headerSize);\n\n    const objects = [];\n    let off = 30;\n\n    for (let i = 0; i < numObjects && off + 24 <= headerEnd; i++) {\n        const guid4 = [b[off], b[off + 1], b[off + 2], b[off + 3]];\n        const objSize = Number(u64le(b, off + 16));\n        if (objSize < 24 || off + objSize > headerEnd) break;\n\n        const dataOff = off + 24;\n        const dataLen = objSize - 24;\n\n        if (guid4[0] === 0x33 && guid4[1] === 0x26 && guid4[2] === 0xb2 && guid4[3] === 0x75 && dataLen >= 10) {\n            const cd = parseAsfContentDescription(b, dataOff);\n            if (!report.detections.metadata_systems.includes(\"asf_content_desc\"))\n                report.detections.metadata_systems.push(\"asf_content_desc\");\n            if (!report.tags.raw.asf) report.tags.raw.asf = {};\n            report.tags.raw.asf.content_description = cd;\n            mapCommon(report, cd, ASF_CD_TO_COMMON);\n        }\n\n        if (guid4[0] === 0x40 && guid4[1] === 0xa4 && guid4[2] === 0xd0 && guid4[3] === 0xd2 && dataLen >= 2) {\n            const ext = parseAsfExtContentDescription(b, dataOff, dataOff + dataLen);\n            if (!report.detections.metadata_systems.includes(\"asf_ext_content_desc\"))\n                report.detections.metadata_systems.push(\"asf_ext_content_desc\");\n            if (!report.tags.raw.asf) report.tags.raw.asf = {};\n            report.tags.raw.asf.extended_content = ext;\n\n            const c = report.tags.common;\n            for (const d of ext) {\n                const field = WMA_TO_COMMON[(d.name || \"\").toUpperCase()];\n                if (field && d.value) c[field] = c[field] || d.value;\n            }\n        }\n\n        objects.push({ guid_prefix: guid4.map(x => x.toString(16).padStart(2, \"0\")).join(\"\"), size: objSize });\n        off += objSize;\n    }\n\n    if (!report.tags.raw.asf) report.tags.raw.asf = {};\n    report.tags.raw.asf.header_objects = objects;\n}\n\nconst ID3_FRAME_DESCRIPTIONS = {\n    TIT2: \"Title/songname/content description\", TPE1: \"Lead performer(s)/Soloist(s)\",\n    TRCK: \"Track number/Position in set\", TALB: \"Album/Movie/Show title\",\n    TDRC: \"Recording time\", TYER: \"Year\", TCON: \"Content type\",\n    TPE2: \"Band/orchestra/accompaniment\", TLEN: \"Length (ms)\", TCOM: \"Composer\",\n    COMM: \"Comments\", APIC: \"Attached picture\", GEOB: \"General encapsulated object\",\n    TXXX: \"User defined text information frame\", UFID: \"Unique file identifier\", PRIV: \"Private frame\",\n};\n\nconst ID3_TO_COMMON = {\n    TIT2: \"title\", TPE1: \"artist\", TALB: \"album\", TDRC: \"date\", TYER: \"date\",\n    TRCK: \"track\", TCON: \"genre\", COMM: \"comment\", TCOM: \"composer\", TCOP: \"copyright\", TLAN: \"language\",\n};\nconst VORBIS_TO_COMMON = {\n    TITLE: \"title\", ARTIST: \"artist\", ALBUM: \"album\", DATE: \"date\",\n    TRACKNUMBER: \"track\", GENRE: \"genre\", COMMENT: \"comment\", COMPOSER: \"composer\", LANGUAGE: \"language\",\n};\nconst WMA_TO_COMMON = {\n    \"WM/ALBUMTITLE\": \"album\", \"WM/GENRE\": \"genre\", \"WM/YEAR\": \"date\",\n    \"WM/TRACKNUMBER\": \"track\", \"WM/COMPOSER\": \"composer\", \"WM/LANGUAGE\": \"language\",\n};\nconst ID3V1_TO_COMMON = { title: \"title\", artist: \"artist\", album: \"album\", year: \"date\", comment: \"comment\", genre: \"genre\", track: \"track\" };\nconst RIFF_TO_COMMON = { INAM: \"title\", IART: \"artist\", ICMT: \"comment\", IGNR: \"genre\", ICRD: \"date\", ICOP: \"copyright\" };\nconst ASF_CD_TO_COMMON = { title: \"title\", author: \"artist\", copyright: \"copyright\", description: \"comment\" };\n\n/** Maps source object fields to the common tags layer via a mapping table. */\nfunction mapCommon(report, source, mapping) {\n    const c = report.tags.common;\n    for (const [sk, ck] of Object.entries(mapping))\n        c[ck] = c[ck] || source[sk] || null;\n}\n\n/** Maps an ID3v2 frame value to the common tags layer. */\nfunction mapCommonId3(report, frameId, text) {\n    const field = ID3_TO_COMMON[frameId];\n    if (field) report.tags.common[field] = report.tags.common[field] || text || null;\n}\n\n/** Decodes an ID3v2 COMM (Comments) frame. */\nfunction decodeCommFrame(data) {\n    if (!data || data.length < 5) return null;\n    const enc = data[0];\n    const language = String.fromCharCode(data[1], data[2], data[3]);\n    const { valueBytes: descBytes, next } = readNullTerminated(data, 4, enc);\n    const short_description = decodeText(descBytes, enc).replace(/\\u0000/g, \"\").trim() || null;\n    const text = decodeText(data.slice(next), enc).replace(/\\u0000/g, \"\").trim() || null;\n    return { language, short_description, text };\n}\n\n/** Normalizes TLEN to integer milliseconds. */\nfunction normalizeTlen(s) {\n    if (!s) return null;\n    if (/^\\s*\\d+\\s*$/.test(s)) return parseInt(s.trim(), 10);\n    const f = Number(s);\n    if (Number.isFinite(f) && f > 0 && f < 100000) return Math.round(f * 1000);\n    return null;\n}\n\n/** Parses the ID3v2 tag header and frames. */\nfunction parseId3v2(mp3) {\n    if (mp3.length < 10 || mp3[0] !== 0x49 || mp3[1] !== 0x44 || mp3[2] !== 0x33) return null;\n\n    const major = mp3[3], minor = mp3[4], flags = mp3[5];\n    const tagSize = synchsafeToInt(mp3[6], mp3[7], mp3[8], mp3[9]);\n    let offset = 10;\n    const end = 10 + tagSize;\n\n    const frames = [];\n    while (offset + 10 <= end) {\n        const id = String.fromCharCode(mp3[offset], mp3[offset + 1], mp3[offset + 2], mp3[offset + 3]);\n        if (!/^[A-Z0-9]{4}$/.test(id)) break;\n        const size = major === 4 ?\n            synchsafeToInt(mp3[offset + 4], mp3[offset + 5], mp3[offset + 6], mp3[offset + 7]) :\n            u32be(mp3, offset + 4);\n        offset += 10;\n        if (size <= 0 || offset + size > mp3.length) break;\n        frames.push({ id, size, data: mp3.slice(offset, offset + size) });\n        offset += size;\n    }\n\n    return { header: { version: `${major}.${minor}`, flags, tag_size: tagSize }, frames };\n}\n\n/** Parses the 128-byte ID3v1 tag at the end of the file. */\nfunction parseId3v1(b) {\n    if (b.length < 128) return null;\n    const off = b.length - 128;\n    if (b[off] !== 0x54 || b[off + 1] !== 0x41 || b[off + 2] !== 0x47) return null;\n\n    let track = null;\n    if (b[off + 125] === 0x00 && b[off + 126] !== 0x00) track = String(b[off + 126]);\n\n    return {\n        title: decodeLatin1Trim(b.slice(off + 3, off + 33)),\n        artist: decodeLatin1Trim(b.slice(off + 33, off + 63)),\n        album: decodeLatin1Trim(b.slice(off + 63, off + 93)),\n        year: decodeLatin1Trim(b.slice(off + 93, off + 97)),\n        comment: decodeLatin1Trim(b.slice(off + 97, off + 127)),\n        track, genre: String(b[off + 127]),\n    };\n}\n\n/** Decodes an ID3v2 TXXX (user-defined text) frame. */\nfunction decodeTxxx(data) {\n    if (!data || data.length < 2) return null;\n    const enc = data[0];\n    const { valueBytes: descBytes, next } = readNullTerminated(data, 1, enc);\n    const desc = decodeText(descBytes, enc).replace(/\\u0000/g, \"\").trim();\n    const val = decodeText(data.slice(next), enc).replace(/\\u0000/g, \"\").trim();\n    return { description: desc || null, value: val || null };\n}\n\n/** Best-effort APEv2 tag parser scanning the last 32 KB. */\nfunction parseApeV2BestEffort(b) {\n    const scanStart = Math.max(0, b.length - 32768);\n    const idx = indexOfAscii(b, \"APETAGEX\", scanStart, b.length);\n    if (idx < 0) return null;\n    if (idx + 32 > b.length) return { present: true, warning: \"APETAGEX found but footer truncated.\" };\n\n    const ver = u32le(b, idx + 8), size = u32le(b, idx + 12);\n    const count = u32le(b, idx + 16), flags = u32le(b, idx + 20);\n\n    const tagStart = idx + 32 - size;\n    if (tagStart < 0 || tagStart >= b.length)\n        return { present: true, version: ver, size, count, flags, warning: \"APEv2 bounds invalid (non-standard placement).\" };\n\n    const items = [];\n    let off = tagStart + 32;\n    const end = Math.min(b.length, idx);\n    while (off + 8 < end && items.length < 5000) {\n        const valueSize = u32le(b, off), itemFlags = u32le(b, off + 4);\n        off += 8;\n        let keyEnd = off;\n        while (keyEnd < end && b[keyEnd] !== 0x00) keyEnd++;\n        const key = decodeLatin1Trim(b.slice(off, keyEnd));\n        off = keyEnd + 1;\n        if (!key || off + valueSize > end) break;\n        const value = safeUtf8(b.slice(off, off + valueSize)).replace(/\\u0000/g, \"\").trim();\n        off += valueSize;\n        items.push({ key, value, flags: itemFlags });\n    }\n\n    return { present: true, version: ver, size, count, flags, items };\n}\n\n/** Enumerates RIFF-style chunks (id + LE32 size) within a byte range, padding to even. */\nfunction enumerateChunks(b, start, end, maxCount) {\n    const chunks = [];\n    let off = start;\n    while (off + 8 <= end && chunks.length < maxCount) {\n        const id = ascii4(b, off);\n        const size = u32le(b, off + 4);\n        const dataOff = off + 8;\n        if (dataOff + size > end) break;\n        chunks.push({ id, size, dataOff });\n        off = dataOff + size + (size % 2);\n    }\n    return chunks;\n}\n\n/** Parses a BWF bext chunk. */\nfunction parseBext(b, off, size) {\n    const slice = b.slice(off, off + size);\n    const timeRefLow = u32le(slice, 338), timeRefHigh = u32le(slice, 342);\n    return {\n        description: decodeLatin1Trim(slice.slice(0, 256)) || null,\n        originator: decodeLatin1Trim(slice.slice(256, 288)) || null,\n        originator_reference: decodeLatin1Trim(slice.slice(288, 320)) || null,\n        origination_date: decodeLatin1Trim(slice.slice(320, 330)) || null,\n        origination_time: decodeLatin1Trim(slice.slice(330, 338)) || null,\n        time_reference_samples: ((BigInt(timeRefHigh) << 32n) | BigInt(timeRefLow)).toString(),\n    };\n}\n\nconst FLAC_TYPE_NAMES = { 0: \"STREAMINFO\", 1: \"PADDING\", 2: \"APPLICATION\", 3: \"SEEKTABLE\", 4: \"VORBIS_COMMENT\", 5: \"CUESHEET\", 6: \"PICTURE\" };\n\n/** Parses FLAC metadata blocks following the \"fLaC\" marker. */\nfunction parseFlacMetaBlocks(b) {\n    const blocks = [];\n    let off = 4;\n    while (off + 4 <= b.length && blocks.length < 10000) {\n        const header = b[off];\n        const isLast = (header & 0x80) !== 0;\n        const type = header & 0x7f;\n        const len = (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3];\n        off += 4;\n        if (off + len > b.length) break;\n        blocks.push({ type, typeName: FLAC_TYPE_NAMES[type] || `TYPE_${type}`, length: len, data: b.slice(off, off + len) });\n        off += len;\n        if (isLast) break;\n    }\n    return blocks;\n}\n\n/** Parses a Vorbis Comment block (used by FLAC and OGG). */\nfunction parseVorbisComment(buf) {\n    let off = 0;\n    const vendorLen = u32le(buf, off); off += 4;\n    if (off + vendorLen > buf.length) return { vendor: null, comments: [], warning: \"vendor_len out of bounds\" };\n    const vendor = safeUtf8(buf.slice(off, off + vendorLen)); off += vendorLen;\n    const count = u32le(buf, off); off += 4;\n\n    const comments = [];\n    for (let i = 0; i < count && off + 4 <= buf.length && comments.length < 20000; i++) {\n        const l = u32le(buf, off); off += 4;\n        if (off + l > buf.length) break;\n        const s = safeUtf8(buf.slice(off, off + l)); off += l;\n        const eq = s.indexOf(\"=\");\n        if (eq > 0) comments.push({ key: s.slice(0, eq).toUpperCase(), value: s.slice(eq + 1) });\n    }\n    return { vendor, comments };\n}\n\n/** Maps Vorbis Comment fields to the common tags layer. */\nfunction mapVorbisCommon(report, vc) {\n    const c = report.tags.common;\n    for (const [vk, ck] of Object.entries(VORBIS_TO_COMMON))\n        c[ck] = c[ck] || vc.comments?.find((x) => x.key === vk)?.value || null;\n}\n\n/** Parses a FLAC PICTURE metadata block (extracts mime, description, data length). */\nfunction parseFlacPicture(data, maxTextBytes) {\n    let off = 4;\n    const mimeLen = u32be(data, off); off += 4;\n    const mime = safeUtf8(data.slice(off, off + Math.min(mimeLen, maxTextBytes))); off += mimeLen;\n    const descLen = u32be(data, off); off += 4;\n    const description = safeUtf8(data.slice(off, off + Math.min(descLen, maxTextBytes))); off += descLen + 16;\n    return { mime, description, dataLength: u32be(data, off) };\n}\n\n/** Parses the ASF Content Description Object fields. */\nfunction parseAsfContentDescription(b, off) {\n    const titleLen = u16le(b, off), authorLen = u16le(b, off + 2);\n    const copyrightLen = u16le(b, off + 4), descLen = u16le(b, off + 6), ratingLen = u16le(b, off + 8);\n    let pos = off + 10;\n    const title = decodeUtf16LE(b, pos, titleLen); pos += titleLen;\n    const author = decodeUtf16LE(b, pos, authorLen); pos += authorLen;\n    const copyright = decodeUtf16LE(b, pos, copyrightLen); pos += copyrightLen;\n    const description = decodeUtf16LE(b, pos, descLen); pos += descLen;\n    const rating = decodeUtf16LE(b, pos, ratingLen);\n    return { title, author, copyright, description, rating };\n}\n\n/** Parses the ASF Extended Content Description Object descriptors. */\nfunction parseAsfExtContentDescription(b, off, end) {\n    const count = u16le(b, off);\n    let pos = off + 2;\n    const descriptors = [];\n    for (let i = 0; i < count && pos + 6 <= end && descriptors.length < 5000; i++) {\n        const nameLen = u16le(b, pos); pos += 2;\n        if (pos + nameLen > end) break;\n        const name = decodeUtf16LE(b, pos, nameLen); pos += nameLen;\n        const valueType = u16le(b, pos); pos += 2;\n        const valueLen = u16le(b, pos); pos += 2;\n        if (pos + valueLen > end) break;\n        let value;\n        if (valueType === 0) value = decodeUtf16LE(b, pos, valueLen);\n        else if (valueType === 3) value = u32le(b, pos);\n        else if (valueType === 5) value = u16le(b, pos);\n        else if (valueType === 2) value = u32le(b, pos) !== 0;\n        else value = `(${valueLen} bytes, type ${valueType})`;\n        pos += valueLen;\n        descriptors.push({ name, value_type: valueType, value });\n    }\n    return descriptors;\n}\n"
  },
  {
    "path": "src/core/lib/BCD.mjs",
    "content": "/**\n * Binary Code Decimal resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n/**\n * BCD encoding schemes.\n */\nexport const ENCODING_SCHEME = [\n    \"8 4 2 1\",\n    \"7 4 2 1\",\n    \"4 2 2 1\",\n    \"2 4 2 1\",\n    \"8 4 -2 -1\",\n    \"Excess-3\",\n    \"IBM 8 4 2 1\",\n];\n\n/**\n * Lookup table for the binary value of each digit representation.\n *\n * I wrote a very nice algorithm to generate 8 4 2 1 encoding programmatically,\n * but unfortunately it's much easier (if less elegant) to use lookup tables\n * when supporting multiple encoding schemes.\n *\n * \"Practicality beats purity\" - PEP 20\n *\n * In some schemes it is possible to represent the same value in multiple ways.\n * For instance, in 4 2 2 1 encoding, 0100 and 0010 both represent 2. Support\n * has not yet been added for this.\n */\nexport const ENCODING_LOOKUP = {\n    \"8 4 2 1\":     [0,  1,  2,  3,  4,  5,  6,  7,  8,  9],\n    \"7 4 2 1\":     [0,  1,  2,  3,  4,  5,  6,  8,  9,  10],\n    \"4 2 2 1\":     [0,  1,  4,  5,  8,  9,  12, 13, 14, 15],\n    \"2 4 2 1\":     [0,  1,  2,  3,  4,  11, 12, 13, 14, 15],\n    \"8 4 -2 -1\":   [0,  7,  6,  5,  4,  11, 10, 9,  8,  15],\n    \"Excess-3\":    [3,  4,  5,  6,  7,  8,  9,  10, 11, 12],\n    \"IBM 8 4 2 1\": [10, 1,  2,  3,  4,  5,  6,  7,  8,  9],\n};\n\n/**\n * BCD formats.\n */\nexport const FORMAT = [\"Nibbles\", \"Bytes\", \"Raw\"];\n"
  },
  {
    "path": "src/core/lib/Bacon.mjs",
    "content": "/**\n * Bacon Cipher resources.\n *\n * @author Karsten Silkenbäumer [github.com/kassi]\n * @copyright Karsten Silkenbäumer 2019\n * @license Apache-2.0\n */\n\n/**\n * Bacon definitions.\n */\nexport const BACON_ALPHABETS = {\n    \"Standard (I=J and U=V)\": {\n        alphabet: \"ABCDEFGHIKLMNOPQRSTUWXYZ\",\n        codes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23]\n    },\n    \"Complete\": {\n        alphabet: \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    }\n};\nexport const BACON_TRANSLATION_01 = \"0/1\";\nexport const BACON_TRANSLATION_AB = \"A/B\";\nexport const BACON_TRANSLATION_CASE = \"Case\";\nexport const BACON_TRANSLATION_AMNZ = \"A-M/N-Z first letter\";\nexport const BACON_TRANSLATIONS = [\n    BACON_TRANSLATION_01,\n    BACON_TRANSLATION_AB,\n    BACON_TRANSLATION_CASE,\n    BACON_TRANSLATION_AMNZ,\n];\nexport const BACON_TRANSLATIONS_FOR_ENCODING = [\n    BACON_TRANSLATION_01,\n    BACON_TRANSLATION_AB\n];\nexport const BACON_CLEARER_MAP = {\n    [BACON_TRANSLATION_01]: /[^01]/g,\n    [BACON_TRANSLATION_AB]: /[^ABab]/g,\n    [BACON_TRANSLATION_CASE]: /[^A-Za-z]/g,\n};\nexport const BACON_NORMALIZE_MAP = {\n    [BACON_TRANSLATION_AB]: {\n        \"A\": \"0\",\n        \"B\": \"1\",\n        \"a\": \"0\",\n        \"b\": \"1\"\n    },\n};\n\n/**\n * Swaps zeros to ones and ones to zeros.\n *\n * @param {string} data\n * @returns {string}\n *\n * @example\n * // returns \"11001 01010\"\n * swapZeroAndOne(\"00110 10101\");\n */\nexport function swapZeroAndOne(string) {\n    return string.replace(/[01]/g, function (c) {\n        return {\n            \"0\": \"1\",\n            \"1\": \"0\"\n        }[c];\n    });\n}\n"
  },
  {
    "path": "src/core/lib/Base32.mjs",
    "content": "// import Utils from \"../Utils.mjs\";\n\n/**\n * Base32 resources.\n *\n * @author Peter C-S [petercs@purelymail.com]\n * @license Apache-2.0\n */\n\n/**\n * Base32 alphabets.\n */\nexport const ALPHABET_OPTIONS = [\n    {\n        name: \"Standard\", // https://www.rfc-editor.org/rfc/rfc4648#section-6\n        value: \"A-Z2-7=\",\n    },\n    {\n        name: \"Hex Extended\", // https://www.rfc-editor.org/rfc/rfc4648#section-7\n        value: \"0-9A-V=\",\n    },\n];\n\n"
  },
  {
    "path": "src/core/lib/Base45.mjs",
    "content": "/**\n * Base45 resources.\n *\n * @author Thomas Weißschuh [thomas@t-8ch.de]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\n/**\n * Highlight to Base45\n */\nexport function highlightToBase45(pos, args) {\n    pos[0].start = Math.floor(pos[0].start / 2) * 3;\n    pos[0].end = Math.ceil(pos[0].end / 2) * 3;\n    return pos;\n}\n\n/**\n * Highlight from Base45\n */\nexport function highlightFromBase45(pos, args) {\n    pos[0].start = Math.floor(pos[0].start / 3) * 2;\n    pos[0].end = Math.ceil(pos[0].end / 3) * 2;\n    return pos;\n}\n\nexport const ALPHABET = \"0-9A-Z $%*+\\\\-./:\";\n"
  },
  {
    "path": "src/core/lib/Base58.mjs",
    "content": "/**\n * Base58 resources.\n *\n * @author tlwr [toby@toby.codes]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n/**\n * Base58 alphabet options.\n */\nexport const ALPHABET_OPTIONS = [\n    {\n        name: \"Bitcoin\",\n        value: \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\",\n    },\n    {\n        name: \"Ripple\",\n        value: \"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz\",\n    },\n];\n"
  },
  {
    "path": "src/core/lib/Base64.mjs",
    "content": "/**\n * Base64 functions.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Base64's the input byte array using the given alphabet, returning a string.\n *\n * @param {byteArray|Uint8Array|ArrayBuffer|string} data\n * @param {string} [alphabet=\"A-Za-z0-9+/=\"]\n * @returns {string}\n *\n * @example\n * // returns \"SGVsbG8=\"\n * toBase64([72, 101, 108, 108, 111]);\n *\n * // returns \"SGVsbG8=\"\n * toBase64(\"Hello\");\n */\nexport function toBase64(data, alphabet=\"A-Za-z0-9+/=\") {\n    if (!data) return \"\";\n    if (typeof data == \"string\") {\n        data = Utils.strToArrayBuffer(data);\n    }\n    if (data instanceof ArrayBuffer) {\n        data = new Uint8Array(data);\n    }\n\n    alphabet = Utils.expandAlphRange(alphabet).join(\"\");\n    if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding\n        throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);\n    }\n\n    let output = \"\",\n        chr1, chr2, chr3,\n        enc1, enc2, enc3, enc4,\n        i = 0;\n\n    while (i < data.length) {\n        chr1 = data[i++];\n        chr2 = data[i++];\n        chr3 = data[i++];\n\n        enc1 = chr1 >> 2;\n        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n        enc4 = chr3 & 63;\n\n        if (isNaN(chr2)) {\n            enc3 = enc4 = 64;\n        } else if (isNaN(chr3)) {\n            enc4 = 64;\n        }\n\n        output += alphabet.charAt(enc1) + alphabet.charAt(enc2) +\n            alphabet.charAt(enc3) + alphabet.charAt(enc4);\n    }\n\n    return output;\n}\n\n\n/**\n * UnBase64's the input string using the given alphabet, returning a byte array.\n *\n * @param {string} data\n * @param {string} [alphabet=\"A-Za-z0-9+/=\"]\n * @param {string} [returnType=\"string\"] - Either \"string\" or \"byteArray\"\n * @param {boolean} [removeNonAlphChars=true]\n * @returns {byteArray}\n *\n * @example\n * // returns \"Hello\"\n * fromBase64(\"SGVsbG8=\");\n *\n * // returns [72, 101, 108, 108, 111]\n * fromBase64(\"SGVsbG8=\", null, \"byteArray\");\n */\nexport function fromBase64(data, alphabet=\"A-Za-z0-9+/=\", returnType=\"string\", removeNonAlphChars=true, strictMode=false) {\n    if (!data) {\n        return returnType === \"string\" ? \"\" : [];\n    }\n\n    alphabet = alphabet || \"A-Za-z0-9+/=\";\n    alphabet = Utils.expandAlphRange(alphabet).join(\"\");\n\n    // Confirm alphabet is a valid length\n    if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding\n        throw new OperationError(`Error: Base64 alphabet should be 64 characters long, or 65 with a padding character. Found ${alphabet.length}: ${alphabet}`);\n    }\n\n    // Remove non-alphabet characters\n    if (removeNonAlphChars) {\n        const re = new RegExp(\"[^\" + alphabet.replace(/[[\\]\\\\\\-^$]/g, \"\\\\$&\") + \"]\", \"g\");\n        data = data.replace(re, \"\");\n    }\n\n    if (strictMode) {\n        // Check for incorrect lengths (even without padding)\n        if (data.length % 4 === 1) {\n            throw new OperationError(`Error: Invalid Base64 input length (${data.length}). Cannot be 4n+1, even without padding chars.`);\n        }\n\n        if (alphabet.length === 65) { // Padding character included\n            const pad = alphabet.charAt(64);\n            const padPos = data.indexOf(pad);\n            if (padPos >= 0) {\n                // Check that the padding character is only used at the end and maximum of twice\n                if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) {\n                    throw new OperationError(`Error: Base64 padding character (${pad}) not used in the correct place.`);\n                }\n\n                // Check that input is padded to the correct length\n                if (data.length % 4 !== 0) {\n                    throw new OperationError(\"Error: Base64 not padded to a multiple of 4.\");\n                }\n            }\n        }\n    }\n\n    const output = [];\n    let chr1, chr2, chr3,\n        enc1, enc2, enc3, enc4,\n        i = 0;\n\n    while (i < data.length) {\n        // Including `|| null` forces empty strings to null so that indexOf returns -1 instead of 0\n        enc1 = alphabet.indexOf(data.charAt(i++) || null);\n        enc2 = alphabet.indexOf(data.charAt(i++) || null);\n        enc3 = alphabet.indexOf(data.charAt(i++) || null);\n        enc4 = alphabet.indexOf(data.charAt(i++) || null);\n\n        if (strictMode && (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0)) {\n            throw new OperationError(\"Error: Base64 input contains non-alphabet char(s)\");\n        }\n\n        chr1 = (enc1 << 2) | (enc2 >> 4);\n        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n        chr3 = ((enc3 & 3) << 6) | enc4;\n\n        if (chr1 >= 0 && chr1 < 256) {\n            output.push(chr1);\n        }\n        if (chr2 >= 0 && chr2 < 256 && enc3 !== 64) {\n            output.push(chr2);\n        }\n        if (chr3 >= 0 && chr3 < 256 && enc4 !== 64) {\n            output.push(chr3);\n        }\n    }\n\n    return returnType === \"string\" ? Utils.byteArrayToUtf8(output) : output;\n}\n\n\n/**\n * Base64 alphabets.\n */\nexport const ALPHABET_OPTIONS = [\n    {name: \"Standard (RFC 4648): A-Za-z0-9+/=\", value: \"A-Za-z0-9+/=\"},\n    {name: \"URL safe (RFC 4648 \\u00A75): A-Za-z0-9-_\", value: \"A-Za-z0-9-_\"},\n    {name: \"Filename safe: A-Za-z0-9+-=\", value: \"A-Za-z0-9+\\\\-=\"},\n    {name: \"itoa64: ./0-9A-Za-z=\", value: \"./0-9A-Za-z=\"},\n    {name: \"XML: A-Za-z0-9_.\", value: \"A-Za-z0-9_.\"},\n    {name: \"y64: A-Za-z0-9._-\", value: \"A-Za-z0-9._-\"},\n    {name: \"z64: 0-9a-zA-Z+/=\", value: \"0-9a-zA-Z+/=\"},\n    {name: \"Radix-64 (RFC 4880): 0-9A-Za-z+/=\", value: \"0-9A-Za-z+/=\"},\n    {name: \"Uuencoding: [space]-_\", value: \" -_\"},\n    {name: \"Xxencoding: +-0-9A-Za-z\", value: \"+\\\\-0-9A-Za-z\"},\n    {name: \"BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r\", value: \"!-,-0-689@A-NP-VX-Z[`a-fh-mp-r\"},\n    {name: \"ROT13: N-ZA-Mn-za-m0-9+/=\", value: \"N-ZA-Mn-za-m0-9+/=\"},\n    {name: \"UNIX crypt: ./0-9A-Za-z\", value: \"./0-9A-Za-z\"},\n    {name: \"Atom128: /128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC\", value: \"/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC\"},\n    {name: \"Megan35: 3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5\", value: \"3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5\"},\n    {name: \"Zong22: ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2\", value: \"ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2\"},\n    {name: \"Hazz15: HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5\", value: \"HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5\"}\n];\n"
  },
  {
    "path": "src/core/lib/Base85.mjs",
    "content": "import Utils from \"../Utils.mjs\";\n\n/**\n * Base85 resources.\n *\n * @author PenguinGeorge [george@penguingeorge.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/**\n * Base85 alphabet options.\n */\nexport const ALPHABET_OPTIONS = [\n    {\n        name: \"Standard\",\n        value: \"!-u\",\n    },\n    {\n        name: \"Z85 (ZeroMQ)\",\n        value: \"0-9a-zA-Z.\\\\-:+=^!/*?&<>()[]{}@%$#\",\n    },\n    {\n        name: \"IPv6\",\n        value: \"0-9A-Za-z!#$%&()*+\\\\-;<=>?@^_`{|}~\",\n    }\n];\n\n\n/**\n * Returns the name of the alphabet, when given the alphabet.\n *\n * @param {string} alphabet\n * @returns {string}\n */\nexport function alphabetName(alphabet) {\n    alphabet = escape(alphabet);\n    let name;\n\n    ALPHABET_OPTIONS.forEach(function(a) {\n        const expanded = Utils.expandAlphRange(a.value).join(\"\");\n        if (alphabet === escape(expanded)) name = a.name;\n    });\n\n    return name;\n}\n"
  },
  {
    "path": "src/core/lib/Base92.mjs",
    "content": "/**\n * Base92 resources.\n *\n * @author sg5506844 [sg5506844@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Base92 alphabet char\n *\n * @param {number} val\n * @returns {number}\n */\nexport function base92Chr(val) {\n    if (val < 0 || val >= 91) {\n        throw new OperationError(\"Invalid value\");\n    }\n    if (val === 0)\n        return \"!\".charCodeAt(0);\n    else if (val <= 61)\n        return \"#\".charCodeAt(0) + val - 1;\n    else\n        return \"a\".charCodeAt(0) + val - 62;\n}\n\n/**\n * Base92 alphabet ord\n *\n * @param {string} val\n * @returns {number}\n */\nexport function base92Ord(val) {\n    if (val === \"!\")\n        return 0;\n    else if (\"#\" <= val && val <= \"_\")\n        return val.charCodeAt(0) - \"#\".charCodeAt(0) + 1;\n    else if (\"a\" <= val && val <= \"}\")\n        return val.charCodeAt(0) - \"a\".charCodeAt(0) + 62;\n    throw new OperationError(`${val} is not a base92 character`);\n}\n\n"
  },
  {
    "path": "src/core/lib/Bech32.mjs",
    "content": "/**\n * Pure JavaScript implementation of Bech32 and Bech32m encoding.\n *\n * Bech32 is defined in BIP-0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki\n * Bech32m is defined in BIP-0350: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki\n *\n * @author Medjedtxm\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/** Bech32 character set (32 characters, excludes 1, b, i, o) */\nconst CHARSET = \"qpzry9x8gf2tvdw0s3jn54khce6mua7l\";\n\n/** Reverse lookup table for decoding */\nconst CHARSET_REV = {};\nfor (let i = 0; i < CHARSET.length; i++) {\n    CHARSET_REV[CHARSET[i]] = i;\n}\n\n/** Checksum constant for Bech32 (BIP-0173) */\nconst BECH32_CONST = 1;\n\n/** Checksum constant for Bech32m (BIP-0350) */\nconst BECH32M_CONST = 0x2bc830a3;\n\n/** Generator polynomial coefficients for checksum */\nconst GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];\n\n/**\n * Compute the polymod checksum\n * @param {number[]} values - Array of 5-bit values\n * @returns {number} - Checksum value\n */\nfunction polymod(values) {\n    let chk = 1;\n    for (const v of values) {\n        const top = chk >> 25;\n        chk = ((chk & 0x1ffffff) << 5) ^ v;\n        for (let i = 0; i < 5; i++) {\n            if ((top >> i) & 1) {\n                chk ^= GENERATOR[i];\n            }\n        }\n    }\n    return chk;\n}\n\n/**\n * Expand HRP for checksum computation\n * @param {string} hrp - Human-readable part (lowercase)\n * @returns {number[]} - Expanded values\n */\nfunction hrpExpand(hrp) {\n    const result = [];\n    for (let i = 0; i < hrp.length; i++) {\n        result.push(hrp.charCodeAt(i) >> 5);\n    }\n    result.push(0);\n    for (let i = 0; i < hrp.length; i++) {\n        result.push(hrp.charCodeAt(i) & 31);\n    }\n    return result;\n}\n\n/**\n * Verify checksum of a Bech32/Bech32m string\n * @param {string} hrp - Human-readable part (lowercase)\n * @param {number[]} data - Data including checksum (5-bit values)\n * @param {string} encoding - \"Bech32\" or \"Bech32m\"\n * @returns {boolean} - True if checksum is valid\n */\nfunction verifyChecksum(hrp, data, encoding) {\n    const constant = encoding === \"Bech32m\" ? BECH32M_CONST : BECH32_CONST;\n    return polymod(hrpExpand(hrp).concat(data)) === constant;\n}\n\n/**\n * Create checksum for Bech32/Bech32m encoding\n * @param {string} hrp - Human-readable part (lowercase)\n * @param {number[]} data - Data values (5-bit)\n * @param {string} encoding - \"Bech32\" or \"Bech32m\"\n * @returns {number[]} - 6 checksum values\n */\nfunction createChecksum(hrp, data, encoding) {\n    const constant = encoding === \"Bech32m\" ? BECH32M_CONST : BECH32_CONST;\n    const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);\n    const mod = polymod(values) ^ constant;\n    const result = [];\n    for (let i = 0; i < 6; i++) {\n        result.push((mod >> (5 * (5 - i))) & 31);\n    }\n    return result;\n}\n\n/**\n * Convert 8-bit bytes to 5-bit words\n * @param {number[]|Uint8Array} data - Input bytes\n * @returns {number[]} - 5-bit words\n */\nexport function toWords(data) {\n    let value = 0;\n    let bits = 0;\n    const result = [];\n\n    for (let i = 0; i < data.length; i++) {\n        value = (value << 8) | data[i];\n        bits += 8;\n\n        while (bits >= 5) {\n            bits -= 5;\n            result.push((value >> bits) & 31);\n        }\n    }\n\n    // Pad remaining bits\n    if (bits > 0) {\n        result.push((value << (5 - bits)) & 31);\n    }\n\n    return result;\n}\n\n/**\n * Convert 5-bit words to 8-bit bytes\n * @param {number[]} words - 5-bit words\n * @returns {number[]} - Output bytes\n */\nexport function fromWords(words) {\n    let value = 0;\n    let bits = 0;\n    const result = [];\n\n    for (let i = 0; i < words.length; i++) {\n        value = (value << 5) | words[i];\n        bits += 5;\n\n        while (bits >= 8) {\n            bits -= 8;\n            result.push((value >> bits) & 255);\n        }\n    }\n\n    // Check for invalid padding per BIP-0173\n    // Condition 1: Cannot have 5+ bits remaining (would indicate incomplete byte)\n    if (bits >= 5) {\n        throw new OperationError(\"Invalid padding: too many bits remaining\");\n    }\n    // Condition 2: Remaining padding bits must all be zero\n    if (bits > 0) {\n        const paddingValue = (value << (8 - bits)) & 255;\n        if (paddingValue !== 0) {\n            throw new OperationError(\"Invalid padding: non-zero bits in padding\");\n        }\n    }\n\n    return result;\n}\n\n/**\n * Encode data to Bech32/Bech32m string\n *\n * @param {string} hrp - Human-readable part\n * @param {number[]|Uint8Array} data - Data bytes to encode\n * @param {string} encoding - \"Bech32\" or \"Bech32m\"\n * @param {boolean} segwit - If true, treat first byte as witness version (for Bitcoin SegWit)\n * @returns {string} - Encoded Bech32/Bech32m string\n */\nexport function encode(hrp, data, encoding = \"Bech32\", segwit = false) {\n    // Validate HRP\n    if (!hrp || hrp.length === 0) {\n        throw new OperationError(\"Human-Readable Part (HRP) cannot be empty.\");\n    }\n\n    // Check HRP characters (ASCII 33-126)\n    for (let i = 0; i < hrp.length; i++) {\n        const c = hrp.charCodeAt(i);\n        if (c < 33 || c > 126) {\n            throw new OperationError(`HRP contains invalid character at position ${i}. Only printable ASCII characters (33-126) are allowed.`);\n        }\n    }\n\n    // Convert HRP to lowercase\n    const hrpLower = hrp.toLowerCase();\n\n    let words;\n    if (segwit && data.length >= 2) {\n        // SegWit encoding: first byte is witness version (0-16), rest is witness program\n        const witnessVersion = data[0];\n        if (witnessVersion > 16) {\n            throw new OperationError(`Invalid witness version: ${witnessVersion}. Must be 0-16.`);\n        }\n        const witnessProgram = Array.prototype.slice.call(data, 1);\n\n        // Validate witness program length per BIP-0141\n        if (witnessProgram.length < 2 || witnessProgram.length > 40) {\n            throw new OperationError(`Invalid witness program length: ${witnessProgram.length}. Must be 2-40 bytes.`);\n        }\n        if (witnessVersion === 0 && witnessProgram.length !== 20 && witnessProgram.length !== 32) {\n            throw new OperationError(`Invalid witness program length for v0: ${witnessProgram.length}. Must be 20 or 32 bytes.`);\n        }\n\n        // Witness version is kept as single 5-bit value, program is converted\n        words = [witnessVersion].concat(toWords(witnessProgram));\n    } else {\n        // Generic encoding: convert all bytes to 5-bit words\n        words = toWords(data);\n    }\n\n    // Create checksum\n    const checksum = createChecksum(hrpLower, words, encoding);\n\n    // Build result string\n    let result = hrpLower + \"1\";\n    for (const w of words.concat(checksum)) {\n        result += CHARSET[w];\n    }\n\n    // Check maximum length (90 characters)\n    if (result.length > 90) {\n        throw new OperationError(`Encoded string exceeds maximum length of 90 characters (got ${result.length}). Consider using smaller input data.`);\n    }\n\n    return result;\n}\n\n/**\n * Decode a Bech32/Bech32m string\n *\n * @param {string} str - Bech32/Bech32m encoded string\n * @param {string} encoding - \"Bech32\", \"Bech32m\", or \"Auto-detect\"\n * @returns {{hrp: string, data: number[]}} - Decoded HRP and data bytes\n */\nexport function decode(str, encoding = \"Auto-detect\") {\n    // Check for empty input\n    if (!str || str.length === 0) {\n        throw new OperationError(\"Input cannot be empty.\");\n    }\n\n    // Check maximum length\n    if (str.length > 90) {\n        throw new OperationError(`Invalid Bech32 string: exceeds maximum length of 90 characters (got ${str.length}).`);\n    }\n\n    // Check for mixed case\n    const hasUpper = /[A-Z]/.test(str);\n    const hasLower = /[a-z]/.test(str);\n    if (hasUpper && hasLower) {\n        throw new OperationError(\"Invalid Bech32 string: mixed case is not allowed. Use all uppercase or all lowercase.\");\n    }\n\n    // Convert to lowercase for processing\n    str = str.toLowerCase();\n\n    // Find separator (last occurrence of '1')\n    const sepIndex = str.lastIndexOf(\"1\");\n    if (sepIndex === -1) {\n        throw new OperationError(\"Invalid Bech32 string: no separator '1' found.\");\n    }\n\n    if (sepIndex === 0) {\n        throw new OperationError(\"Invalid Bech32 string: Human-Readable Part (HRP) cannot be empty.\");\n    }\n\n    if (sepIndex + 7 > str.length) {\n        throw new OperationError(\"Invalid Bech32 string: data part is too short (minimum 6 characters for checksum).\");\n    }\n\n    // Extract HRP and data part\n    const hrp = str.substring(0, sepIndex);\n    const dataPart = str.substring(sepIndex + 1);\n\n    // Validate HRP characters\n    for (let i = 0; i < hrp.length; i++) {\n        const c = hrp.charCodeAt(i);\n        if (c < 33 || c > 126) {\n            throw new OperationError(`HRP contains invalid character at position ${i}.`);\n        }\n    }\n\n    // Decode data characters to 5-bit values\n    const data = [];\n    for (let i = 0; i < dataPart.length; i++) {\n        const c = dataPart[i];\n        if (CHARSET_REV[c] === undefined) {\n            throw new OperationError(`Invalid character '${c}' at position ${sepIndex + 1 + i}.`);\n        }\n        data.push(CHARSET_REV[c]);\n    }\n\n    // Verify checksum\n    let usedEncoding;\n    if (encoding === \"Bech32\") {\n        if (!verifyChecksum(hrp, data, \"Bech32\")) {\n            throw new OperationError(\"Invalid Bech32 checksum.\");\n        }\n        usedEncoding = \"Bech32\";\n    } else if (encoding === \"Bech32m\") {\n        if (!verifyChecksum(hrp, data, \"Bech32m\")) {\n            throw new OperationError(\"Invalid Bech32m checksum.\");\n        }\n        usedEncoding = \"Bech32m\";\n    } else {\n        // Auto-detect: try Bech32 first, then Bech32m\n        if (verifyChecksum(hrp, data, \"Bech32\")) {\n            usedEncoding = \"Bech32\";\n        } else if (verifyChecksum(hrp, data, \"Bech32m\")) {\n            usedEncoding = \"Bech32m\";\n        } else {\n            throw new OperationError(\"Invalid Bech32/Bech32m string: checksum verification failed.\");\n        }\n    }\n\n    // Remove checksum (last 6 values)\n    const words = data.slice(0, data.length - 6);\n\n    // Check if this is likely a SegWit address (Bitcoin, Litecoin, etc.)\n    // For SegWit, the first 5-bit word is the witness version (0-16)\n    // and should be extracted separately, not bit-converted with the rest\n    const segwitHrps = [\"bc\", \"tb\", \"ltc\", \"tltc\", \"bcrt\"];\n    const couldBeSegWit = segwitHrps.includes(hrp) && words.length > 0 && words[0] <= 16;\n\n    let bytes;\n    let witnessVersion = null;\n\n    if (couldBeSegWit) {\n        // Try SegWit decode first\n        try {\n            witnessVersion = words[0];\n            const programWords = words.slice(1);\n            const programBytes = fromWords(programWords);\n\n            // Validate SegWit witness program length (20 or 32 bytes for v0, 2-40 for others)\n            const validV0 = witnessVersion === 0 && (programBytes.length === 20 || programBytes.length === 32);\n            const validOther = witnessVersion !== 0 && programBytes.length >= 2 && programBytes.length <= 40;\n\n            if (validV0 || validOther) {\n                // Valid SegWit address\n                bytes = [witnessVersion, ...programBytes];\n            } else {\n                // Not valid SegWit, fall back to generic decode\n                witnessVersion = null;\n                bytes = fromWords(words);\n            }\n        } catch (e) {\n            // SegWit decode failed, try generic decode\n            witnessVersion = null;\n            try {\n                bytes = fromWords(words);\n            } catch (e2) {\n                throw new OperationError(`Failed to decode data: ${e2.message}`);\n            }\n        }\n    } else {\n        // Generic Bech32: convert all words\n        try {\n            bytes = fromWords(words);\n        } catch (e) {\n            throw new OperationError(`Failed to decode data: ${e.message}`);\n        }\n    }\n\n    return {\n        hrp: hrp,\n        data: bytes,\n        encoding: usedEncoding,\n        witnessVersion: witnessVersion\n    };\n}\n"
  },
  {
    "path": "src/core/lib/BigIntUtils.mjs",
    "content": "/**\n * @author p-leriche [philip.leriche@cantab.net]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Number theory utilities used by cryptographic operations.\n *\n * Currently provides:\n *   - parseBigInt\n *   - Extended Euclidean Algorithm\n *   - Modular Exponentiation\n *\n * Additional algorithms may be added as required.\n */\n\n/**\n * parseBigInt helper operation\n */\nexport function parseBigInt(value, param) {\n    const v = (value ?? \"\").trim();\n    if (/^0x[0-9a-f]+$/i.test(v)) return BigInt(v);\n    if (/^[+-]?[0-9]+$/.test(v)) return BigInt(v);\n    throw new OperationError(param + \" must be decimal or hex (0x...)\");\n}\n\n/**\n * Extended Euclidean Algorithm\n *\n * Returns [g, x, y] such that:\n *   a*x + b*y = g = gcd(a, b)\n *\n *   (Uses an iterative algorithm to avoid possible stack overflow)\n */\nexport function egcd(a, b) {\n    let oldR = a, r = b;\n    let oldS = 1n, s = 0n;\n    let oldT = 0n, t = 1n;\n\n    while (r !== 0n) {\n        const quotient = oldR / r;\n\n        [oldR, r] = [r, oldR - quotient * r];\n        [oldS, s] = [s, oldS - quotient * s];\n        [oldT, t] = [t, oldT - quotient * t];\n    }\n\n    // oldR is the gcd\n    // oldS and oldT are the Bézout coefficients\n    return [oldR, oldS, oldT];\n}\n\n/**\n * Modular exponentiation\n */\nexport function modPow(base, exponent, modulus) {\n    let result = 1n;\n    base %= modulus;\n\n    while (exponent > 0n) {\n        if (exponent & 1n) {\n            result = (result * base) % modulus;\n        }\n        base = (base * base) % modulus;\n        exponent >>= 1n;\n    }\n\n    return result;\n}\n\n"
  },
  {
    "path": "src/core/lib/Binary.mjs",
    "content": "/**\n * Binary functions.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n\n/**\n * Convert a byte array into a binary string.\n *\n * @param {Uint8Array|byteArray|number} data\n * @param {string} [delim=\"Space\"]\n * @param {number} [padding=8]\n * @returns {string}\n *\n * @example\n * // returns \"00001010 00010100 00011110\"\n * toBinary([10,20,30]);\n *\n * // returns \"00001010:00010100:00011110\"\n * toBinary([10,20,30], \"Colon\");\n *\n * // returns \"1010:10100:11110\"\n * toBinary([10,20,30], \"Colon\", 0);\n */\nexport function toBinary(data, delim=\"Space\", padding=8) {\n    if (data === undefined || data === null)\n        throw new OperationError(\"Unable to convert to binary: Empty input data enocuntered\");\n\n    delim = Utils.charRep(delim);\n    let output = \"\";\n\n    if (data.length) { // array\n        for (let i = 0; i < data.length; i++) {\n            output += data[i].toString(2).padStart(padding, \"0\");\n            if (i !== data.length - 1) output += delim;\n        }\n    } else if (typeof data === \"number\") { // Single value\n        return data.toString(2).padStart(padding, \"0\");\n    } else {\n        return \"\";\n    }\n    return output;\n}\n\n\n/**\n * Convert a binary string into a byte array.\n *\n * @param {string} data\n * @param {string} [delim]\n * @param {number} [byteLen=8]\n * @returns {byteArray}\n *\n * @example\n * // returns [10,20,30]\n * fromBinary(\"00001010 00010100 00011110\");\n *\n * // returns [10,20,30]\n * fromBinary(\"00001010:00010100:00011110\", \"Colon\");\n */\nexport function fromBinary(data, delim=\"Space\", byteLen=8) {\n    if (byteLen < 1 || Math.round(byteLen) !== byteLen)\n        throw new OperationError(\"Byte length must be a positive integer\");\n\n    const delimRegex = Utils.regexRep(delim);\n    data = data.replace(delimRegex, \"\");\n\n    const output = [];\n    for (let i = 0; i < data.length; i += byteLen) {\n        output.push(parseInt(data.substr(i, byteLen), 2));\n    }\n    return output;\n}\n\n"
  },
  {
    "path": "src/core/lib/BitwiseOp.mjs",
    "content": "/**\n * Bitwise operation resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/**\n * Runs bitwise operations across the input data.\n *\n * @param {byteArray|Uint8Array} input\n * @param {byteArray} key\n * @param {function} func - The bitwise calculation to carry out\n * @param {boolean} nullPreserving\n * @param {string} scheme\n * @returns {byteArray}\n */\nexport function bitOp (input, key, func, nullPreserving, scheme) {\n    if (!key || !key.length) key = [0];\n    const result = [];\n    let x = null,\n        k = null,\n        o = null;\n\n    for (let i = 0; i < input.length; i++) {\n        k = key[i % key.length];\n        if (scheme === \"Cascade\") k = input[i + 1] || 0;\n        o = input[i];\n        x = nullPreserving && (o === 0 || o === k) ? o : func(o, k);\n        result.push(x);\n        if (scheme &&\n            scheme !== \"Standard\" &&\n            !(nullPreserving && (o === 0 || o === k))) {\n            switch (scheme) {\n                case \"Input differential\":\n                    key[i % key.length] = o;\n                    break;\n                case \"Output differential\":\n                    key[i % key.length] = x;\n                    break;\n            }\n        }\n    }\n\n    return result;\n}\n\n/**\n * XOR bitwise calculation.\n *\n * @param {number} operand\n * @param {number} key\n * @returns {number}\n */\nexport function xor(operand, key) {\n    return operand ^ key;\n}\n\n\n/**\n * NOT bitwise calculation.\n *\n * @param {number} operand\n * @returns {number}\n */\nexport function not(operand, _) {\n    return ~operand & 0xff;\n}\n\n\n/**\n * AND bitwise calculation.\n *\n * @param {number} operand\n * @param {number} key\n * @returns {number}\n */\nexport function and(operand, key) {\n    return operand & key;\n}\n\n\n/**\n * OR bitwise calculation.\n *\n * @param {number} operand\n * @param {number} key\n * @returns {number}\n */\nexport function or(operand, key) {\n    return operand | key;\n}\n\n\n/**\n * ADD bitwise calculation.\n *\n * @param {number} operand\n * @param {number} key\n * @returns {number}\n */\nexport function add(operand, key) {\n    return (operand + key) % 256;\n}\n\n\n/**\n * SUB bitwise calculation.\n *\n * @param {number} operand\n * @param {number} key\n * @returns {number}\n */\nexport function sub(operand, key) {\n    const result = operand - key;\n    return (result < 0) ? 256 + result : result;\n}\n\n\n/**\n * Delimiter options for bitwise operations\n */\nexport const BITWISE_OP_DELIMS = [\"Hex\", \"Decimal\", \"Binary\", \"Base64\", \"UTF8\", \"Latin1\"];\n"
  },
  {
    "path": "src/core/lib/Blowfish.mjs",
    "content": "/**\n  Blowfish.js from Dojo Toolkit 1.8.1 (https://github.com/dojo/dojox/tree/1.8/encoding)\n  Extracted by Sladex (xslade@gmail.com)\n  Shoehorned into working with mjs for CyberChef by Matt C (matt@artemisbot.uk)\n  Refactored and implemented modes support by cbeuw (cbeuw.andy@gmail.com)\n\n  @license BSD\n  ========================================================================\n  The \"New\" BSD License:\n  **********************\n\n  Copyright (c) 2005-2016, The Dojo Foundation\n  All rights reserved.\n\n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions are met:\n\n  * Redistributions of source code must retain the above copyright notice, this\n    list of conditions and the following disclaimer.\n  * Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n  * Neither the name of the Dojo Foundation nor the names of its contributors\n    may be used to endorse or promote products derived from this software\n    without specific prior written permission.\n\n  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n  DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\n  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nconst crypto = {};\n\nimport forge from \"node-forge\";\n\n\n/* dojo-release-1.8.1/dojo/_base/lang.js.uncompressed.js */\n\nconst lang = {};\nlang.isString = function(it) {\n\t// summary:\n\t//\t\tReturn true if it is a String\n\t// it: anything\n\t//\t\tItem to test.\n    return (typeof it == \"string\" || it instanceof String); // Boolean\n};\n\n\n/* dojo-release-1.8.1/dojo/_base/array.js.uncompressed.js */\n\nconst arrayUtil = {};\narrayUtil.map = function(arr, callback, thisObject, Ctr) {\n\t// summary:\n\t//\t\tapplies callback to each element of arr and returns\n\t//\t\tan Array with the results\n\t// arr: Array|String\n\t//\t\tthe array to iterate on. If a string, operates on\n\t//\t\tindividual characters.\n\t// callback: Function\n\t//\t\ta function is invoked with three arguments, (item, index,\n\t//\t\tarray),\t and returns a value\n\t// thisObject: Object?\n\t//\t\tmay be used to scope the call to callback\n\t// returns: Array\n\t// description:\n\t//\t\tThis function corresponds to the JavaScript 1.6 Array.map() method, with one difference: when\n\t//\t\trun over sparse arrays, this implementation passes the \"holes\" in the sparse array to\n\t//\t\tthe callback function with a value of undefined. JavaScript 1.6's map skips the holes in the sparse array.\n\t//\t\tFor more details, see:\n\t//\t\thttps://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map\n\t// example:\n\t//\t| // returns [2, 3, 4, 5]\n\t//\t| array.map([1, 2, 3, 4], function(item){ return item+1 });\n\n\t// TODO: why do we have a non-standard signature here? do we need \"Ctr\"?\n    let i = 0;\n    const l = arr && arr.length || 0, out = new (Ctr || Array)(l);\n    if (l && typeof arr == \"string\") arr = arr.split(\"\");\n    if (thisObject) {\n        for (; i < l; ++i) {\n            out[i] = callback.call(thisObject, arr[i], i, arr);\n        }\n    } else {\n        for (; i < l; ++i) {\n            out[i] = callback(arr[i], i, arr);\n        }\n    }\n    return out; // Array\n};\n\n/* dojo-release-1.8.1/dojox/encoding/crypto/Blowfish.js.uncompressed.js */\n\n/*\tBlowfish\n *\tCreated based on the C# implementation by Marcus Hahn (http://www.hotpixel.net/)\n *\tUnsigned math based on Paul Johnstone and Peter Wood patches.\n *\t2005-12-08\n */\nconst boxes={\n    p: [\n        0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,\n        0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,\n        0x9216d5d9, 0x8979fb1b\n    ],\n    s0: [\n        0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,\n        0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,\n        0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,\n        0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,\n        0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,\n        0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,\n        0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,\n        0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,\n        0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,\n        0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,\n        0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,\n        0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,\n        0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,\n        0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,\n        0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,\n        0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,\n        0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,\n        0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,\n        0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,\n        0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,\n        0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,\n        0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,\n        0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,\n        0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,\n        0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,\n        0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,\n        0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,\n        0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,\n        0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,\n        0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,\n        0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,\n        0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a\n    ],\n    s1: [\n        0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,\n        0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,\n        0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,\n        0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,\n        0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,\n        0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,\n        0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,\n        0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,\n        0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,\n        0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,\n        0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,\n        0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,\n        0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,\n        0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,\n        0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,\n        0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,\n        0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,\n        0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,\n        0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,\n        0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,\n        0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,\n        0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,\n        0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,\n        0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,\n        0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,\n        0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,\n        0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,\n        0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,\n        0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,\n        0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,\n        0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,\n        0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7\n    ],\n    s2: [\n        0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,\n        0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,\n        0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,\n        0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,\n        0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,\n        0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,\n        0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,\n        0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,\n        0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,\n        0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,\n        0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,\n        0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,\n        0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,\n        0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,\n        0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,\n        0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,\n        0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,\n        0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,\n        0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,\n        0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,\n        0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,\n        0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,\n        0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,\n        0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,\n        0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,\n        0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,\n        0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,\n        0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,\n        0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,\n        0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,\n        0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,\n        0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0\n    ],\n    s3: [\n        0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,\n        0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,\n        0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,\n        0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,\n        0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,\n        0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,\n        0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,\n        0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,\n        0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,\n        0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,\n        0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,\n        0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,\n        0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,\n        0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,\n        0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,\n        0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,\n        0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,\n        0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,\n        0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,\n        0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,\n        0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,\n        0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,\n        0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,\n        0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,\n        0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,\n        0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,\n        0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,\n        0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,\n        0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,\n        0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,\n        0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,\n        0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6\n    ]\n};\n\n\n////////////////////////////////////////////////////////////////////////////\n//\tfixes based on patch submitted by Peter Wood (#5791)\nconst xor = function(x, y) {\n    return (((x>>0x10)^(y>>0x10))<<0x10)|(((x&0xffff)^(y&0xffff))&0xffff);\n};\n\n\nconst f = function(v, box) {\n    const d=box.s3[v&0xff]; v>>=8;\n    const c=box.s2[v&0xff]; v>>=8;\n    const b=box.s1[v&0xff]; v>>=8;\n    const a=box.s0[v&0xff];\n\n    let r = (((a>>0x10)+(b>>0x10)+(((a&0xffff)+(b&0xffff))>>0x10))<<0x10)|(((a&0xffff)+(b&0xffff))&0xffff);\n    r = (((r>>0x10)^(c>>0x10))<<0x10)|(((r&0xffff)^(c&0xffff))&0xffff);\n    return (((r>>0x10)+(d>>0x10)+(((r&0xffff)+(d&0xffff))>>0x10))<<0x10)|(((r&0xffff)+(d&0xffff))&0xffff);\n};\n\n\nconst eb = function(o, box) {\n\t//\tTODO: see if this can't be made more efficient\n    let l=o.left;\n    let r=o.right;\n    l=xor(l, box.p[0]);\n    r=xor(r, xor(f(l, box), box.p[1]));\n    l=xor(l, xor(f(r, box), box.p[2]));\n    r=xor(r, xor(f(l, box), box.p[3]));\n    l=xor(l, xor(f(r, box), box.p[4]));\n    r=xor(r, xor(f(l, box), box.p[5]));\n    l=xor(l, xor(f(r, box), box.p[6]));\n    r=xor(r, xor(f(l, box), box.p[7]));\n    l=xor(l, xor(f(r, box), box.p[8]));\n    r=xor(r, xor(f(l, box), box.p[9]));\n    l=xor(l, xor(f(r, box), box.p[10]));\n    r=xor(r, xor(f(l, box), box.p[11]));\n    l=xor(l, xor(f(r, box), box.p[12]));\n    r=xor(r, xor(f(l, box), box.p[13]));\n    l=xor(l, xor(f(r, box), box.p[14]));\n    r=xor(r, xor(f(l, box), box.p[15]));\n    l=xor(l, xor(f(r, box), box.p[16]));\n    o.right=l;\n    o.left=xor(r, box.p[17]);\n};\n\nconst db = function(o, box) {\n    let l=o.left;\n    let r=o.right;\n    l=xor(l, box.p[17]);\n    r=xor(r, xor(f(l, box), box.p[16]));\n    l=xor(l, xor(f(r, box), box.p[15]));\n    r=xor(r, xor(f(l, box), box.p[14]));\n    l=xor(l, xor(f(r, box), box.p[13]));\n    r=xor(r, xor(f(l, box), box.p[12]));\n    l=xor(l, xor(f(r, box), box.p[11]));\n    r=xor(r, xor(f(l, box), box.p[10]));\n    l=xor(l, xor(f(r, box), box.p[9]));\n    r=xor(r, xor(f(l, box), box.p[8]));\n    l=xor(l, xor(f(r, box), box.p[7]));\n    r=xor(r, xor(f(l, box), box.p[6]));\n    l=xor(l, xor(f(r, box), box.p[5]));\n    r=xor(r, xor(f(l, box), box.p[4]));\n    l=xor(l, xor(f(r, box), box.p[3]));\n    r=xor(r, xor(f(l, box), box.p[2]));\n    l=xor(l, xor(f(r, box), box.p[1]));\n    o.right=l;\n    o.left=xor(r, box.p[0]);\n};\n\nconst encryptBlock=function(inblock, outblock, box) {\n    const o = {};\n    o.left=inblock[0];\n    o.right=inblock[1];\n    eb(o, box);\n    outblock[0] = o.left;\n    outblock[1] = o.right;\n};\n\nconst decryptBlock=function(inblock, outblock, box) {\n    const o= {};\n    o.left=inblock[0];\n    o.right=inblock[1];\n    db(o, box);\n    outblock[0] = o.left;\n    outblock[1] = o.right;\n};\n\ncrypto.Blowfish = new function() {\n    this.createCipher=function(key, modeName) {\n        return new forge.cipher.BlockCipher({\n            algorithm: new Blowfish.Algorithm(key, modeName),\n            key: key,\n            decrypt: false\n        });\n    };\n\n    this.createDecipher=function(key, modeName) {\n        return new forge.cipher.BlockCipher({\n            algorithm: new Blowfish.Algorithm(key, modeName),\n            key: key,\n            decrypt: true\n        });\n    };\n}();\n\n\ncrypto.Blowfish.Algorithm=function(key, modeName) {\n    this.initialize({key: key});\n    const _box = this.box;\n    const modeOption = {\n        blockSize: 8,\n        cipher: {\n            encrypt: function(inblock, outblock) {\n                encryptBlock(inblock, outblock, _box);\n            },\n            decrypt: function(inblock, outblock) {\n                decryptBlock(inblock, outblock, _box);\n            },\n        }\n    };\n\n    switch (modeName.toLowerCase()) {\n        case \"ecb\":\n            this.mode=new forge.cipher.modes.ecb(modeOption);\n            break;\n        case \"cbc\":\n            this.mode=new forge.cipher.modes.cbc(modeOption);\n            break;\n        case \"cfb\":\n            this.mode=new forge.cipher.modes.cfb(modeOption);\n            break;\n        case \"ofb\":\n            this.mode=new forge.cipher.modes.ofb(modeOption);\n            break;\n        case \"ctr\":\n            this.mode=new forge.cipher.modes.ctr(modeOption);\n            break;\n        default:\n            this.mode=new forge.cipher.modes.ecb(modeOption);\n            break;\n    }\n\n};\n\ncrypto.Blowfish.Algorithm.prototype.initialize=function(options) {\n    const POW8=Math.pow(2, 8);\n\n    let k=options.key;\n    if (lang.isString(k)) {\n        k = arrayUtil.map(k.split(\"\"), function(item) {\n            return item.charCodeAt(0) & 0xff;\n        });\n    }\n\n\t//\tinit the boxes\n    let pos=0, data=0;\n    const res={ left: 0, right: 0 };\n    const box = {\n        p: arrayUtil.map(boxes.p.slice(0), function(item) {\n            const l=k.length;\n            for (let j=0; j<4; j++) {\n                data=(data*POW8)|k[pos++ % l];\n            }\n            return (((item>>0x10)^(data>>0x10))<<0x10)|(((item&0xffff)^(data&0xffff))&0xffff);\n        }),\n        s0: boxes.s0.slice(0),\n        s1: boxes.s1.slice(0),\n        s2: boxes.s2.slice(0),\n        s3: boxes.s3.slice(0)\n    };\n\n\t//\tencrypt p and the s boxes\n    for (let i=0, l=box.p.length; i<l;) {\n        eb(res, box);\n        box.p[i++]=res.left;\n        box.p[i++]=res.right;\n    }\n    for (let i=0; i<4; i++) {\n        for (let j=0, l=box[\"s\"+i].length; j<l;) {\n            eb(res, box);\n            box[\"s\"+i][j++]=res.left;\n            box[\"s\"+i][j++]=res.right;\n        }\n    }\n    this.box = box;\n};\n\nexport const Blowfish = crypto.Blowfish;\n"
  },
  {
    "path": "src/core/lib/Bombe.mjs",
    "content": "/**\n * Emulation of the Bombe machine.\n *\n * @author s2224834\n * @author The National Museum of Computing - Bombe Rebuild Project\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {Rotor, Plugboard, a2i, i2a} from \"./Enigma.mjs\";\n\n/**\n * Convenience/optimisation subclass of Rotor\n *\n * This allows creating multiple Rotors which share backing maps, to avoid repeatedly parsing the\n * rotor spec strings and duplicating the maps in memory.\n */\nclass CopyRotor extends Rotor {\n    /**\n     * Return a copy of this Rotor.\n     * @returns {Object}\n     */\n    copy() {\n        const clone = {\n            map: this.map,\n            revMap: this.revMap,\n            pos: this.pos,\n            step: this.step,\n            transform: this.transform,\n            revTransform: this.revTransform,\n        };\n        return clone;\n    }\n}\n\n/**\n * Node in the menu graph\n *\n * A node represents a cipher/plaintext letter.\n */\nclass Node {\n    /**\n     * Node constructor.\n     * @param {number} letter - The plain/ciphertext letter this node represents (as a number).\n     */\n    constructor(letter) {\n        this.letter = letter;\n        this.edges = new Set();\n        this.visited = false;\n    }\n}\n\n/**\n * Edge in the menu graph\n *\n * An edge represents an Enigma machine transformation between two letters.\n */\nclass Edge {\n    /**\n     * Edge constructor - an Enigma machine mapping between letters\n     * @param {number} pos - The rotor position, relative to the beginning of the crib, at this edge\n     * @param {number} node1 - Letter at one end (as a number)\n     * @param {number} node2 - Letter at the other end\n     */\n    constructor(pos, node1, node2) {\n        this.pos = pos;\n        this.node1 = node1;\n        this.node2 = node2;\n        node1.edges.add(this);\n        node2.edges.add(this);\n        this.visited = false;\n    }\n\n    /**\n     * Given the node at one end of this edge, return the other end.\n     * @param node {number} - The node we have\n     * @returns {number}\n     */\n    getOther(node) {\n        return this.node1 === node ? this.node2 : this.node1;\n    }\n}\n\n/**\n * As all the Bombe's rotors move in step, at any given point the vast majority of the scramblers\n * in the machine share the majority of their state, which is hosted in this class.\n */\nclass SharedScrambler {\n    /**\n     * SharedScrambler constructor.\n     * @param {Object[]} rotors - List of rotors in the shared state _only_.\n     * @param {Object} reflector - The reflector in use.\n     */\n    constructor(rotors, reflector) {\n        this.lowerCache = new Array(26);\n        this.higherCache = new Array(26);\n        for (let i=0; i<26; i++) {\n            this.higherCache[i] = new Array(26);\n        }\n        this.changeRotors(rotors, reflector);\n    }\n\n    /**\n     * Replace the rotors and reflector in this SharedScrambler.\n     * This takes care of flushing caches as well.\n     * @param {Object[]} rotors - List of rotors in the shared state _only_.\n     * @param {Object} reflector - The reflector in use.\n     */\n    changeRotors(rotors, reflector) {\n        this.reflector = reflector;\n        this.rotors = rotors;\n        this.rotorsRev = [].concat(rotors).reverse();\n        this.cacheGen();\n    }\n\n    /**\n     * Step the rotors forward.\n     * @param {number} n - How many rotors to step. This includes the rotors which are not part of\n     * the shared state, so should be 2 or more.\n     */\n    step(n) {\n        for (let i=0; i<n-1; i++) {\n            this.rotors[i].step();\n        }\n        this.cacheGen();\n    }\n\n    /**\n     * Optimisation: We pregenerate all routes through the machine with the top rotor removed,\n     * as these rarely change. This saves a lot of lookups. This function generates this route\n     * table.\n     * We also just-in-time cache the full routes through the scramblers, because after stepping\n     * the fast rotor some scramblers will be in states occupied by other scrambles on previous\n     * iterations.\n     */\n    cacheGen() {\n        for (let i=0; i<26; i++) {\n            this.lowerCache[i] = undefined;\n            for (let j=0; j<26; j++) {\n                this.higherCache[i][j] = undefined;\n            }\n        }\n        for (let i=0; i<26; i++) {\n            if (this.lowerCache[i] !== undefined) {\n                continue;\n            }\n            let letter = i;\n            for (const rotor of this.rotors) {\n                letter = rotor.transform(letter);\n            }\n            letter = this.reflector.transform(letter);\n            for (const rotor of this.rotorsRev) {\n                letter = rotor.revTransform(letter);\n            }\n            // By symmetry\n            this.lowerCache[i] = letter;\n            this.lowerCache[letter] = i;\n        }\n    }\n\n    /**\n     * Map a letter through this (partial) scrambler.\n     * @param {number} i - The letter\n     * @returns {number}\n     */\n    transform(i) {\n        return this.lowerCache[i];\n    }\n}\n\n/**\n * Scrambler.\n *\n * This is effectively just an Enigma machine, but it only operates on one character at a time and\n * the stepping mechanism is different.\n */\nclass Scrambler {\n    /** Scrambler constructor.\n     * @param {Object} base - The SharedScrambler whose state this scrambler uses\n     * @param {Object} rotor - The non-shared fast rotor in this scrambler\n     * @param {number} pos - Position offset from start of crib\n     * @param {number} end1 - Letter in menu this scrambler is attached to\n     * @param {number} end2 - Other letter in menu this scrambler is attached to\n     */\n    constructor(base, rotor, pos, end1, end2) {\n        this.baseScrambler = base;\n        this.initialPos = pos;\n        this.changeRotor(rotor);\n        this.end1 = end1;\n        this.end2 = end2;\n        // For efficiency reasons, we pull the relevant shared cache from the baseScrambler into\n        // this object - this saves us a few pointer dereferences\n        this.cache = this.baseScrambler.higherCache[pos];\n    }\n\n    /**\n     * Replace the rotor in this scrambler.\n     * The position is reset automatically.\n     * @param {Object} rotor - New rotor\n     */\n    changeRotor(rotor) {\n        this.rotor = rotor;\n        this.rotor.pos += this.initialPos;\n    }\n\n    /**\n     * Step the rotor forward.\n     *\n     * The base SharedScrambler needs to be instructed to step separately.\n     */\n    step() {\n        // The Bombe steps the slowest rotor on an actual Enigma fastest, for reasons.\n        // ...but for optimisation reasons I'm going to cheat and not do that, as this vastly\n        // simplifies caching the state of the majority of the scramblers. The results are the\n        // same, just in a slightly different order.\n        this.rotor.step();\n        this.cache = this.baseScrambler.higherCache[this.rotor.pos];\n    }\n\n\n    /**\n     * Run a letter through the scrambler.\n     * @param {number} i - The letter to transform (as a number)\n     * @returns {number}\n     */\n    transform(i) {\n        let letter = i;\n        const cached = this.cache[i];\n        if (cached !== undefined) {\n            return cached;\n        }\n        letter = this.rotor.transform(letter);\n        letter = this.baseScrambler.transform(letter);\n        letter = this.rotor.revTransform(letter);\n        this.cache[i] = letter;\n        this.cache[letter] = i;\n        return letter;\n    }\n\n    /**\n     * Given one letter in the menu this scrambler maps to, return the other.\n     * @param end {number} - The node we have\n     * @returns {number}\n     */\n    getOtherEnd(end) {\n        return this.end1 === end ? this.end2 : this.end1;\n    }\n\n    /**\n     * Read the position this scrambler is set to.\n     * Note that because of Enigma's stepping, you need to set an actual Enigma to the previous\n     * position in order to get it to make a certain set of electrical connections when a button\n     * is pressed - this function *does* take this into account.\n     * However, as with the rest of the Bombe, it does not take stepping into account - the middle\n     * and slow rotors are treated as static.\n     * @return {string}\n     */\n    getPos() {\n        let result = \"\";\n        // Roll back the fast rotor by one step\n        let pos = Utils.mod(this.rotor.pos - 1, 26);\n        result += i2a(pos);\n        for (let i=0; i<this.baseScrambler.rotors.length; i++) {\n            pos = this.baseScrambler.rotors[i].pos;\n            result += i2a(pos);\n        }\n        return result.split(\"\").reverse().join(\"\");\n    }\n}\n\n/**\n * Bombe simulator class.\n */\nexport class BombeMachine {\n    /**\n     * Construct a Bombe.\n     *\n     * Note that there is no handling of offsets here: the crib specified must exactly match the\n     * ciphertext. It will check that the crib is sane (length is vaguely sensible and there's no\n     * matching characters between crib and ciphertext) but cannot check further - if it's wrong\n     * your results will be wrong!\n     *\n     * There is also no handling of rotor stepping - if the target Enigma stepped in the middle of\n     * your crib, you're out of luck. TODO: Allow specifying a step point - this is fairly easy to\n     * configure on a real Bombe, but we're not clear on whether it was ever actually done for\n     * real (there would almost certainly have been better ways of attacking in most situations\n     * than attempting to exhaust options for the stepping point, but in some circumstances, e.g.\n     * via Banburismus, the stepping point might have been known).\n     *\n     * @param {string[]} rotors - list of rotor spec strings (without step points!)\n     * @param {Object} reflector - Reflector object\n     * @param {string} ciphertext - The ciphertext to attack\n     * @param {string} crib - Known plaintext for this ciphertext\n     * @param {boolean} check - Whether to use the checking machine\n     * @param {function} update - Function to call to send status updates (optional)\n     */\n    constructor(rotors, reflector, ciphertext, crib, check, update=undefined) {\n        if (ciphertext.length < crib.length) {\n            throw new OperationError(\"Crib overruns supplied ciphertext\");\n        }\n        if (crib.length < 2) {\n            // This is the absolute bare minimum to be sane, and even then it's likely too short to\n            // be useful\n            throw new OperationError(\"Crib is too short\");\n        }\n        if (crib.length > 25) {\n            // A crib longer than this will definitely cause the middle rotor to step somewhere\n            // A shorter crib is preferable to reduce this chance, of course\n            throw new OperationError(\"Crib is too long\");\n        }\n        for (let i=0; i<crib.length; i++) {\n            if (ciphertext[i] === crib[i]) {\n                throw new OperationError(`Invalid crib: character ${ciphertext[i]} at pos ${i} in both ciphertext and crib`);\n            }\n        }\n        this.ciphertext = ciphertext;\n        this.crib = crib;\n        this.initRotors(rotors);\n        this.check = check;\n        this.updateFn = update;\n\n        const [mostConnected, edges] = this.makeMenu();\n\n        // This is the bundle of wires corresponding to the 26 letters within each of the 26\n        // possible nodes in the menu\n        this.wires = new Array(26*26);\n\n        // These are the pseudo-Engima devices corresponding to each edge in the menu, and the\n        // nodes in the menu they each connect to\n        this.scramblers = new Array();\n        for (let i=0; i<26; i++) {\n            this.scramblers.push(new Array());\n        }\n        this.sharedScrambler = new SharedScrambler(this.baseRotors.slice(1), reflector);\n        this.allScramblers = new Array();\n        this.indicator = undefined;\n        for (const edge of edges) {\n            const cRotor = this.baseRotors[0].copy();\n            const end1 = a2i(edge.node1.letter);\n            const end2 = a2i(edge.node2.letter);\n            const scrambler = new Scrambler(this.sharedScrambler, cRotor, edge.pos, end1, end2);\n            if (edge.pos === 0) {\n                this.indicator = scrambler;\n            }\n            this.scramblers[end1].push(scrambler);\n            this.scramblers[end2].push(scrambler);\n            this.allScramblers.push(scrambler);\n        }\n        // The Bombe uses a set of rotors to keep track of what settings it's testing. We cheat and\n        // use one of the actual scramblers if there's one in the right position, but if not we'll\n        // just create one.\n        if (this.indicator === undefined) {\n            this.indicator = new Scrambler(this.sharedScrambler, this.baseRotors[0].copy(), 0, undefined, undefined);\n            this.allScramblers.push(this.indicator);\n        }\n\n        this.testRegister = a2i(mostConnected.letter);\n        // This is an arbitrary letter other than the most connected letter\n        for (const edge of mostConnected.edges) {\n            this.testInput = [this.testRegister, a2i(edge.getOther(mostConnected).letter)];\n            break;\n        }\n    }\n\n    /**\n     * Build Rotor objects from list of rotor wiring strings.\n     * @param {string[]} rotors - List of rotor wiring strings\n     */\n    initRotors(rotors) {\n        // This is ordered from the Enigma fast rotor to the slow, so bottom to top for the Bombe\n        this.baseRotors = [];\n        for (const rstr of rotors) {\n            const rotor = new CopyRotor(rstr, \"\", \"A\", \"A\");\n            this.baseRotors.push(rotor);\n        }\n    }\n\n    /**\n     * Replace the rotors and reflector in all components of this Bombe.\n     * @param {string[]} rotors - List of rotor wiring strings\n     * @param {Object} reflector - Reflector object\n     */\n    changeRotors(rotors, reflector) {\n        // At the end of the run, the rotors are all back in the same position they started\n        this.initRotors(rotors);\n        this.sharedScrambler.changeRotors(this.baseRotors.slice(1), reflector);\n        for (const scrambler of this.allScramblers) {\n            scrambler.changeRotor(this.baseRotors[0].copy());\n        }\n    }\n\n    /**\n     * If we have a way of sending status messages, do so.\n     * @param {...*} msg - Message to send.\n     */\n    update(...msg) {\n        if (this.updateFn !== undefined) {\n            this.updateFn(...msg);\n        }\n    }\n\n    /**\n     * Recursive depth-first search on the menu graph.\n     * This is used to a) isolate unconnected sub-graphs, and b) count the number of loops in each\n     * of those graphs.\n     * @param {Object} node - Node object to start the search from\n     * @returns {[number, number, Object, number, Object[]} - loop count, node count, most connected\n     *      node, order of most connected node, list of edges in this sub-graph\n     */\n    dfs(node) {\n        let loops = 0;\n        let nNodes = 1;\n        let mostConnected = node;\n        let nConnections = mostConnected.edges.size;\n        let edges = new Set();\n        node.visited = true;\n        for (const edge of node.edges) {\n            if (edge.visited) {\n                // Already been here from the other end.\n                continue;\n            }\n            edge.visited = true;\n            edges.add(edge);\n            const other = edge.getOther(node);\n            if (other.visited) {\n                // We have a loop, record that and continue\n                loops += 1;\n                continue;\n            }\n            // This is a newly visited node\n            const [oLoops, oNNodes, oMostConnected, oNConnections, oEdges] = this.dfs(other);\n            loops += oLoops;\n            nNodes += oNNodes;\n            edges = new Set([...edges, ...oEdges]);\n            if (oNConnections > nConnections) {\n                mostConnected = oMostConnected;\n                nConnections = oNConnections;\n            }\n        }\n        return [loops, nNodes, mostConnected, nConnections, edges];\n    }\n\n    /**\n     * Build a menu from the ciphertext and crib.\n     * A menu is just a graph where letters in either the ciphertext or crib (Enigma is symmetric,\n     * so there's no difference mathematically) are nodes and states of the Enigma machine itself\n     * are the edges.\n     * Additionally, we want a single connected graph, and of the subgraphs available, we want the\n     * one with the most loops (since these generate feedback cycles which efficiently close off\n     * disallowed states).\n     * Finally, we want to identify the most connected node in that graph (as it's the best choice\n     * of measurement point).\n     * @returns [Object, Object[]] - the most connected node, and the list of edges in the subgraph\n     */\n    makeMenu() {\n        // First, we make a graph of all of the mappings given by the crib\n        // Make all nodes first\n        const nodes = new Map();\n        for (const c of this.ciphertext + this.crib) {\n            if (!nodes.has(c)) {\n                const node = new Node(c);\n                nodes.set(c, node);\n            }\n        }\n        // Then all edges\n        for (let i=0; i<this.crib.length; i++) {\n            const a = this.crib[i];\n            const b = this.ciphertext[i];\n            new Edge(i, nodes.get(a), nodes.get(b));\n        }\n        // list of [loop_count, node_count, most_connected_node, connections_on_most_connected, edges]\n        const graphs = [];\n        // Then, for each unconnected subgraph, we count the number of loops and nodes\n        for (const start of nodes.keys()) {\n            if (nodes.get(start).visited) {\n                continue;\n            }\n            const subgraph = this.dfs(nodes.get(start));\n            graphs.push(subgraph);\n        }\n        // Return the subgraph with the most loops (ties broken by node count)\n        graphs.sort((a, b) => {\n            let result = b[0] - a[0];\n            if (result === 0) {\n                result = b[1] - a[1];\n            }\n            return result;\n        });\n        this.nLoops = graphs[0][0];\n        return [graphs[0][2], graphs[0][4]];\n    }\n\n    /**\n     * Bombe electrical simulation. Energise a wire. For all connected wires (both via the diagonal\n     * board and via the scramblers), energise them too, recursively.\n     * @param {number} i - Bombe wire bundle\n     * @param {number} j - Bombe stecker hypothesis wire within bundle\n     */\n    energise(i, j) {\n        const idx = 26*i + j;\n        if (this.wires[idx]) {\n            return;\n        }\n        this.wires[idx] = true;\n        // Welchman's diagonal board: if A steckers to B, that implies B steckers to A. Handle\n        // both.\n        const idxPair = 26*j + i;\n        this.wires[idxPair] = true;\n        if (i === this.testRegister || j === this.testRegister) {\n            this.energiseCount++;\n            if (this.energiseCount === 26) {\n                // no point continuing, bail out\n                return;\n            }\n        }\n\n        for (let k=0; k<this.scramblers[i].length; k++) {\n            const scrambler = this.scramblers[i][k];\n            const out = scrambler.transform(j);\n            const other = scrambler.getOtherEnd(i);\n            // Lift the pre-check before the call, to save some function call overhead\n            const otherIdx = 26*other + out;\n            if (!this.wires[otherIdx]) {\n                this.energise(other, out);\n                if (this.energiseCount === 26) {\n                    return;\n                }\n            }\n        }\n        if (i === j) {\n            return;\n        }\n        for (let k=0; k<this.scramblers[j].length; k++) {\n            const scrambler = this.scramblers[j][k];\n            const out = scrambler.transform(i);\n            const other = scrambler.getOtherEnd(j);\n            const otherIdx = 26*other + out;\n            if (!this.wires[otherIdx]) {\n                this.energise(other, out);\n                if (this.energiseCount === 26) {\n                    return;\n                }\n            }\n        }\n    }\n\n    /**\n     * Trial decryption at the current setting.\n     * Used after we get a stop.\n     * This applies the detected stecker pair if we have one. It does not handle the other\n     * steckering or stepping (which is why we limit it to 26 characters, since it's guaranteed to\n     * be wrong after that anyway).\n     * @param {string} stecker - Known stecker spec string.\n     * @returns {string}\n     */\n    tryDecrypt(stecker) {\n        const fastRotor = this.indicator.rotor;\n        const initialPos = fastRotor.pos;\n        const res = [];\n        const plugboard = new Plugboard(stecker);\n        // The indicator scrambler starts in the right place for the beginning of the ciphertext.\n        for (let i=0; i<Math.min(26, this.ciphertext.length); i++) {\n            const t = this.indicator.transform(plugboard.transform(a2i(this.ciphertext[i])));\n            res.push(i2a(plugboard.transform(t)));\n            this.indicator.step(1);\n        }\n        fastRotor.pos = initialPos;\n        return res.join(\"\");\n    }\n\n    /**\n     * Format a steckered pair, in sorted order to allow uniquing.\n     * @param {number} a - A letter\n     * @param {number} b - Its stecker pair\n     * @returns {string}\n     */\n    formatPair(a, b) {\n        if (a < b) {\n            return `${i2a(a)}${i2a(b)}`;\n        }\n        return `${i2a(b)}${i2a(a)}`;\n    }\n\n    /**\n     * The checking machine was used to manually verify Bombe stops. Using a device which was\n     * effectively a non-stepping Enigma, the user would walk through each of the links in the\n     * menu at the rotor positions determined by the Bombe. By starting with the stecker pair the\n     * Bombe gives us, we find the stecker pair of each connected letter in the graph, and so on.\n     * If a contradiction is reached, the stop is invalid. If not, we have most (but not\n     * necessarily all) of the plugboard connections.\n     * You will notice that this procedure is exactly the same as what the Bombe itself does, only\n     * we start with an assumed good hypothesis and read out the stecker pair for every letter.\n     * On the real hardware that wasn't practical, but fortunately we're not the real hardware, so\n     * we don't need to implement the manual checking machine procedure.\n     * @param {number} pair - The stecker pair of the test register.\n     * @returns {string} - The empty string for invalid stops, or a plugboard configuration string\n     *      containing all known pairs.\n     */\n    checkingMachine(pair) {\n        if (pair !== this.testInput[1]) {\n            // We have a new hypothesis for this stop - apply the new one.\n            // De-energise the board\n            for (let i=0; i<this.wires.length; i++) {\n                this.wires[i] = false;\n            }\n            this.energiseCount = 0;\n            // Re-energise with the corrected hypothesis\n            this.energise(this.testRegister, pair);\n        }\n\n        const results = new Set();\n        results.add(this.formatPair(this.testRegister, pair));\n        for (let i=0; i<26; i++) {\n            let count = 0;\n            let other;\n            for (let j=0; j<26; j++) {\n                if (this.wires[i*26 + j]) {\n                    count++;\n                    other = j;\n                }\n            }\n            if (count > 1) {\n                // This is an invalid stop.\n                return \"\";\n            } else if (count === 0) {\n                // No information about steckering from this wire\n                continue;\n            }\n            results.add(this.formatPair(i, other));\n        }\n        return [...results].join(\" \");\n    }\n\n    /**\n     * Check to see if the Bombe has stopped. If so, process the stop.\n     * @returns {(undefined|string[3])} - Undefined for no stop, or [rotor settings, plugboard settings, decryption preview]\n     */\n    checkStop() {\n        // Count the energised outputs\n        const count = this.energiseCount;\n        if (count === 26) {\n            return undefined;\n        }\n        // If it's not all of them, we have a stop\n        let steckerPair;\n        // The Bombe tells us one stecker pair as well. The input wire and test register we\n        // started with are hypothesised to be a stecker pair.\n        if (count === 25) {\n            // Our steckering hypothesis is wrong. Correct value is the un-energised wire.\n            for (let j=0; j<26; j++) {\n                if (!this.wires[26*this.testRegister + j]) {\n                    steckerPair = j;\n                    break;\n                }\n            }\n        } else if (count === 1) {\n            // This means our hypothesis for the steckering is correct.\n            steckerPair = this.testInput[1];\n        } else {\n            // This was known as a \"boxing stop\" - we have a stop but not a single hypothesis.\n            // If this happens a lot it implies the menu isn't good enough.\n            // If we have the checking machine enabled, we're going to just check each wire in\n            // turn. If we get 0 or 1 hit, great.\n            // If we get multiple hits, or the checking machine is off, the user will just have to\n            // deal with it.\n            if (!this.check) {\n                // We can't draw any conclusions about the steckering (one could maybe suggest\n                // options in some cases, but too hard to present clearly).\n                return [this.indicator.getPos(), \"??\", this.tryDecrypt(\"\")];\n            }\n            let stecker = undefined;\n            for (let i = 0; i < 26; i++) {\n                const newStecker = this.checkingMachine(i);\n                if (newStecker !== \"\") {\n                    if (stecker !== undefined) {\n                        // Multiple hypotheses can't be ruled out.\n                        return [this.indicator.getPos(), \"??\", this.tryDecrypt(\"\")];\n                    }\n                    stecker = newStecker;\n                }\n            }\n            if (stecker === undefined) {\n                // Checking machine ruled all possibilities out.\n                return undefined;\n            }\n            // If we got here, there was just one possibility allowed by the checking machine. Success.\n            return [this.indicator.getPos(), stecker, this.tryDecrypt(stecker)];\n        }\n        let stecker;\n        if (this.check) {\n            stecker = this.checkingMachine(steckerPair);\n            if (stecker === \"\") {\n                // Invalid stop - don't count it, don't return it\n                return undefined;\n            }\n        } else {\n            stecker = `${i2a(this.testRegister)}${i2a(steckerPair)}`;\n        }\n        const testDecrypt = this.tryDecrypt(stecker);\n        return [this.indicator.getPos(), stecker, testDecrypt];\n    }\n\n    /**\n     * Having set up the Bombe, do the actual attack run. This tries every possible rotor setting\n     * and attempts to logically invalidate them. If it can't, it's added to the list of candidate\n     * solutions.\n     * @returns {string[][3]} - list of 3-tuples of candidate rotor setting, plugboard settings, and decryption preview\n     */\n    run() {\n        let stops = 0;\n        const result = [];\n        // For each possible rotor setting\n        const nChecks = Math.pow(26, this.baseRotors.length);\n        for (let i=1; i<=nChecks; i++) {\n            // Benchmarking suggests this is faster than using .fill()\n            for (let i=0; i<this.wires.length; i++) {\n                this.wires[i] = false;\n            }\n            this.energiseCount = 0;\n            // Energise the test input, follow the current through each scrambler\n            // (and the diagonal board)\n            this.energise(...this.testInput);\n\n            const stop = this.checkStop();\n            if (stop !== undefined) {\n                stops++;\n                result.push(stop);\n            }\n            // Step all the scramblers\n            // This loop counts how many rotors have reached their starting position (meaning the\n            // next one needs to step as well)\n            let n = 1;\n            for (let j=1; j<this.baseRotors.length; j++) {\n                if ((i % Math.pow(26, j)) === 0) {\n                    n++;\n                } else {\n                    break;\n                }\n            }\n            if (n > 1) {\n                this.sharedScrambler.step(n);\n            }\n            for (const scrambler of this.allScramblers) {\n                scrambler.step();\n            }\n            // Send status messages at what seems to be a reasonably sensible frequency\n            // (note this won't be triggered on 3-rotor runs - they run fast enough it doesn't seem necessary)\n            if (n > 3) {\n                this.update(this.nLoops, stops, i/nChecks);\n            }\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/core/lib/Braille.mjs",
    "content": "/**\n * Braille resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/**\n * Braille lookup table.\n */\nexport const BRAILLE_LOOKUP = {\n    ascii: \" A1B'K2L@CIF/MSP\\\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\\\0Z7(_?W]#Y)=\",\n    dot6:  \"⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿\"\n};\n"
  },
  {
    "path": "src/core/lib/CanvasComponents.mjs",
    "content": "/**\n * Various components for drawing diagrams on an HTML5 canvas.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n/**\n * Draws a line from one point to another\n *\n * @param ctx\n * @param startX\n * @param startY\n * @param endX\n * @param endY\n */\nexport function drawLine(ctx, startX, startY, endX, endY) {\n    ctx.beginPath();\n    ctx.moveTo(startX, startY);\n    ctx.lineTo(endX, endY);\n    ctx.closePath();\n    ctx.stroke();\n}\n\n/**\n * Draws a bar chart on the canvas.\n *\n * @param canvas\n * @param scores\n * @param xAxisLabel\n * @param yAxisLabel\n * @param numXLabels\n * @param numYLabels\n * @param fontSize\n */\nexport function drawBarChart(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {\n    fontSize = fontSize || 15;\n    if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {\n        numXLabels = Math.round(canvas.width / 50);\n    }\n    if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {\n        numYLabels = Math.round(canvas.height / 50);\n    }\n\n    // Graph properties\n    const ctx = canvas.getContext(\"2d\"),\n        leftPadding = canvas.width * 0.08,\n        rightPadding = canvas.width * 0.03,\n        topPadding = canvas.height * 0.08,\n        bottomPadding = canvas.height * 0.2,\n        graphHeight = canvas.height - topPadding - bottomPadding,\n        graphWidth = canvas.width - leftPadding - rightPadding,\n        base = topPadding + graphHeight,\n        ceil = topPadding;\n\n    ctx.font = fontSize + \"px Arial\";\n\n    // Draw axis\n    ctx.lineWidth = \"1.0\";\n    ctx.strokeStyle = \"#444\";\n    drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x\n    drawLine(ctx, leftPadding, base, leftPadding, ceil); // y\n\n    // Bar properties\n    const barPadding = graphWidth * 0.003,\n        barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,\n        max = Math.max.apply(Math, scores);\n    let currX = leftPadding + barPadding;\n\n    // Draw bars\n    ctx.fillStyle = \"green\";\n    for (let i = 0; i < scores.length; i++) {\n        const h = scores[i] / max * graphHeight;\n        ctx.fillRect(currX, base - h, barWidth, h);\n        currX += barWidth + barPadding;\n    }\n\n    // Mark x axis\n    ctx.fillStyle = \"black\";\n    ctx.textAlign = \"center\";\n    currX = leftPadding + barPadding;\n    if (numXLabels >= scores.length) {\n        // Mark every score\n        for (let i = 0; i <= scores.length; i++) {\n            ctx.fillText(i, currX, base + (bottomPadding * 0.3));\n            currX += barWidth + barPadding;\n        }\n    } else {\n        // Mark some scores\n        for (let i = 0; i <= numXLabels; i++) {\n            const val = Math.ceil((scores.length / numXLabels) * i);\n            currX = (graphWidth / numXLabels) * i + leftPadding;\n            ctx.fillText(val, currX, base + (bottomPadding * 0.3));\n        }\n    }\n\n    // Mark y axis\n    ctx.textAlign = \"right\";\n    let currY;\n    if (numYLabels >= max) {\n        // Mark every increment\n        for (let i = 0; i <= max; i++) {\n            currY = base - (i / max * graphHeight) + fontSize / 3;\n            ctx.fillText(i, leftPadding * 0.8, currY);\n        }\n    } else {\n        // Mark some increments\n        for (let i = 0; i <= numYLabels; i++) {\n            const val = Math.ceil((max / numYLabels) * i);\n            currY = base - (val / max * graphHeight) + fontSize / 3;\n            ctx.fillText(val, leftPadding * 0.8, currY);\n        }\n    }\n\n    // Label x axis\n    if (xAxisLabel) {\n        ctx.textAlign = \"center\";\n        ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);\n    }\n\n    // Label y axis\n    if (yAxisLabel) {\n        ctx.save();\n        const x = leftPadding * 0.3,\n            y = graphHeight / 2 + topPadding;\n        ctx.translate(x, y);\n        ctx.rotate(-Math.PI / 2);\n        ctx.textAlign = \"center\";\n        ctx.fillText(yAxisLabel, 0, 0);\n        ctx.restore();\n    }\n}\n\n/**\n * Draws a scale bar on the canvas.\n *\n * @param canvas\n * @param score\n * @param max\n * @param markings\n */\nexport function drawScaleBar(canvas, score, max, markings) {\n    // Bar properties\n    const ctx = canvas.getContext(\"2d\"),\n        leftPadding = canvas.width * 0.01,\n        rightPadding = canvas.width * 0.01,\n        topPadding = canvas.height * 0.1,\n        bottomPadding = canvas.height * 0.35,\n        barHeight = canvas.height - topPadding - bottomPadding,\n        barWidth = canvas.width - leftPadding - rightPadding;\n\n    // Scale properties\n    const proportion = score / max;\n\n    // Draw bar outline\n    ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);\n\n    // Shade in up to proportion\n    const grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);\n    grad.addColorStop(0, \"green\");\n    grad.addColorStop(0.5, \"gold\");\n    grad.addColorStop(1, \"red\");\n    ctx.fillStyle = grad;\n    ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);\n\n    // Add markings\n    let x0, y0, x1, y1;\n    ctx.fillStyle = \"black\";\n    ctx.textAlign = \"center\";\n    ctx.font = \"13px Arial\";\n    for (let i = 0; i < markings.length; i++) {\n        // Draw min line down\n        x0 = barWidth / max * markings[i].min + leftPadding;\n        y0 = topPadding + barHeight + (bottomPadding * 0.1);\n        x1 = x0;\n        y1 = topPadding + barHeight + (bottomPadding * 0.3);\n        drawLine(ctx, x0, y0, x1, y1);\n\n        // Draw max line down\n        x0 = barWidth / max * markings[i].max + leftPadding;\n        x1 = x0;\n        drawLine(ctx, x0, y0, x1, y1);\n\n        // Join min and max lines\n        x0 = barWidth / max * markings[i].min + leftPadding;\n        y0 = topPadding + barHeight + (bottomPadding * 0.3);\n        x1 = barWidth / max * markings[i].max + leftPadding;\n        y1 = y0;\n        drawLine(ctx, x0, y0, x1, y1);\n\n        // Add label\n        if (markings[i].max >= max * 0.9) {\n            ctx.textAlign = \"right\";\n            x0 = x1;\n        } else if (markings[i].max <= max * 0.1) {\n            ctx.textAlign = \"left\";\n        } else {\n            x0 = x0 + (x1 - x0) / 2;\n        }\n        y0 = topPadding + barHeight + (bottomPadding * 0.8);\n        ctx.fillText(markings[i].label, x0, y0);\n    }\n}\n"
  },
  {
    "path": "src/core/lib/Charts.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * @constant\n * @default\n */\nexport const RECORD_DELIMITER_OPTIONS = [\"Line feed\", \"CRLF\"];\n\n\n/**\n * @constant\n * @default\n */\nexport const FIELD_DELIMITER_OPTIONS = [\"Space\", \"Comma\", \"Semi-colon\", \"Colon\", \"Tab\"];\n\n\n/**\n * Default from colour\n *\n * @constant\n * @default\n */\nexport const COLOURS = {\n    min: \"white\",\n    max: \"black\"\n};\n\n\n/**\n * Gets values from input for a plot.\n *\n * @param {string} input\n * @param {string} recordDelimiter\n * @param {string} fieldDelimiter\n * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record\n * @param {number} length\n * @returns {Object[]}\n */\nexport function getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) {\n    let headings;\n    const values = [];\n\n    input\n        .split(recordDelimiter)\n        .forEach((row, rowIndex) => {\n            const split = row.split(fieldDelimiter);\n            if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`);\n\n            if (columnHeadingsAreIncluded && rowIndex === 0) {\n                headings = split;\n            } else {\n                values.push(split);\n            }\n        });\n    return { headings, values };\n}\n\n\n/**\n * Gets values from input for a scatter plot.\n *\n * @param {string} input\n * @param {string} recordDelimiter\n * @param {string} fieldDelimiter\n * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record\n * @returns {Object[]}\n */\nexport function getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {\n    let { headings, values } = getValues(\n        input,\n        recordDelimiter,\n        fieldDelimiter,\n        columnHeadingsAreIncluded,\n        2\n    );\n\n    if (headings) {\n        headings = {x: headings[0], y: headings[1]};\n    }\n\n    values = values.map(row => {\n        const x = parseFloat(row[0]),\n            y = parseFloat(row[1]);\n\n        if (Number.isNaN(x)) throw new OperationError(\"Values must be numbers in base 10.\");\n        if (Number.isNaN(y)) throw new OperationError(\"Values must be numbers in base 10.\");\n\n        return [x, y];\n    });\n\n    return { headings, values };\n}\n\n\n/**\n * Gets values from input for a scatter plot with colour from the third column.\n *\n * @param {string} input\n * @param {string} recordDelimiter\n * @param {string} fieldDelimiter\n * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record\n * @returns {Object[]}\n */\nexport function getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {\n    let { headings, values } = getValues(\n        input,\n        recordDelimiter, fieldDelimiter,\n        columnHeadingsAreIncluded,\n        3\n    );\n\n    if (headings) {\n        headings = {x: headings[0], y: headings[1]};\n    }\n\n    values = values.map(row => {\n        const x = parseFloat(row[0]),\n            y = parseFloat(row[1]),\n            colour = row[2];\n\n        if (Number.isNaN(x)) throw new OperationError(\"Values must be numbers in base 10.\");\n        if (Number.isNaN(y)) throw new OperationError(\"Values must be numbers in base 10.\");\n\n        return [x, y, Utils.escapeHtml(colour)];\n    });\n\n    return { headings, values };\n}\n\n/**\n * Gets values from input for a time series plot.\n *\n * @param {string} input\n * @param {string} recordDelimiter\n * @param {string} fieldDelimiter\n * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record\n * @returns {Object[]}\n */\nexport function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {\n    const { values } = getValues(\n        input,\n        recordDelimiter, fieldDelimiter,\n        false,\n        3\n    );\n\n    let xValues = new Set();\n    const series = {};\n\n    values.forEach(row => {\n        const serie = row[0],\n            xVal = row[1],\n            val = parseFloat(row[2]);\n\n        if (Number.isNaN(val)) throw new OperationError(\"Values must be numbers in base 10.\");\n\n        xValues.add(xVal);\n        if (typeof series[serie] === \"undefined\") series[serie] = {};\n        series[serie][xVal] = val;\n    });\n\n    xValues = new Array(...xValues);\n\n    const seriesList = [];\n    for (const seriesName in series) {\n        const serie = series[seriesName];\n        seriesList.push({name: seriesName, data: serie});\n    }\n\n    return { xValues, series: seriesList };\n}\n"
  },
  {
    "path": "src/core/lib/ChrEnc.mjs",
    "content": "/**\n * Character encoding resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport cptable from \"codepage\";\n\n/**\n * Character encoding format mappings.\n */\nexport const CHR_ENC_CODE_PAGES = {\n    \"UTF-8 (65001)\": 65001,\n    \"UTF-7 (65000)\": 65000,\n    \"UTF-16LE (1200)\": 1200,\n    \"UTF-16BE (1201)\": 1201,\n    \"UTF-32LE (12000)\": 12000,\n    \"UTF-32BE (12001)\": 12001,\n    \"IBM EBCDIC International (500)\": 500,\n    \"IBM EBCDIC US-Canada (37)\": 37,\n    \"IBM EBCDIC Multilingual/ROECE (Latin 2) (870)\": 870,\n    \"IBM EBCDIC Greek Modern (875)\": 875,\n    \"IBM EBCDIC French (1010)\": 1010,\n    \"IBM EBCDIC Turkish (Latin 5) (1026)\": 1026,\n    \"IBM EBCDIC Latin 1/Open System (1047)\": 1047,\n    \"IBM EBCDIC Lao (1132/1133/1341)\": 1132,\n    \"IBM EBCDIC US-Canada (037 + Euro symbol) (1140)\": 1140,\n    \"IBM EBCDIC Germany (20273 + Euro symbol) (1141)\": 1141,\n    \"IBM EBCDIC Denmark-Norway (20277 + Euro symbol) (1142)\": 1142,\n    \"IBM EBCDIC Finland-Sweden (20278 + Euro symbol) (1143)\": 1143,\n    \"IBM EBCDIC Italy (20280 + Euro symbol) (1144)\": 1144,\n    \"IBM EBCDIC Latin America-Spain (20284 + Euro symbol) (1145)\": 1145,\n    \"IBM EBCDIC United Kingdom (20285 + Euro symbol) (1146)\": 1146,\n    \"IBM EBCDIC France (20297 + Euro symbol) (1147)\": 1147,\n    \"IBM EBCDIC International (500 + Euro symbol) (1148)\": 1148,\n    \"IBM EBCDIC Icelandic (20871 + Euro symbol) (1149)\": 1149,\n    \"IBM EBCDIC Germany (20273)\": 20273,\n    \"IBM EBCDIC Denmark-Norway (20277)\": 20277,\n    \"IBM EBCDIC Finland-Sweden (20278)\": 20278,\n    \"IBM EBCDIC Italy (20280)\": 20280,\n    \"IBM EBCDIC Latin America-Spain (20284)\": 20284,\n    \"IBM EBCDIC United Kingdom (20285)\": 20285,\n    \"IBM EBCDIC Japanese Katakana Extended (20290)\": 20290,\n    \"IBM EBCDIC France (20297)\": 20297,\n    \"IBM EBCDIC Arabic (20420)\": 20420,\n    \"IBM EBCDIC Greek (20423)\": 20423,\n    \"IBM EBCDIC Hebrew (20424)\": 20424,\n    \"IBM EBCDIC Korean Extended (20833)\": 20833,\n    \"IBM EBCDIC Thai (20838)\": 20838,\n    \"IBM EBCDIC Icelandic (20871)\": 20871,\n    \"IBM EBCDIC Cyrillic Russian (20880)\": 20880,\n    \"IBM EBCDIC Turkish (20905)\": 20905,\n    \"IBM EBCDIC Latin 1/Open System (1047 + Euro symbol) (20924)\": 20924,\n    \"IBM EBCDIC Cyrillic Serbian-Bulgarian (21025)\": 21025,\n    \"OEM United States (437)\": 437,\n    \"OEM Greek (formerly 437G); Greek (DOS) (737)\": 737,\n    \"OEM Baltic; Baltic (DOS) (775)\": 775,\n    \"OEM Russian; Cyrillic + Euro symbol (808)\": 808,\n    \"OEM Multilingual Latin 1; Western European (DOS) (850)\": 850,\n    \"OEM Latin 2; Central European (DOS) (852)\": 852,\n    \"OEM Cyrillic (primarily Russian) (855)\": 855,\n    \"OEM Turkish; Turkish (DOS) (857)\": 857,\n    \"OEM Multilingual Latin 1 + Euro symbol (858)\": 858,\n    \"OEM Portuguese; Portuguese (DOS) (860)\": 860,\n    \"OEM Icelandic; Icelandic (DOS) (861)\": 861,\n    \"OEM Hebrew; Hebrew (DOS) (862)\": 862,\n    \"OEM French Canadian; French Canadian (DOS) (863)\": 863,\n    \"OEM Arabic; Arabic (864) (864)\": 864,\n    \"OEM Nordic; Nordic (DOS) (865)\": 865,\n    \"OEM Russian; Cyrillic (DOS) (866)\": 866,\n    \"OEM Modern Greek; Greek, Modern (DOS) (869)\": 869,\n    \"OEM Cyrillic (primarily Russian) + Euro Symbol (872)\": 872,\n    \"Windows-874 Thai (874)\": 874,\n    \"Windows-1250 Central European (1250)\": 1250,\n    \"Windows-1251 Cyrillic (1251)\": 1251,\n    \"Windows-1252 Latin (1252)\": 1252,\n    \"Windows-1253 Greek (1253)\": 1253,\n    \"Windows-1254 Turkish (1254)\": 1254,\n    \"Windows-1255 Hebrew (1255)\": 1255,\n    \"Windows-1256 Arabic (1256)\": 1256,\n    \"Windows-1257 Baltic (1257)\": 1257,\n    \"Windows-1258 Vietnam (1258)\": 1258,\n    \"ISO-8859-1 Latin 1 Western European (28591)\": 28591,\n    \"ISO-8859-2 Latin 2 Central European (28592)\": 28592,\n    \"ISO-8859-3 Latin 3 South European (28593)\": 28593,\n    \"ISO-8859-4 Latin 4 North European (28594)\": 28594,\n    \"ISO-8859-5 Latin/Cyrillic (28595)\": 28595,\n    \"ISO-8859-6 Latin/Arabic (28596)\": 28596,\n    \"ISO-8859-7 Latin/Greek (28597)\": 28597,\n    \"ISO-8859-8 Latin/Hebrew (28598)\": 28598,\n    \"ISO 8859-8 Hebrew (ISO-Logical) (38598)\": 38598,\n    \"ISO-8859-9 Latin 5 Turkish (28599)\": 28599,\n    \"ISO-8859-10 Latin 6 Nordic (28600)\": 28600,\n    \"ISO-8859-11 Latin/Thai (28601)\": 28601,\n    \"ISO-8859-13 Latin 7 Baltic Rim (28603)\": 28603,\n    \"ISO-8859-14 Latin 8 Celtic (28604)\": 28604,\n    \"ISO-8859-15 Latin 9 (28605)\": 28605,\n    \"ISO-8859-16 Latin 10 (28606)\": 28606,\n    \"ISO 2022 JIS Japanese with no halfwidth Katakana (50220)\": 50220,\n    \"ISO 2022 JIS Japanese with halfwidth Katakana (50221)\": 50221,\n    \"ISO 2022 Japanese JIS X 0201-1989 (1 byte Kana-SO/SI) (50222)\": 50222,\n    \"ISO 2022 Korean (50225)\": 50225,\n    \"ISO 2022 Simplified Chinese (50227)\": 50227,\n    \"ISO 6937 Non-Spacing Accent (20269)\": 20269,\n    \"EUC Japanese (51932)\": 51932,\n    \"EUC Simplified Chinese (51936)\": 51936,\n    \"EUC Korean (51949)\": 51949,\n    \"ISCII Devanagari (57002)\": 57002,\n    \"ISCII Bengali (57003)\": 57003,\n    \"ISCII Tamil (57004)\": 57004,\n    \"ISCII Telugu (57005)\": 57005,\n    \"ISCII Assamese (57006)\": 57006,\n    \"ISCII Oriya (57007)\": 57007,\n    \"ISCII Kannada (57008)\": 57008,\n    \"ISCII Malayalam (57009)\": 57009,\n    \"ISCII Gujarati (57010)\": 57010,\n    \"ISCII Punjabi (57011)\": 57011,\n    \"Japanese Shift-JIS (932)\": 932,\n    \"Simplified Chinese GBK (936)\": 936,\n    \"Korean (949)\": 949,\n    \"Traditional Chinese Big5 (950)\": 950,\n    \"US-ASCII (7-bit) (20127)\": 20127,\n    \"Simplified Chinese GB2312 (20936)\": 20936,\n    \"KOI8-R Russian Cyrillic (20866)\": 20866,\n    \"KOI8-U Ukrainian Cyrillic (21866)\": 21866,\n    \"Mazovia (Polish) MS-DOS (620)\": 620,\n    \"Arabic (ASMO 708) (708)\": 708,\n    \"Arabic (Transparent ASMO); Arabic (DOS) (720)\": 720,\n    \"Kamenický (Czech) MS-DOS (895)\": 895,\n    \"Korean (Johab) (1361)\": 1361,\n    \"MAC Roman (10000)\": 10000,\n    \"Japanese (Mac) (10001)\": 10001,\n    \"MAC Traditional Chinese (Big5) (10002)\": 10002,\n    \"Korean (Mac) (10003)\": 10003,\n    \"Arabic (Mac) (10004)\": 10004,\n    \"Hebrew (Mac) (10005)\": 10005,\n    \"Greek (Mac) (10006)\": 10006,\n    \"Cyrillic (Mac) (10007)\": 10007,\n    \"MAC Simplified Chinese (GB 2312) (10008)\": 10008,\n    \"Romanian (Mac) (10010)\": 10010,\n    \"Ukrainian (Mac) (10017)\": 10017,\n    \"Thai (Mac) (10021)\": 10021,\n    \"MAC Latin 2 (Central European) (10029)\": 10029,\n    \"Icelandic (Mac) (10079)\": 10079,\n    \"Turkish (Mac) (10081)\": 10081,\n    \"Croatian (Mac) (10082)\": 10082,\n    \"CNS Taiwan (Chinese Traditional) (20000)\": 20000,\n    \"TCA Taiwan (20001)\": 20001,\n    \"ETEN Taiwan (Chinese Traditional) (20002)\": 20002,\n    \"IBM5550 Taiwan (20003)\": 20003,\n    \"TeleText Taiwan (20004)\": 20004,\n    \"Wang Taiwan (20005)\": 20005,\n    \"Western European IA5 (IRV International Alphabet 5) (20105)\": 20105,\n    \"IA5 German (7-bit) (20106)\": 20106,\n    \"IA5 Swedish (7-bit) (20107)\": 20107,\n    \"IA5 Norwegian (7-bit) (20108)\": 20108,\n    \"T.61 (20261)\": 20261,\n    \"Japanese (JIS 0208-1990 and 0212-1990) (20932)\": 20932,\n    \"Korean Wansung (20949)\": 20949,\n    \"Extended/Ext Alpha Lowercase (21027)\": 21027,\n    \"Europa 3 (29001)\": 29001,\n    \"Atari ST/TT (47451)\": 47451,\n    \"HZ-GB2312 Simplified Chinese (52936)\": 52936,\n    \"Simplified Chinese GB18030 (54936)\": 54936,\n};\n\n\nexport const CHR_ENC_SIMPLE_LOOKUP = {};\nexport const CHR_ENC_SIMPLE_REVERSE_LOOKUP = {};\n\nfor (const name in CHR_ENC_CODE_PAGES) {\n    const simpleName = name.match(/(^.+)\\([\\d/]+\\)$/)[1];\n\n    CHR_ENC_SIMPLE_LOOKUP[simpleName] = CHR_ENC_CODE_PAGES[name];\n    CHR_ENC_SIMPLE_REVERSE_LOOKUP[CHR_ENC_CODE_PAGES[name]] = simpleName;\n}\n\n\n/**\n * Returns the width of the character set for the given codepage.\n * For example, UTF-8 is a Single Byte Character Set, whereas\n * UTF-16 is a Double Byte Character Set.\n *\n * @param {number} page - The codepage number\n * @returns {number}\n */\nexport function chrEncWidth(page) {\n    if (typeof page !== \"number\") return 0;\n\n    // Raw Bytes have a width of 1\n    if (page === 0) return 1;\n\n    const pageStr = page.toString();\n    // Confirm this page is legitimate\n    if (!Object.prototype.hasOwnProperty.call(CHR_ENC_SIMPLE_REVERSE_LOOKUP, pageStr))\n        return 0;\n\n    // Statically defined code pages\n    if (Object.prototype.hasOwnProperty.call(cptable, pageStr))\n        return cptable[pageStr].dec.length > 256 ? 2 : 1;\n\n    // Cached code pages\n    if (cptable.utils.cache.sbcs.includes(pageStr))\n        return 1;\n    if (cptable.utils.cache.dbcs.includes(pageStr))\n        return 2;\n\n    // Dynamically generated code pages\n    if (Object.prototype.hasOwnProperty.call(cptable.utils.magic, pageStr)) {\n        // Generate a single character and measure it\n        const a = cptable.utils.encode(page, \"a\");\n        return a.length;\n    }\n\n    return 0;\n}\n\n/**\n * Unicode Normalisation Forms\n *\n * @author Matthieu [m@tthieu.xyz]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nexport const UNICODE_NORMALISATION_FORMS = [\"NFD\", \"NFC\", \"NFKD\", \"NFKC\"];\n\n\n/**\n * Detects whether the input buffer is valid UTF8.\n *\n * @param {ArrayBuffer} data\n * @returns {number} - 0 = not UTF8, 1 = ASCII, 2 = UTF8\n */\nexport function isUTF8(data) {\n    const bytes = new Uint8Array(data);\n    let i = 0;\n    let onlyASCII = true;\n    while (i < bytes.length) {\n        if (( // ASCII\n            bytes[i] === 0x09 ||\n            bytes[i] === 0x0A ||\n            bytes[i] === 0x0D ||\n            (0x20 <= bytes[i] && bytes[i] <= 0x7E)\n        )) {\n            i += 1;\n            continue;\n        }\n\n        onlyASCII = false;\n\n        if (( // non-overlong 2-byte\n            (0xC2 <= bytes[i] && bytes[i] <= 0xDF) &&\n            (0x80 <= bytes[i+1] && bytes[i+1] <= 0xBF)\n        )) {\n            i += 2;\n            continue;\n        }\n\n        if (( // excluding overlongs\n            bytes[i] === 0xE0 &&\n            (0xA0 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&\n            (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)\n        ) ||\n        ( // straight 3-byte\n            ((0xE1 <= bytes[i] && bytes[i] <= 0xEC) ||\n            bytes[i] === 0xEE ||\n            bytes[i] === 0xEF) &&\n            (0x80 <= bytes[i + 1] && bytes[i+1] <= 0xBF) &&\n            (0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF)\n        ) ||\n        ( // excluding surrogates\n            bytes[i] === 0xED &&\n            (0x80 <= bytes[i+1] && bytes[i+1] <= 0x9F) &&\n            (0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF)\n        )) {\n            i += 3;\n            continue;\n        }\n\n        if (( // planes 1-3\n            bytes[i] === 0xF0 &&\n            (0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&\n            (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&\n            (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)\n        ) ||\n        ( // planes 4-15\n            (0xF1 <= bytes[i] && bytes[i] <= 0xF3) &&\n            (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&\n            (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&\n            (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)\n        ) ||\n        ( // plane 16\n            bytes[i] === 0xF4 &&\n            (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8F) &&\n            (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&\n            (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)\n        )) {\n            i += 4;\n            continue;\n        }\n\n        return 0;\n    }\n\n    return onlyASCII ? 1 : 2;\n}\n"
  },
  {
    "path": "src/core/lib/CipherSaber2.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nexport function encode(tempIVP, key, rounds, input) {\n    const ivp = new Uint8Array([...key, ...tempIVP]);\n    const state = new Array(256).fill(0);\n    let j = 0, i = 0;\n    const result = [];\n\n    // Mixing states based off of IV.\n    for (let i = 0; i < 256; i++)\n        state[i] = i;\n    const ivpLength = ivp.length;\n    for (let r = 0; r < rounds; r ++) {\n        for (let k = 0; k < 256; k++) {\n            j = (j + state[k] + ivp[k % ivpLength]) % 256;\n            [state[k], state[j]] = [state[j], state[k]];\n        }\n    }\n    j = 0;\n    i = 0;\n\n    // XOR cipher with key.\n    for (let x = 0; x < input.length; x++) {\n        i = (++i) % 256;\n        j = (j + state[i]) % 256;\n        [state[i], state[j]] = [state[j], state[i]];\n        const n = (state[i] + state[j]) % 256;\n        result.push(state[n] ^ input[x]);\n    }\n    return result;\n}\n"
  },
  {
    "path": "src/core/lib/Ciphers.mjs",
    "content": "/**\n * Cipher functions.\n *\n * @author Matt C [matt@artemisbot.uk]\n * @author n1474335 [n1474335@gmail.com]\n * @author Evie H [evie@evie.sh]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n *\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport CryptoJS from \"crypto-js\";\n\n/**\n * Affine Cipher Encode operation.\n *\n * @author Matt C [matt@artemisbot.uk]\n * @param {string} input\n * @param {Object[]} args\n * @returns {string}\n */\nexport function affineEncode(input, args) {\n    const alphabet = \"abcdefghijklmnopqrstuvwxyz\",\n        a = args[0],\n        b = args[1];\n    let output = \"\";\n\n    if (!/^\\+?(0|[1-9]\\d*)$/.test(a) || !/^\\+?(0|[1-9]\\d*)$/.test(b)) {\n        throw new OperationError(\"The values of a and b can only be integers.\");\n    }\n\n    if (Utils.gcd(a, 26) !== 1) {\n        throw new OperationError(\"The value of `a` must be coprime to 26.\");\n    }\n\n    for (let i = 0; i < input.length; i++) {\n        if (alphabet.indexOf(input[i]) >= 0) {\n            // Uses the affine function ax+b % m = y (where m is length of the alphabet)\n            output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26];\n        } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {\n            // Same as above, accounting for uppercase\n            output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase();\n        } else {\n            // Non-alphabetic characters\n            output += input[i];\n        }\n    }\n    return output;\n}\n\n/**\n * Generates a polybius square for the given keyword\n *\n * @private\n * @author Matt C [matt@artemisbot.uk]\n * @param {string} keyword - Must be upper case\n * @returns {string}\n */\nexport function genPolybiusSquare (keyword) {\n    const alpha = \"ABCDEFGHIKLMNOPQRSTUVWXYZ\",\n        polArray = `${keyword}${alpha}`.split(\"\").unique(),\n        polybius = [];\n\n    for (let i = 0; i < 5; i++) {\n        polybius[i] = polArray.slice(i*5, i*5 + 5);\n    }\n\n    return polybius;\n}\n\n/**\n * A mapping of string formats to their classes in the CryptoJS library.\n *\n * @private\n * @constant\n */\nexport const format = {\n    \"Hex\":     CryptoJS.enc.Hex,\n    \"Base64\":  CryptoJS.enc.Base64,\n    \"UTF8\":    CryptoJS.enc.Utf8,\n    \"UTF16\":   CryptoJS.enc.Utf16,\n    \"UTF16LE\": CryptoJS.enc.Utf16LE,\n    \"UTF16BE\": CryptoJS.enc.Utf16BE,\n    \"Latin1\":  CryptoJS.enc.Latin1,\n};\n"
  },
  {
    "path": "src/core/lib/Code.mjs",
    "content": "/**\n * Code resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/**\n * This tries to rename variable names in a code snippet according to a function.\n *\n * @param {string} input\n * @param {function} replacer - This function will be fed the token which should be renamed.\n * @returns {string}\n */\nexport function replaceVariableNames(input, replacer) {\n    const tokenRegex = /\\\\\"|\"(?:\\\\\"|[^\"])*\"|(\\b[a-z0-9\\-_]+\\b)/ig;\n\n    return input.replace(tokenRegex, (...args) => {\n        const match = args[0],\n            quotes = args[1];\n\n        if (!quotes) {\n            return match;\n        } else {\n            return replacer(match);\n        }\n    });\n}\n"
  },
  {
    "path": "src/core/lib/Colossus.mjs",
    "content": "/**\n * Colossus - an emulation of the world's first electronic computer\n *\n * @author VirtualColossus [martin@virtualcolossus.co.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport {INIT_PATTERNS, ITA2_TABLE, ROTOR_SIZES} from \"../lib/Lorenz.mjs\";\n\n/**\n * Colossus simulator class.\n */\nexport class ColossusComputer {\n    /**\n     * Construct a Colossus.\n     *\n     * @param {string} ciphertext\n     * @param {string} pattern - named pattern of Chi, Mu and Psi wheels\n     * @param {Object} qbusin - which data inputs are being sent to q bus - each can be null, plain or delta\n     * @param {Object[]} qbusswitches - Q bus calculation switches, multiple rows\n     * @param {Object} control - control switches which specify stepping modes\n     * @param {Object} starts - rotor start positions\n     */\n    constructor(ciphertext, pattern, qbusin, qbusswitches, control, starts, settotal, limit) {\n        this.ITAlookup = ITA2_TABLE;\n        this.ReverseITAlookup = {};\n        for (const letter in this.ITAlookup) {\n            const code = this.ITAlookup[letter];\n            this.ReverseITAlookup[code] = letter;\n        }\n\n        this.initThyratrons(pattern);\n\n        this.ciphertext = ciphertext;\n        this.qbusin = qbusin;\n        this.qbusswitches = qbusswitches;\n        this.control = control;\n        this.starts = starts;\n        this.settotal = settotal;\n        this.limitations = limit;\n\n\n        this.allCounters = [0, 0, 0, 0, 0];\n\n        this.Zbits = [0, 0, 0, 0, 0]; // Z input is the cipher tape\n        this.ZbitsOneBack = [0, 0, 0, 0, 0]; // for delta\n        this.Qbits = [0, 0, 0, 0, 0]; // input generated for placing onto the Q-bus (the logic processor)\n\n        this.Xbits = [0, 0, 0, 0, 0]; // X is the Chi wheel bits\n        this.Xptr = [0, 0, 0, 0, 0]; // pointers to the current X bits (Chi wheels)\n        this.XbitsOneBack = [0, 0, 0, 0, 0]; // the X bits one back (for delta)\n\n        this.Sbits = [0, 0, 0, 0, 0]; // S is the Psi wheel bits\n        this.Sptr = [0, 0, 0, 0, 0]; // pointers to the current S bits (Psi wheels)\n        this.SbitsOneBack = [0, 0, 0, 0, 0]; // the S bits one back (for delta)\n\n        this.Mptr = [0, 0];\n\n        this.rotorPtrs = {};\n\n        this.totalmotor = 0;\n        this.P5Zbit = [0, 0];\n    }\n\n    /**\n     * Begin a run\n     *\n     * @returns {object}\n     */\n    run() {\n        const result = {\n            printout: \"\"\n        };\n\n        // loop until our start positions are back to the beginning\n        this.rotorPtrs = {X1: this.starts.X1, X2: this.starts.X2, X3: this.starts.X3, X4: this.starts.X4, X5: this.starts.X5, M61: this.starts.M61, M37: this.starts.M37, S1: this.starts.S1, S2: this.starts.S2, S3: this.starts.S3, S4: this.starts.S4, S5: this.starts.S5};\n        // this.rotorPtrs = this.starts;\n        let runcount = 1;\n\n        const fast = this.control.fast;\n        const slow = this.control.slow;\n\n        // Print Headers\n        result.printout += fast + \" \" + slow + \"\\n\";\n\n        do {\n            this.allCounters = [0, 0, 0, 0, 0];\n            this.ZbitsOneBack = [0, 0, 0, 0, 0];\n            this.XbitsOneBack = [0, 0, 0, 0, 0];\n\n            // Run full tape loop and process counters\n            this.runTape();\n\n            // Only print result if larger than set total\n            let fastRef = \"00\";\n            let slowRef = \"00\";\n            if (fast !== \"\") fastRef = this.rotorPtrs[fast].toString().padStart(2, \"0\");\n            if (slow !== \"\") slowRef = this.rotorPtrs[slow].toString().padStart(2, \"0\");\n            let printline = \"\";\n            for (let c=0;c<5;c++) {\n                if (this.allCounters[c] > this.settotal) {\n                    printline += String.fromCharCode(c+97) + this.allCounters[c]+\" \";\n                }\n            }\n            if (printline !== \"\") {\n                result.printout += fastRef + \" \" + slowRef + \" : \";\n                result.printout += printline;\n                result.printout += \"\\n\";\n            }\n\n            // Step fast rotor if required\n            if (fast !== \"\") {\n                this.rotorPtrs[fast]++;\n                if (this.rotorPtrs[fast] > ROTOR_SIZES[fast]) this.rotorPtrs[fast] = 1;\n            }\n\n            // Step slow rotor if fast rotor has returned to initial start position\n            if (slow !== \"\" && this.rotorPtrs[fast] === this.starts[fast]) {\n                this.rotorPtrs[slow]++;\n                if (this.rotorPtrs[slow] > ROTOR_SIZES[slow]) this.rotorPtrs[slow] = 1;\n            }\n\n            runcount++;\n        } while (JSON.stringify(this.rotorPtrs) !== JSON.stringify(this.starts));\n\n        result.counters = this.allCounters;\n        result.runcount = runcount;\n\n        return result;\n    }\n\n    /**\n     * Run tape loop\n     */\n    runTape() {\n        let charZin = \"\";\n\n        this.Xptr = [this.rotorPtrs.X1, this.rotorPtrs.X2, this.rotorPtrs.X3, this.rotorPtrs.X4, this.rotorPtrs.X5];\n        this.Mptr = [this.rotorPtrs.M37, this.rotorPtrs.M61];\n        this.Sptr = [this.rotorPtrs.S1, this.rotorPtrs.S2, this.rotorPtrs.S3, this.rotorPtrs.S4, this.rotorPtrs.S5];\n\n        // Run full loop of all character on the input tape (Z)\n        for (let i=0; i<this.ciphertext.length; i++) {\n            charZin = this.ciphertext.charAt(i);\n\n            // Firstly, we check what inputs are specified on the Q-bus input switches\n            this.getQbusInputs(charZin);\n\n            /*\n             * Pattern conditions on individual impulses. Matching patterns of bits on the Q bus.\n             * This is the top section on Colussus K rack - the Q bus programming switches\n             */\n            const tmpcnt = this.runQbusProcessingConditional();\n\n            /*\n             * Addition of impulses.\n             * This is the bottom section of Colossus K rack.\n             */\n            this.runQbusProcessingAddition(tmpcnt);\n\n            // Store Z bit impulse 5 two back required for P5 limitation\n            this.P5Zbit[1] = this.P5Zbit[0];\n            this.P5Zbit[0] = this.ITAlookup[charZin].split(\"\")[4];\n\n            // Step rotors\n            this.stepThyratrons();\n\n        }\n    }\n\n    /**\n     * Step thyratron rings to simulate movement of Lorenz rotors\n     * Chi rotors all step one per character\n     * Motor M61 rotor steps one per character, M37 steps dependant on M61 setting\n     * Psi rotors only step dependant on M37 setting + limitation\n     */\n    stepThyratrons() {\n        let X2bPtr = this.Xptr[1]-1;\n        if (X2bPtr===0) X2bPtr = ROTOR_SIZES.X2;\n        let S1bPtr = this.Sptr[0]-1;\n        if (S1bPtr===0) S1bPtr = ROTOR_SIZES.S1;\n\n        // Get Chi rotor 5 two back to calculate plaintext (Z+Chi+Psi=Plain)\n        let X5bPtr=this.Xptr[4]-1;\n        if (X5bPtr===0) X5bPtr=ROTOR_SIZES.X5;\n        X5bPtr=X5bPtr-1;\n        if (X5bPtr===0) X5bPtr=ROTOR_SIZES.X5;\n        // Get Psi rotor 5 two back to calculate plaintext (Z+Chi+Psi=Plain)\n        let S5bPtr=this.Sptr[4]-1;\n        if (S5bPtr===0) S5bPtr=ROTOR_SIZES.S5;\n        S5bPtr=S5bPtr-1;\n        if (S5bPtr===0) S5bPtr=ROTOR_SIZES.S5;\n\n        const x2sw = this.limitations.X2;\n        const s1sw = this.limitations.S1;\n        const p5sw = this.limitations.P5;\n\n        // Limitation calculations\n        let lim=1;\n        if (x2sw) lim = this.rings.X[2][X2bPtr-1];\n        if (s1sw) lim = lim ^ this.rings.S[1][S1bPtr-1];\n\n        // P5\n        if (p5sw) {\n            let p5lim = this.P5Zbit[1];\n            p5lim = p5lim ^ this.rings.X[5][X5bPtr-1];\n            p5lim = p5lim ^ this.rings.S[5][S5bPtr-1];\n            lim = lim ^ p5lim;\n        }\n\n        const basicmotor = this.rings.M[2][this.Mptr[0]-1];\n        this.totalmotor = basicmotor;\n\n        if (x2sw || s1sw) {\n            if (basicmotor===0 && lim===1) {\n                this.totalmotor = 0;\n            } else {\n                this.totalmotor = 1;\n            }\n        }\n\n        // Step Chi rotors\n        for (let r=0; r<5; r++) {\n            this.Xptr[r]++;\n            if (this.Xptr[r] > ROTOR_SIZES[\"X\"+(r+1)]) this.Xptr[r] = 1;\n        }\n\n        if (this.totalmotor) {\n            // Step Psi rotors\n            for (let r=0; r<5; r++) {\n                this.Sptr[r]++;\n                if (this.Sptr[r] > ROTOR_SIZES[\"S\"+(r+1)]) this.Sptr[r] = 1;\n            }\n        }\n\n        // Move M37 rotor if M61 set\n        if (this.rings.M[1][this.Mptr[1]-1]===1) this.Mptr[0]++;\n        if (this.Mptr[0] > ROTOR_SIZES.M37) this.Mptr[0]=1;\n\n        // Always move M61 rotor\n        this.Mptr[1]++;\n        if (this.Mptr[1] > ROTOR_SIZES.M61) this.Mptr[1]=1;\n    }\n\n    /**\n     * Get Q bus inputs\n     */\n    getQbusInputs(charZin) {\n        // Zbits - the bits from the current character from the cipher tape.\n        this.Zbits = this.ITAlookup[charZin].split(\"\");\n        if (this.qbusin.Z === \"Z\") {\n            // direct Z\n            this.Qbits = this.Zbits;\n        } else if (this.qbusin.Z === \"ΔZ\") {\n            // delta Z, the Bitwise XOR of this character Zbits + last character Zbits\n            for (let b=0;b<5;b++) {\n                this.Qbits[b] = this.Zbits[b] ^ this.ZbitsOneBack[b];\n            }\n        }\n        this.ZbitsOneBack = this.Zbits.slice(); // copy value of object, not reference\n\n        // Xbits - the current Chi wheel bits\n        for (let b=0;b<5;b++) {\n            this.Xbits[b] = this.rings.X[b+1][this.Xptr[b]-1];\n        }\n        if (this.qbusin.Chi !== \"\") {\n            if (this.qbusin.Chi === \"Χ\") {\n                // direct X added to Qbits\n                for (let b=0;b<5;b++) {\n                    this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b];\n                }\n            } else if (this.qbusin.Chi === \"ΔΧ\") {\n                // delta X\n                for (let b=0;b<5;b++) {\n                    this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b];\n                    this.Qbits[b] = this.Qbits[b] ^ this.XbitsOneBack[b];\n                }\n            }\n        }\n        this.XbitsOneBack = this.Xbits.slice();\n\n        // Sbits - the current Psi wheel bits\n        for (let b=0;b<5;b++) {\n            this.Sbits[b] = this.rings.S[b+1][this.Sptr[b]-1];\n        }\n        if (this.qbusin.Psi !== \"\") {\n            if (this.qbusin.Psi === \"Ψ\") {\n                // direct S added to Qbits\n                for (let b=0;b<5;b++) {\n                    this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b];\n                }\n            } else if (this.qbusin.Psi === \"ΔΨ\") {\n                // delta S\n                for (let b=0;b<5;b++) {\n                    this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b];\n                    this.Qbits[b] = this.Qbits[b] ^ this.SbitsOneBack[b];\n                }\n            }\n        }\n        this.SbitsOneBack = this.Sbits.slice();\n    }\n\n    /**\n     * Conditional impulse Q bus section\n     */\n    runQbusProcessingConditional() {\n        const cnt = [-1, -1, -1, -1, -1];\n        const numrows = this.qbusswitches.condition.length;\n\n        for (let r=0;r<numrows;r++) {\n            const row = this.qbusswitches.condition[r];\n            if (row.Counter !== \"\") {\n                let result = true;\n                const cPnt = row.Counter-1;\n                const Qswitch = this.readBusSwitches(row.Qswitches);\n                // Match switches to bit pattern\n                for (let s=0;s<5;s++) {\n                    if (Qswitch[s] >= 0 && Qswitch[s] !== parseInt(this.Qbits[s], 10)) result = false;\n                }\n                // Check for NOT switch\n                if (row.Negate) result = !result;\n\n                // AND each row to get final result\n                if (cnt[cPnt] === -1) {\n                    cnt[cPnt] = result;\n                } else if (!result) {\n                    cnt[cPnt] = false;\n                }\n            }\n        }\n\n        // Negate the whole column, this allows A OR B by doing NOT(NOT A AND NOT B)\n        for (let c=0;c<5;c++) {\n            if (this.qbusswitches.condNegateAll && cnt[c] !== -1) cnt[c] = !cnt[c];\n        }\n\n        return cnt;\n    }\n\n    /**\n     * Addition of impulses Q bus section\n     */\n    runQbusProcessingAddition(cnt) {\n        const row = this.qbusswitches.addition[0];\n        const Qswitch = row.Qswitches.slice();\n\n        // To save making the arguments of this operation any larger, limiting addition counter to first one only\n        // Colossus could actually add into any of the five counters.\n        if (row.C1) {\n            let addition = 0;\n            for (let s=0;s<5;s++) {\n                // XOR addition\n                if (Qswitch[s]) {\n                    addition = addition ^ this.Qbits[s];\n                }\n            }\n            const equals = (row.Equals===\"\"?-1:(row.Equals===\".\"?0:1));\n            if (addition === equals) {\n                // AND with conditional rows to get final result\n                if (cnt[0] === -1) cnt[0] = true;\n            } else {\n                cnt[0] = false;\n            }\n        }\n\n        // Final check, check for addition section negate\n        // then, if any column set, from top to bottom of rack, add to counter.\n        for (let c=0;c<5;c++) {\n            if (this.qbusswitches.addNegateAll && cnt[c] !== -1) cnt[c] = !cnt[c];\n\n            if (this.qbusswitches.totalMotor === \"\" || (this.qbusswitches.totalMotor === \"x\" && this.totalmotor === 0) || (this.qbusswitches.totalMotor === \".\" && this.totalmotor === 1)) {\n                if (cnt[c] === true) this.allCounters[c]++;\n            }\n\n        }\n    }\n\n    /**\n     * Initialise thyratron rings\n     * These hold the pattern of 1s & 0s for each rotor on banks of thyraton GT1C valves which act as a one-bit store.\n     */\n    initThyratrons(pattern) {\n        this.rings = {\n            X: {\n                1: INIT_PATTERNS[pattern].X[1].slice().reverse(),\n                2: INIT_PATTERNS[pattern].X[2].slice().reverse(),\n                3: INIT_PATTERNS[pattern].X[3].slice().reverse(),\n                4: INIT_PATTERNS[pattern].X[4].slice().reverse(),\n                5: INIT_PATTERNS[pattern].X[5].slice().reverse()\n            },\n            M: {\n                1: INIT_PATTERNS[pattern].M[1].slice().reverse(),\n                2: INIT_PATTERNS[pattern].M[2].slice().reverse(),\n            },\n            S: {\n                1: INIT_PATTERNS[pattern].S[1].slice().reverse(),\n                2: INIT_PATTERNS[pattern].S[2].slice().reverse(),\n                3: INIT_PATTERNS[pattern].S[3].slice().reverse(),\n                4: INIT_PATTERNS[pattern].S[4].slice().reverse(),\n                5: INIT_PATTERNS[pattern].S[5].slice().reverse()\n            }\n        };\n    }\n\n    /**\n     * Read argument bus switches X & . and convert to 1 & 0\n     */\n    readBusSwitches(row) {\n        const output = [-1, -1, -1, -1, -1];\n        for (let c=0;c<5;c++) {\n            if (row[c]===\".\") output[c] = 0;\n            if (row[c]===\"x\") output[c] = 1;\n        }\n        return output;\n    }\n\n}\n"
  },
  {
    "path": "src/core/lib/ConvertCoordinates.mjs",
    "content": "/**\n * Co-ordinate conversion resources.\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport geohash from \"ngeohash\";\n/*\nCurrently unable to update to geodesy v2 as we cannot load .js modules into a .mjs file.\nWhen we do update, imports will look like this:\n\nimport LatLonEllipsoidal from \"geodesy/latlon-ellipsoidal.js\";\nimport Mgrs from \"geodesy/mgrs.js\";\nimport OsGridRef from \"geodesy/osgridref.js\";\nimport Utm from \"geodesy/utm.js\";\n*/\nimport geodesy from \"geodesy\";\nconst LatLonEllipsoidal = geodesy.LatLonEllipsoidal,\n    Mgrs = geodesy.Mgrs,\n    OsGridRef = geodesy.OsGridRef,\n    Utm = geodesy.Utm;\n\n/**\n * Co-ordinate formats\n */\nexport const FORMATS = [\n    \"Degrees Minutes Seconds\",\n    \"Degrees Decimal Minutes\",\n    \"Decimal Degrees\",\n    \"Geohash\",\n    \"Military Grid Reference System\",\n    \"Ordnance Survey National Grid\",\n    \"Universal Transverse Mercator\"\n];\n\n/**\n * Formats that should be passed to the conversion module as-is\n */\nconst NO_CHANGE = [\n    \"Geohash\",\n    \"Military Grid Reference System\",\n    \"Ordnance Survey National Grid\",\n    \"Universal Transverse Mercator\",\n];\n\n/**\n * Convert a given latitude and longitude into a different format.\n *\n * @param {string} input - Input string to be converted\n * @param {string} inFormat - Format of the input coordinates\n * @param {string} inDelim - The delimiter splitting the lat/long of the input\n * @param {string} outFormat - Format to convert to\n * @param {string} outDelim - The delimiter to separate the output with\n * @param {string} includeDir - Whether or not to include the compass direction in the output\n * @param {number} precision - Precision of the result\n * @returns {string} A formatted string of the converted co-ordinates\n */\nexport function convertCoordinates (input, inFormat, inDelim, outFormat, outDelim, includeDir, precision) {\n    let isPair = false,\n        split,\n        latlon,\n        convLat,\n        convLon,\n        conv,\n        hash,\n        utm,\n        mgrs,\n        osng,\n        splitLat,\n        splitLong,\n        lat,\n        lon;\n\n    // Can't have a precision less than 0!\n    if (precision < 0) {\n        precision = 0;\n    }\n\n    if (inDelim === \"Auto\") {\n        // Try to detect a delimiter in the input.\n        inDelim = findDelim(input);\n        if (inDelim === null) {\n            throw new OperationError(\"Unable to detect the input delimiter automatically.\");\n        }\n    } else if (!inDelim.includes(\"Direction\")) {\n        // Convert the delimiter argument value to the actual character\n        inDelim = realDelim(inDelim);\n    }\n\n    if (inFormat === \"Auto\") {\n        // Try to detect the format of the input data\n        inFormat = findFormat(input, inDelim);\n        if (inFormat === null) {\n            throw new OperationError(\"Unable to detect the input format automatically.\");\n        }\n    }\n\n    // Convert the output delimiter argument to the real character\n    outDelim = realDelim(outDelim);\n\n    if (!NO_CHANGE.includes(inFormat)) {\n        if (inDelim.includes(\"Direction\")) {\n            // Split on directions\n            split = input.split(/[NnEeSsWw]/g);\n            if (split[0] === \"\") {\n                // Remove first element if direction preceding\n                split = split.slice(1);\n            }\n        } else {\n            split = input.split(inDelim);\n        }\n        // Replace any co-ordinate symbols with spaces so we can split on them later\n        for (let i = 0; i < split.length; i++) {\n            split[i] = split[i].replace(/[°˝´'\"]/g, \" \");\n        }\n        if (split.length > 1) {\n            isPair = true;\n        }\n    } else {\n        // Remove any delimiters from the input\n        input = input.replace(inDelim, \"\");\n        isPair = true;\n    }\n\n    // Conversions from the input format into a geodesy latlon object\n    switch (inFormat) {\n        case \"Geohash\":\n            hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, \"\"));\n            latlon = new LatLonEllipsoidal(hash.latitude, hash.longitude);\n            break;\n        case \"Military Grid Reference System\":\n            utm = Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, \"\")).toUtm();\n            latlon = utm.toLatLonE();\n            break;\n        case \"Ordnance Survey National Grid\":\n            osng = OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, \"\"));\n            latlon = OsGridRef.osGridToLatLon(osng);\n            break;\n        case \"Universal Transverse Mercator\":\n            // Geodesy needs a space between the first 2 digits and the next letter\n            if (/^[\\d]{2}[A-Za-z]/.test(input)) {\n                input = input.slice(0, 2) + \" \" + input.slice(2);\n            }\n            utm = Utm.parse(input);\n            latlon = utm.toLatLonE();\n            break;\n        case \"Degrees Minutes Seconds\":\n            if (isPair) {\n                // Split up the lat/long into degrees / minutes / seconds values\n                splitLat = splitInput(split[0]);\n                splitLong = splitInput(split[1]);\n\n                if (splitLat.length >= 3 && splitLong.length >= 3) {\n                    lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10);\n                    lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10);\n                    latlon = new LatLonEllipsoidal(lat.degrees, lon.degrees);\n                } else {\n                    throw new OperationError(\"Invalid co-ordinate format for Degrees Minutes Seconds\");\n                }\n            } else {\n                // Not a pair, so only try to convert one set of co-ordinates\n                splitLat = splitInput(split[0]);\n                if (splitLat.length >= 3) {\n                    lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]);\n                    latlon = new LatLonEllipsoidal(lat.degrees, lat.degrees);\n                } else {\n                    throw new OperationError(\"Invalid co-ordinate format for Degrees Minutes Seconds\");\n                }\n            }\n            break;\n        case \"Degrees Decimal Minutes\":\n            if (isPair) {\n                splitLat = splitInput(split[0]);\n                splitLong = splitInput(split[1]);\n                if (splitLat.length !== 2 || splitLong.length !== 2) {\n                    throw new OperationError(\"Invalid co-ordinate format for Degrees Decimal Minutes.\");\n                }\n                // Convert to decimal degrees, and then convert to a geodesy object\n                lat = convDDMToDD(splitLat[0], splitLat[1], 10);\n                lon = convDDMToDD(splitLong[0], splitLong[1], 10);\n                latlon = new LatLonEllipsoidal(lat.degrees, lon.degrees);\n            } else {\n                // Not a pair, so only try to convert one set of co-ordinates\n                splitLat = splitInput(input);\n                if (splitLat.length !== 2) {\n                    throw new OperationError(\"Invalid co-ordinate format for Degrees Decimal Minutes.\");\n                }\n                lat = convDDMToDD(splitLat[0], splitLat[1], 10);\n                latlon = new LatLonEllipsoidal(lat.degrees, lat.degrees);\n            }\n            break;\n        case \"Decimal Degrees\":\n            if (isPair) {\n                splitLat =  splitInput(split[0]);\n                splitLong = splitInput(split[1]);\n                if (splitLat.length !== 1 || splitLong.length !== 1) {\n                    throw new OperationError(\"Invalid co-ordinate format for Decimal Degrees.\");\n                }\n                latlon = new LatLonEllipsoidal(splitLat[0], splitLong[0]);\n            } else {\n                // Not a pair, so only try to convert one set of co-ordinates\n                splitLat = splitInput(split[0]);\n                if (splitLat.length !== 1) {\n                    throw new OperationError(\"Invalid co-ordinate format for Decimal Degrees.\");\n                }\n                latlon = new LatLonEllipsoidal(splitLat[0], splitLat[0]);\n            }\n            break;\n        default:\n            throw new OperationError(`Unknown input format '${inFormat}'`);\n    }\n\n    // Everything is now a geodesy latlon object\n    // These store the latitude and longitude as decimal\n    if (inFormat.includes(\"Degrees\")) {\n        // If the input string contains directions, we need to check if they're S or W.\n        // If either of the directions are, we should make the decimal value negative\n        const dirs = input.toUpperCase().match(/[NESW]/g);\n        if (dirs && dirs.length >= 1) {\n            // Make positive lat/lon values with S/W directions into negative values\n            if (dirs[0] === \"S\" || dirs[0] === \"W\" && latlon.lat > 0) {\n                latlon.lat = -latlon.lat;\n            }\n            if (dirs.length >= 2) {\n                if (dirs[1] === \"S\" || dirs[1] === \"W\" && latlon.lon > 0) {\n                    latlon.lon = -latlon.lon;\n                }\n            }\n        }\n    }\n\n    // Try to find the compass directions of the lat and long\n    const [latDir, longDir] = findDirs(latlon.lat + \",\" + latlon.lon, \",\");\n\n    // Output conversions for each output format\n    switch (outFormat) {\n        case \"Decimal Degrees\":\n            // We could use the built in latlon.toString(),\n            // but this makes adjusting the output harder\n            lat = convDDToDD(latlon.lat, precision);\n            lon = convDDToDD(latlon.lon, precision);\n            convLat = lat.string;\n            convLon = lon.string;\n            break;\n        case \"Degrees Decimal Minutes\":\n            lat = convDDToDDM(latlon.lat, precision);\n            lon = convDDToDDM(latlon.lon, precision);\n            convLat = lat.string;\n            convLon = lon.string;\n            break;\n        case \"Degrees Minutes Seconds\":\n            lat = convDDToDMS(latlon.lat, precision);\n            lon = convDDToDMS(latlon.lon, precision);\n            convLat = lat.string;\n            convLon = lon.string;\n            break;\n        case \"Geohash\":\n            convLat = geohash.encode(latlon.lat, latlon.lon, precision);\n            break;\n        case \"Military Grid Reference System\":\n            utm = latlon.toUtm();\n            mgrs = utm.toMgrs();\n            // MGRS wants a precision that's an even number between 2 and 10\n            if (precision % 2 !== 0) {\n                precision = precision + 1;\n            }\n            if (precision > 10) {\n                precision = 10;\n            }\n            convLat = mgrs.toString(precision);\n            break;\n        case \"Ordnance Survey National Grid\":\n            osng = OsGridRef.latLonToOsGrid(latlon);\n            if (osng.toString() === \"\") {\n                throw new OperationError(\"Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?\");\n            }\n            // OSNG wants a precision that's an even number between 2 and 10\n            if (precision % 2 !== 0) {\n                precision = precision + 1;\n            }\n            if (precision > 10) {\n                precision = 10;\n            }\n            convLat = osng.toString(precision);\n            break;\n        case \"Universal Transverse Mercator\":\n            utm = latlon.toUtm();\n            convLat = utm.toString(precision);\n            break;\n    }\n\n    if (convLat === undefined) {\n        throw new OperationError(\"Error converting co-ordinates.\");\n    }\n\n    if (outFormat.includes(\"Degrees\")) {\n        // Format DD/DDM/DMS for output\n        // If we're outputting a compass direction, remove the negative sign\n        if (latDir === \"S\" && includeDir !== \"None\") {\n            convLat = convLat.replace(\"-\", \"\");\n        }\n        if (longDir === \"W\" && includeDir !== \"None\") {\n            convLon = convLon.replace(\"-\", \"\");\n        }\n\n        let outConv = \"\";\n        if (includeDir === \"Before\") {\n            outConv += latDir + \" \";\n        }\n\n        outConv += convLat;\n        if (includeDir === \"After\") {\n            outConv += \" \" + latDir;\n        }\n        outConv += outDelim;\n        if (isPair) {\n            if (includeDir === \"Before\") {\n                outConv += longDir + \" \";\n            }\n            outConv += convLon;\n            if (includeDir === \"After\") {\n                outConv += \" \" + longDir;\n            }\n            outConv += outDelim;\n        }\n        conv = outConv;\n    } else {\n        conv = convLat + outDelim;\n    }\n\n    return conv;\n}\n\n/**\n * Split up the input using a space or degrees signs, and sanitise the result\n *\n * @param {string} input - The input data to be split\n * @returns {number[]} An array of the different items in the string, stored as floats\n */\nfunction splitInput (input) {\n    const split = [];\n\n    input.split(/\\s+/).forEach(item => {\n        // Remove any character that isn't a digit, decimal point or negative sign\n        item = item.replace(/[^0-9.-]/g, \"\");\n        if (item.length > 0) {\n            // Turn the item into a float\n            split.push(parseFloat(item));\n        }\n    });\n    return split;\n}\n\n/**\n * Convert Degrees Minutes Seconds to Decimal Degrees\n *\n * @param {number} degrees - The degrees of the input co-ordinates\n * @param {number} minutes - The minutes of the input co-ordinates\n * @param {number} seconds - The seconds of the input co-ordinates\n * @param {number} precision - The precision the result should be rounded to\n * @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)\n */\nfunction convDMSToDD (degrees, minutes, seconds, precision) {\n    const absDegrees = Math.abs(degrees);\n    let conv = absDegrees + (minutes / 60) + (seconds / 3600);\n    let outString = round(conv, precision) + \"°\";\n    if (isNegativeZero(degrees) || degrees < 0) {\n        conv = -conv;\n        outString = \"-\" + outString;\n    }\n    return {\n        \"degrees\": conv,\n        \"string\": outString\n    };\n}\n\n/**\n * Convert Decimal Degrees Minutes to Decimal Degrees\n *\n * @param {number} degrees - The input degrees to be converted\n * @param {number} minutes - The input minutes to be converted\n * @param {number} precision - The precision which the result should be rounded to\n * @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)\n */\nfunction convDDMToDD (degrees, minutes, precision) {\n    const absDegrees = Math.abs(degrees);\n    let conv = absDegrees + minutes / 60;\n    let outString = round(conv, precision) + \"°\";\n    if (isNegativeZero(degrees) || degrees < 0) {\n        conv = -conv;\n        outString = \"-\" + outString;\n    }\n    return {\n        \"degrees\": conv,\n        \"string\": outString\n    };\n}\n\n/**\n * Convert Decimal Degrees to Decimal Degrees\n *\n * Doesn't affect the input, just puts it into an object\n * @param {number} degrees - The input degrees to be converted\n * @param {number} precision - The precision which the result should be rounded to\n * @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)\n */\nfunction convDDToDD (degrees, precision) {\n    return {\n        \"degrees\": degrees,\n        \"string\": round(degrees, precision) + \"°\"\n    };\n}\n\n/**\n * Convert Decimal Degrees to Degrees Minutes Seconds\n *\n * @param {number} decDegrees - The input data to be converted\n * @param {number} precision - The precision which the result should be rounded to\n * @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)\n */\nfunction convDDToDMS (decDegrees, precision) {\n    const absDegrees = Math.abs(decDegrees);\n    let degrees = Math.floor(absDegrees);\n    const minutes = Math.floor(60 * (absDegrees - degrees)),\n        seconds = round(3600 * (absDegrees - degrees) - 60 * minutes, precision);\n    let outString = degrees + \"° \" + minutes + \"' \" + seconds + \"\\\"\";\n    if (isNegativeZero(decDegrees) || decDegrees < 0) {\n        degrees = -degrees;\n        outString = \"-\" + outString;\n    }\n    return {\n        \"degrees\": degrees,\n        \"minutes\": minutes,\n        \"seconds\": seconds,\n        \"string\": outString\n    };\n}\n\n/**\n * Convert Decimal Degrees to Degrees Decimal Minutes\n *\n * @param {number} decDegrees - The input degrees to be converted\n * @param {number} precision - The precision the input data should be rounded to\n * @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)\n */\nfunction convDDToDDM (decDegrees, precision) {\n    const absDegrees = Math.abs(decDegrees);\n    let degrees = Math.floor(absDegrees);\n    const minutes = absDegrees - degrees,\n        decMinutes = round(minutes * 60, precision);\n    let outString = degrees + \"° \" + decMinutes + \"'\";\n    if (decDegrees < 0 || isNegativeZero(decDegrees)) {\n        degrees = -degrees;\n        outString = \"-\" + outString;\n    }\n\n    return {\n        \"degrees\": degrees,\n        \"minutes\": decMinutes,\n        \"string\": outString,\n    };\n}\n\n/**\n * Finds and returns the compass directions in an input string\n *\n * @param {string} input - The input co-ordinates containing the direction\n * @param {string} delim - The delimiter separating latitide and longitude\n * @returns {string[]} String array containing the latitude and longitude directions\n */\nexport function findDirs(input, delim) {\n    const upperInput = input.toUpperCase();\n    const dirExp = new RegExp(/[NESW]/g);\n\n    const dirs = upperInput.match(dirExp);\n\n    if (dirs) {\n        // If there's actually compass directions\n        // in the input, use these to work out the direction\n        if (dirs.length <= 2 && dirs.length >= 1) {\n            return dirs.length === 2 ? [dirs[0], dirs[1]] : [dirs[0], \"\"];\n        }\n    }\n\n    // Nothing was returned, so guess the directions\n    let lat = upperInput,\n        long,\n        latDir = \"\",\n        longDir = \"\";\n    if (!delim.includes(\"Direction\")) {\n        if (upperInput.includes(delim)) {\n            const split = upperInput.split(delim);\n            if (split.length >= 1) {\n                if (split[0] !== \"\") {\n                    lat = split[0];\n                }\n                if (split.length >= 2 && split[1] !== \"\") {\n                    long = split[1];\n                }\n            }\n        }\n    } else {\n        const split = upperInput.split(dirExp);\n        if (split.length > 1) {\n            lat = split[0] === \"\" ? split[1] : split[0];\n            if (split.length > 2 && split[2] !== \"\") {\n                long = split[2];\n            }\n        }\n    }\n\n    if (lat) {\n        lat = parseFloat(lat);\n        latDir = lat < 0 ? \"S\" : \"N\";\n    }\n\n    if (long) {\n        long = parseFloat(long);\n        longDir = long < 0 ? \"W\" : \"E\";\n    }\n\n    return [latDir, longDir];\n}\n\n/**\n * Detects the co-ordinate format of the input data\n *\n * @param {string} input - The input data whose format we need to detect\n * @param {string} delim - The delimiter separating the data in input\n * @returns {string} The input format\n */\nexport function findFormat (input, delim) {\n    let testData;\n    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]+/),\n        osngPattern = new RegExp(/^[A-HJ-Z]{2}\\s+[0-9\\s]+$/),\n        geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/),\n        utmPattern = new RegExp(/^[0-9]{2}\\s?[C-HJ-NP-X]\\s[0-9.]+\\s?[0-9.]+$/),\n        degPattern = new RegExp(/[°'\"]/g);\n\n    input = input.trim();\n\n    if (delim !== null && delim.includes(\"Direction\")) {\n        const split = input.split(/[NnEeSsWw]/);\n        if (split.length > 1) {\n            testData = split[0] === \"\" ? split[1] : split[0];\n        }\n    } else if (delim !== null && delim !== \"\") {\n        if (input.includes(delim)) {\n            const split = input.split(delim);\n            if (split.length > 1) {\n                testData = split[0] === \"\" ? split[1] : split[0];\n            }\n        } else {\n            testData = input;\n        }\n    }\n\n    // Test non-degrees formats\n    if (!degPattern.test(input)) {\n        const filteredInput = input.toUpperCase().replace(delim, \"\");\n\n        if (utmPattern.test(filteredInput)) {\n            return \"Universal Transverse Mercator\";\n        }\n        if (mgrsPattern.test(filteredInput)) {\n            return \"Military Grid Reference System\";\n        }\n        if (osngPattern.test(filteredInput)) {\n            return \"Ordnance Survey National Grid\";\n        }\n        if (geohashPattern.test(filteredInput)) {\n            return \"Geohash\";\n        }\n    }\n\n    // Test DMS/DDM/DD formats\n    if (testData !== undefined) {\n        const split = splitInput(testData);\n        switch (split.length) {\n            case 3:\n                return \"Degrees Minutes Seconds\";\n            case 2:\n                return \"Degrees Decimal Minutes\";\n            case 1:\n                return \"Decimal Degrees\";\n        }\n    }\n    return null;\n}\n\n/**\n * Automatically find the delimeter type from the given input\n *\n * @param {string} input\n * @returns {string} Delimiter type\n */\nexport function findDelim (input) {\n    input = input.trim();\n    const delims = [\",\", \";\", \":\"];\n    const testDir = input.match(/[NnEeSsWw]/g);\n    if (testDir !== null && testDir.length > 0 && testDir.length < 3) {\n        // Possibly contains a direction\n        const splitInput = input.split(/[NnEeSsWw]/);\n        if (splitInput.length <= 3 && splitInput.length > 0) {\n            // If there's 3 splits (one should be empty), then assume we have directions\n            if (splitInput[0] === \"\") {\n                return \"Direction Preceding\";\n            } else if (splitInput[splitInput.length - 1] === \"\") {\n                return \"Direction Following\";\n            }\n        }\n    }\n\n    // Loop through the standard delimiters, and try to find them in the input\n    for (let i = 0; i < delims.length; i++) {\n        const delim = delims[i];\n        if (input.includes(delim)) {\n            const splitInput = input.split(delim);\n            if (splitInput.length <= 3 && splitInput.length > 0) {\n                // Don't want to try and convert more than 2 co-ordinates\n                return delim;\n            }\n        }\n    }\n    return null;\n}\n\n/**\n * Gets the real string for a delimiter name.\n *\n * @param {string} delim The delimiter to be matched\n * @returns {string}\n */\nexport function realDelim (delim) {\n    return {\n        \"Auto\":         \"Auto\",\n        \"Space\":        \" \",\n        \"\\\\n\":          \"\\n\",\n        \"Comma\":        \",\",\n        \"Semi-colon\":   \";\",\n        \"Colon\":        \":\"\n    }[delim];\n}\n\n/**\n * Returns true if a zero is negative\n *\n * @param {number} zero\n * @returns {boolean}\n */\nfunction isNegativeZero(zero) {\n    return zero === 0 && (1/zero < 0);\n}\n\n/**\n * Rounds a number to a specified number of decimal places\n *\n * @param {number} input - The number to be rounded\n * @param {precision} precision - The number of decimal places the number should be rounded to\n * @returns {number}\n */\nfunction round(input, precision) {\n    precision = Math.pow(10, precision);\n    return Math.round(input * precision) / precision;\n}\n"
  },
  {
    "path": "src/core/lib/Crypt.mjs",
    "content": "/**\n * Crypt resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nexport const cryptNotice = \"WARNING: Cryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness. We advise you not to use keys generated from CyberChef in operational contexts.\";\n"
  },
  {
    "path": "src/core/lib/DateTime.mjs",
    "content": "/**\n * DateTime resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n/**\n * DateTime units.\n */\nexport const UNITS = [\"Seconds (s)\", \"Milliseconds (ms)\", \"Microseconds (μs)\", \"Nanoseconds (ns)\"];\n\n/**\n * DateTime formats.\n */\nexport const DATETIME_FORMATS = [\n    {\n        name: \"Standard date and time\",\n        value: \"DD/MM/YYYY HH:mm:ss\"\n    },\n    {\n        name: \"American-style date and time\",\n        value: \"MM/DD/YYYY HH:mm:ss\"\n    },\n    {\n        name: \"International date and time\",\n        value: \"YYYY-MM-DD HH:mm:ss\"\n    },\n    {\n        name: \"Verbose date and time\",\n        value: \"dddd Do MMMM YYYY HH:mm:ss Z z\"\n    },\n    {\n        name: \"UNIX timestamp (seconds)\",\n        value: \"X\"\n    },\n    {\n        name: \"UNIX timestamp offset (milliseconds)\",\n        value: \"x\"\n    },\n    {\n        name: \"Automatic\",\n        value: \"\"\n    },\n];\n\n/**\n * MomentJS DateTime formatting examples.\n */\nexport const FORMAT_EXAMPLES = `Format string tokens:\n<table class=\"table table-striped table-hover table-sm table-bordered\" style=\"font-family: sans-serif\">\n  <thead class=\"thead-dark\">\n    <tr>\n      <th>Category</th>\n      <th>Token</th>\n      <th>Output</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><b>Month</b></td>\n      <td>M</td>\n      <td>1 2 ... 11 12</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>Mo</td>\n      <td>1st 2nd ... 11th 12th</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>MM</td>\n      <td>01 02 ... 11 12</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>MMM</td>\n      <td>Jan Feb ... Nov Dec</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>MMMM</td>\n      <td>January February ... November December</td>\n    </tr>\n    <tr>\n      <td><b>Quarter</b></td>\n      <td>Q</td>\n      <td>1 2 3 4</td>\n    </tr>\n    <tr>\n      <td><b>Day of Month</b></td>\n      <td>D</td>\n      <td>1 2 ... 30 31</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>Do</td>\n      <td>1st 2nd ... 30th 31st</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>DD</td>\n      <td>01 02 ... 30 31</td>\n    </tr>\n    <tr>\n      <td><b>Day of Year</b></td>\n      <td>DDD</td>\n      <td>1 2 ... 364 365</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>DDDo</td>\n      <td>1st 2nd ... 364th 365th</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>DDDD</td>\n      <td>001 002 ... 364 365</td>\n    </tr>\n    <tr>\n      <td><b>Day of Week</b></td>\n      <td>d</td>\n      <td>0 1 ... 5 6</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>do</td>\n      <td>0th 1st ... 5th 6th</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>dd</td>\n      <td>Su Mo ... Fr Sa</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>ddd</td>\n      <td>Sun Mon ... Fri Sat</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>dddd</td>\n      <td>Sunday Monday ... Friday Saturday</td>\n    </tr>\n    <tr>\n      <td><b>Day of Week (Locale)</b></td>\n      <td>e</td>\n      <td>0 1 ... 5 6</td>\n    </tr>\n    <tr>\n      <td><b>Day of Week (ISO)</b></td>\n      <td>E</td>\n      <td>1 2 ... 6 7</td>\n    </tr>\n    <tr>\n      <td><b>Week of Year</b></td>\n      <td>w</td>\n      <td>1 2 ... 52 53</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>wo</td>\n      <td>1st 2nd ... 52nd 53rd</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>ww</td>\n      <td>01 02 ... 52 53</td>\n    </tr>\n    <tr>\n      <td><b>Week of Year (ISO)</b></td>\n      <td>W</td>\n      <td>1 2 ... 52 53</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>Wo</td>\n      <td>1st 2nd ... 52nd 53rd</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>WW</td>\n      <td>01 02 ... 52 53</td>\n    </tr>\n    <tr>\n      <td><b>Year</b></td>\n      <td>YY</td>\n      <td>70 71 ... 29 30</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>YYYY</td>\n      <td>1970 1971 ... 2029 2030</td>\n    </tr>\n    <tr>\n      <td><b>Week Year</b></td>\n      <td>gg</td>\n      <td>70 71 ... 29 30</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>gggg</td>\n      <td>1970 1971 ... 2029 2030</td>\n    </tr>\n    <tr>\n      <td><b>Week Year (ISO)</b></td>\n      <td>GG</td>\n      <td>70 71 ... 29 30</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>GGGG</td>\n      <td>1970 1971 ... 2029 2030</td>\n    </tr>\n    <tr>\n      <td><b>AM/PM</b></td>\n      <td>A</td>\n      <td>AM PM</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>a</td>\n      <td>am pm</td>\n    </tr>\n    <tr>\n      <td><b>Hour</b></td>\n      <td>H</td>\n      <td>0 1 ... 22 23</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>HH</td>\n      <td>00 01 ... 22 23</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>h</td>\n      <td>1 2 ... 11 12</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>hh</td>\n      <td>01 02 ... 11 12</td>\n    </tr>\n    <tr>\n      <td><b>Minute</b></td>\n      <td>m</td>\n      <td>0 1 ... 58 59</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>mm</td>\n      <td>00 01 ... 58 59</td>\n    </tr>\n    <tr>\n      <td><b>Second</b></td>\n      <td>s</td>\n      <td>0 1 ... 58 59</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>ss</td>\n      <td>00 01 ... 58 59</td>\n    </tr>\n    <tr>\n      <td><b>Fractional Second</b></td>\n      <td>S</td>\n      <td>0 1 ... 8 9</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>SS</td>\n      <td>00 01 ... 98 99</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>SSS</td>\n      <td>000 001 ... 998 999</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>SSSS ... SSSSSSSSS</td>\n      <td>000[0..] 001[0..] ... 998[0..] 999[0..]</td>\n    </tr>\n    <tr>\n      <td><b>Timezone</b></td>\n      <td>z or zz</td>\n      <td>EST CST ... MST PST</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>Z</td>\n      <td>-07:00 -06:00 ... +06:00 +07:00</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td>ZZ</td>\n      <td>-0700 -0600 ... +0600 +0700</td>\n    </tr>\n    <tr>\n      <td><b>Unix Timestamp</b></td>\n      <td>X</td>\n      <td>1360013296</td>\n    </tr>\n    <tr>\n      <td><b>Unix Millisecond Timestamp</b></td>\n      <td>x</td>\n      <td>1360013296123</td>\n    </tr>\n  </tbody>\n</table>`;\n\n"
  },
  {
    "path": "src/core/lib/Decimal.mjs",
    "content": "/**\n * Decimal functions.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\n\n\n/**\n * Convert a string of decimal values into a byte array.\n *\n * @param {string} data\n * @param {string} [delim]\n * @returns {byteArray}\n *\n * @example\n * // returns [10,20,30]\n * fromDecimal(\"10 20 30\");\n *\n * // returns [10,20,30]\n * fromDecimal(\"10:20:30\", \"Colon\");\n */\nexport function fromDecimal(data, delim=\"Auto\") {\n    delim = Utils.charRep(delim);\n    const output = [];\n    let byteStr = data.split(delim);\n    if (byteStr[byteStr.length-1] === \"\")\n        byteStr = byteStr.slice(0, byteStr.length-1);\n\n    for (let i = 0; i < byteStr.length; i++) {\n        output[i] = parseInt(byteStr[i], 10);\n    }\n    return output;\n}\n"
  },
  {
    "path": "src/core/lib/Delim.mjs",
    "content": "/**\n * Various delimiters\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/**\n * Generic sequence delimiters.\n */\nexport const DELIM_OPTIONS = [\"Space\", \"Comma\", \"Semi-colon\", \"Colon\", \"Line feed\", \"CRLF\"];\n\n/**\n * Binary sequence delimiters.\n */\nexport const BIN_DELIM_OPTIONS = [\"Space\", \"Comma\", \"Semi-colon\", \"Colon\", \"Line feed\", \"CRLF\", \"None\"];\n\n/**\n * Letter sequence delimiters.\n */\nexport const LETTER_DELIM_OPTIONS = [\"Space\", \"Line feed\", \"CRLF\", \"Forward slash\", \"Backslash\", \"Comma\", \"Semi-colon\", \"Colon\"];\n\n/**\n * Word sequence delimiters.\n */\nexport const WORD_DELIM_OPTIONS = [\"Line feed\", \"CRLF\", \"Forward slash\", \"Backslash\", \"Comma\", \"Semi-colon\", \"Colon\"];\n\n/**\n * Input sequence delimiters.\n */\nexport const INPUT_DELIM_OPTIONS = [\"Line feed\", \"CRLF\", \"Space\", \"Comma\", \"Semi-colon\", \"Colon\", \"Nothing (separate chars)\"];\n\n/**\n * Arithmetic sequence delimiters\n */\nexport const ARITHMETIC_DELIM_OPTIONS = [\"Line feed\", \"Space\", \"Comma\", \"Semi-colon\", \"Colon\", \"CRLF\"];\n\n/**\n * Hash delimiters\n */\nexport const HASH_DELIM_OPTIONS = [\"Line feed\", \"CRLF\", \"Space\", \"Comma\"];\n\n/**\n * IP delimiters\n */\nexport const IP_DELIM_OPTIONS = [\"Line feed\", \"CRLF\", \"Space\", \"Comma\", \"Semi-colon\"];\n\n/**\n * Split delimiters.\n */\nexport const SPLIT_DELIM_OPTIONS = [\n    {name: \"Comma\", value: \",\"},\n    {name: \"Space\", value: \" \"},\n    {name: \"Line feed\", value: \"\\\\n\"},\n    {name: \"CRLF\", value: \"\\\\r\\\\n\"},\n    {name: \"Semi-colon\", value: \";\"},\n    {name: \"Colon\", value: \":\"},\n    {name: \"Nothing (separate chars)\", value: \"\"}\n];\n\n/**\n * Join delimiters.\n */\nexport const JOIN_DELIM_OPTIONS = [\n    {name: \"Line feed\", value: \"\\\\n\"},\n    {name: \"CRLF\", value: \"\\\\r\\\\n\"},\n    {name: \"Space\", value: \" \"},\n    {name: \"Comma\", value: \",\"},\n    {name: \"Semi-colon\", value: \";\"},\n    {name: \"Colon\", value: \":\"},\n    {name: \"Nothing (join chars)\", value: \"\"}\n];\n\n/**\n * RGBA list delimiters.\n */\nexport const RGBA_DELIM_OPTIONS = [\n    {name: \"Comma\", value: \",\"},\n    {name: \"Space\", value: \" \"},\n    {name: \"CRLF\", value: \"\\\\r\\\\n\"},\n    {name: \"Line Feed\", value: \"\\n\"}\n];\n"
  },
  {
    "path": "src/core/lib/Enigma.mjs",
    "content": "/**\n * Emulation of the Enigma machine.\n *\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Provided default Enigma rotor set.\n * These are specified as a list of mappings from the letters A through Z in order, optionally\n * followed by < and a list of letters at which the rotor steps.\n */\nexport const ROTORS = [\n    {name: \"I\", value: \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\"},\n    {name: \"II\", value: \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\"},\n    {name: \"III\", value: \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\"},\n    {name: \"IV\", value: \"ESOVPZJAYQUIRHXLNFTGKDCMWB<K\"},\n    {name: \"V\", value: \"VZBRGITYUPSDNHLXAWMJQOFECK<A\"},\n    {name: \"VI\", value: \"JPGVOUMFYQBENHZRDKASXLICTW<AN\"},\n    {name: \"VII\", value: \"NZJHGRCXMYSWBOUFAIVLPEKQDT<AN\"},\n    {name: \"VIII\", value: \"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN\"},\n];\n\nexport const ROTORS_FOURTH = [\n    {name: \"Beta\", value: \"LEYJVCNIXWPBQMDRTAKZGFUHOS\"},\n    {name: \"Gamma\", value: \"FSOKANUERHMBTIYCWLQPZXVGJD\"},\n];\n\n/**\n * Provided default Enigma reflector set.\n * These are specified as 13 space-separated transposed pairs covering every letter.\n */\nexport const REFLECTORS = [\n    {name: \"B\", value: \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\"},\n    {name: \"C\", value: \"AF BV CP DJ EI GO HY KR LZ MX NW TQ SU\"},\n    {name: \"B Thin\", value: \"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV\"},\n    {name: \"C Thin\", value: \"AR BD CO EJ FN GT HK IV LM PW QZ SX UY\"},\n];\n\nexport const LETTERS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".split(\"\");\n\n/**\n * Map a letter to a number in 0..25.\n *\n * @param {char} c\n * @param {boolean} permissive - Case insensitive; don't throw errors on other chars.\n * @returns {number}\n */\nexport function a2i(c, permissive=false) {\n    const i = Utils.ord(c);\n    if (i >= 65 && i <= 90) {\n        return i - 65;\n    }\n    if (permissive) {\n        // Allow case insensitivity\n        if (i >= 97 && i <= 122) {\n            return i - 97;\n        }\n        return -1;\n    }\n    throw new OperationError(\"a2i called on non-uppercase ASCII character\");\n}\n\n/**\n * Map a number in 0..25 to a letter.\n *\n * @param {number} i\n * @returns {char}\n */\nexport function i2a(i) {\n    if (i >= 0 && i < 26) {\n        return Utils.chr(i+65);\n    }\n    throw new OperationError(\"i2a called on value outside 0..25\");\n}\n\n/**\n * A rotor in the Enigma machine.\n */\nexport class Rotor {\n    /**\n     * Rotor constructor.\n     *\n     * @param {string} wiring - A 26 character string of the wiring order.\n     * @param {string} steps - A 0..26 character string of stepping points.\n     * @param {char} ringSetting - The ring setting.\n     * @param {char} initialPosition - The initial position of the rotor.\n     */\n    constructor(wiring, steps, ringSetting, initialPosition) {\n        if (!/^[A-Z]{26}$/.test(wiring)) {\n            throw new OperationError(\"Rotor wiring must be 26 unique uppercase letters\");\n        }\n        if (!/^[A-Z]{0,26}$/.test(steps)) {\n            throw new OperationError(\"Rotor steps must be 0-26 unique uppercase letters\");\n        }\n        if (!/^[A-Z]$/.test(ringSetting)) {\n            throw new OperationError(\"Rotor ring setting must be exactly one uppercase letter\");\n        }\n        if (!/^[A-Z]$/.test(initialPosition)) {\n            throw new OperationError(\"Rotor initial position must be exactly one uppercase letter\");\n        }\n        this.map = new Array(26);\n        this.revMap = new Array(26);\n        const uniq = {};\n        for (let i=0; i<LETTERS.length; i++) {\n            const a = a2i(LETTERS[i]);\n            const b = a2i(wiring[i]);\n            this.map[a] = b;\n            this.revMap[b] = a;\n            uniq[b] = true;\n        }\n        if (Object.keys(uniq).length !== LETTERS.length) {\n            throw new OperationError(\"Rotor wiring must have each letter exactly once\");\n        }\n        const rs = a2i(ringSetting);\n        this.steps = new Set();\n        for (const x of steps) {\n            this.steps.add(Utils.mod(a2i(x) - rs, 26));\n        }\n        if (this.steps.size !== steps.length) {\n            // This isn't strictly fatal, but it's probably a mistake\n            throw new OperationError(\"Rotor steps must be unique\");\n        }\n        this.pos = Utils.mod(a2i(initialPosition) - rs, 26);\n    }\n\n    /**\n     * Step the rotor forward by one.\n     */\n    step() {\n        this.pos = Utils.mod(this.pos + 1, 26);\n        return this.pos;\n    }\n\n    /**\n     * Transform a character through this rotor forwards.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */\n    transform(c) {\n        return Utils.mod(this.map[Utils.mod(c + this.pos, 26)] - this.pos, 26);\n    }\n\n    /**\n     * Transform a character through this rotor backwards.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */\n    revTransform(c) {\n        return Utils.mod(this.revMap[Utils.mod(c + this.pos, 26)] - this.pos, 26);\n    }\n}\n\n/**\n * Base class for plugboard and reflector (since these do effectively the same\n * thing).\n */\nclass PairMapBase {\n    /**\n     * PairMapBase constructor.\n     *\n     * @param {string} pairs - A whitespace separated string of letter pairs to swap.\n     * @param {string} [name='PairMapBase'] - For errors, the name of this object.\n     */\n    constructor(pairs, name=\"PairMapBase\") {\n        // I've chosen to make whitespace significant here to make a) code and\n        // b) inputs easier to read\n        this.pairs = pairs;\n        this.map = {};\n        if (pairs === \"\") {\n            return;\n        }\n        pairs.split(/\\s+/).forEach(pair => {\n            if (!/^[A-Z]{2}$/.test(pair)) {\n                throw new OperationError(name + \" must be a whitespace-separated list of uppercase letter pairs\");\n            }\n            const a = a2i(pair[0]), b = a2i(pair[1]);\n            if (a === b) {\n                // self-stecker\n                return;\n            }\n            if (Object.prototype.hasOwnProperty.call(this.map, a)) {\n                throw new OperationError(`${name} connects ${pair[0]} more than once`);\n            }\n            if (Object.prototype.hasOwnProperty.call(this.map, b)) {\n                throw new OperationError(`${name} connects ${pair[1]} more than once`);\n            }\n            this.map[a] = b;\n            this.map[b] = a;\n        });\n    }\n\n    /**\n     * Transform a character through this object.\n     * Returns other characters unchanged.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */\n    transform(c) {\n        if (!Object.prototype.hasOwnProperty.call(this.map, c)) {\n            return c;\n        }\n        return this.map[c];\n    }\n\n    /**\n     * Alias for transform, to allow interchangeable use with rotors.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */\n    revTransform(c) {\n        return this.transform(c);\n    }\n}\n\n/**\n * Reflector. PairMapBase but requires that all characters are accounted for.\n *\n * Includes a couple of optimisations on that basis.\n */\nexport class Reflector extends PairMapBase {\n    /**\n     * Reflector constructor. See PairMapBase.\n     * Additional restriction: every character must be accounted for.\n     */\n    constructor(pairs) {\n        super(pairs, \"Reflector\");\n        const s = Object.keys(this.map).length;\n        if (s !== 26) {\n            throw new OperationError(\"Reflector must have exactly 13 pairs covering every letter\");\n        }\n        const optMap = new Array(26);\n        for (const x of Object.keys(this.map)) {\n            optMap[x] = this.map[x];\n        }\n        this.map = optMap;\n    }\n\n    /**\n     * Transform a character through this object.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */\n    transform(c) {\n        return this.map[c];\n    }\n}\n\n/**\n * Plugboard. Unmodified PairMapBase.\n */\nexport class Plugboard extends PairMapBase {\n    /**\n     * Plugboard constructor. See PairMapbase.\n     */\n    constructor(pairs) {\n        super(pairs, \"Plugboard\");\n    }\n}\n\n/**\n * Base class for the Enigma machine itself. Holds rotors, a reflector, and a plugboard.\n */\nexport class EnigmaBase {\n    /**\n     * EnigmaBase constructor.\n     *\n     * @param {Object[]} rotors - List of Rotors.\n     * @param {Object} reflector - A Reflector.\n     * @param {Plugboard} plugboard - A Plugboard.\n     */\n    constructor(rotors, reflector, plugboard) {\n        this.rotors = rotors;\n        this.rotorsRev = [].concat(rotors).reverse();\n        this.reflector = reflector;\n        this.plugboard = plugboard;\n    }\n\n    /**\n     * Step the rotors forward by one.\n     *\n     * This happens before the output character is generated.\n     *\n     * Note that rotor 4, if it's there, never steps.\n     *\n     * Why is all the logic in EnigmaBase and not a nice neat method on\n     * Rotor that knows when it should advance the next item?\n     * Because the double stepping anomaly is a thing. tl;dr if the left rotor\n     * should step the next time the middle rotor steps, the middle rotor will\n     * immediately step.\n     */\n    step() {\n        const r0 = this.rotors[0];\n        const r1 = this.rotors[1];\n        r0.step();\n        // The second test here is the double-stepping anomaly\n        if (r0.steps.has(r0.pos) || r1.steps.has(Utils.mod(r1.pos + 1, 26))) {\n            r1.step();\n            if (r1.steps.has(r1.pos)) {\n                const r2 = this.rotors[2];\n                r2.step();\n            }\n        }\n    }\n\n    /**\n     * Encrypt (or decrypt) some data.\n     * Takes an arbitrary string and runs the Engima machine on that data from\n     * *its current state*, and outputs the result. Non-alphabetic characters\n     * are returned unchanged.\n     *\n     * @param {string} input - Data to encrypt.\n     * @returns {string}\n     */\n    crypt(input) {\n        let result = \"\";\n        for (const c of input) {\n            let letter = a2i(c, true);\n            if (letter === -1) {\n                result += c;\n                continue;\n            }\n            // First, step the rotors forward.\n            this.step();\n            // Now, run through the plugboard.\n            letter = this.plugboard.transform(letter);\n            // Then through each wheel in sequence, through the reflector, and\n            // backwards through the wheels again.\n            for (const rotor of this.rotors) {\n                letter = rotor.transform(letter);\n            }\n            letter = this.reflector.transform(letter);\n            for (const rotor of this.rotorsRev) {\n                letter = rotor.revTransform(letter);\n            }\n            // Finally, back through the plugboard.\n            letter = this.plugboard.revTransform(letter);\n            result += i2a(letter);\n        }\n        return result;\n    }\n}\n\n/**\n * The Enigma machine itself. Holds 3-4 rotors, a reflector, and a plugboard.\n */\nexport class EnigmaMachine extends EnigmaBase {\n    /**\n     * EnigmaMachine constructor.\n     *\n     * @param {Object[]} rotors - List of Rotors.\n     * @param {Object} reflector - A Reflector.\n     * @param {Plugboard} plugboard - A Plugboard.\n     */\n    constructor(rotors, reflector, plugboard) {\n        super(rotors, reflector, plugboard);\n        if (rotors.length !== 3 && rotors.length !== 4) {\n            throw new OperationError(\"Enigma must have 3 or 4 rotors\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/core/lib/Extract.mjs",
    "content": "/**\n * Identifier extraction functions\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n *\n */\n\n/**\n * Runs search operations across the input data using regular expressions.\n *\n * @param {string} input\n * @param {RegExp} searchRegex\n * @param {RegExp} [removeRegex=null] - A regular expression defining results to remove from the\n *      final list\n * @param {Function} [sortBy=null] - The sorting comparison function to apply\n * @param {boolean} [unique=false] - Whether to unique the results\n * @returns {string}\n */\nexport function search(input, searchRegex, removeRegex=null, sortBy=null, unique=false) {\n    let results = [];\n    let match;\n\n    while ((match = searchRegex.exec(input))) {\n        // Moves pointer when an empty string is matched (prevents infinite loop)\n        if (match.index === searchRegex.lastIndex) {\n            searchRegex.lastIndex++;\n        }\n\n        if (removeRegex && removeRegex.test(match[0]))\n            continue;\n\n        results.push(match[0]);\n    }\n\n    if (sortBy) {\n        results = results.sort(sortBy);\n    }\n\n    if (unique) {\n        results = results.unique();\n    }\n\n    return results;\n}\n\n\n/**\n * URL regular expression\n */\nconst protocol = \"[A-Z]+://\",\n    hostname = \"[-\\\\w]+(?:\\\\.\\\\w[-\\\\w]*)+\",\n    port = \":\\\\d+\",\n    path = \"/[^.!,?\\\"<>\\\\[\\\\]{}\\\\s\\\\x7F-\\\\xFF]*\" +\n        \"(?:[.!,?]+[^.!,?\\\"<>\\\\[\\\\]{}\\\\s\\\\x7F-\\\\xFF]+)*\";\n\nexport const URL_REGEX = new RegExp(protocol + hostname + \"(?:\" + port + \")?(?:\" + path + \")?\", \"ig\");\n\n\n/**\n * Domain name regular expression\n */\nexport const DOMAIN_REGEX = /\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b/ig;\n\n\n/**\n * DMARC Domain name regular expression\n */\nexport const DMARC_DOMAIN_REGEX = /\\b((?=[a-z0-9_-]{1,63}\\.)(xn--)?[a-z0-9_]+(-[a-z0-9_]+)*\\.)+[a-z]{2,63}\\b/ig;\n"
  },
  {
    "path": "src/core/lib/FileSignatures.mjs",
    "content": "/**\n * File signatures and extractor functions\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n *\n */\nimport Stream from \"./Stream.mjs\";\n\n/**\n * A categorised table of file types, including signatures to identify them and functions\n * to extract them where possible.\n */\nexport const FILE_SIGNATURES = {\n    \"Images\": [\n        {\n            name: \"Joint Photographic Experts Group image\",\n            extension: \"jpg,jpeg,jpe,thm,mpo\",\n            mime: \"image/jpeg\",\n            description: \"\",\n            signature: {\n                0: 0xff,\n                1: 0xd8,\n                2: 0xff,\n                3: [0xc0, 0xc4, 0xdb, 0xdd, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe7, 0xe8, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xfe]\n            },\n            extractor: extractJPEG\n        },\n        {\n            name: \"Graphics Interchange Format image\",\n            extension: \"gif\",\n            mime: \"image/gif\",\n            description: \"\",\n            signature: {\n                0: 0x47, // GIF\n                1: 0x49,\n                2: 0x46,\n                3: 0x38, // 8\n                4: [0x37, 0x39], // 7|9\n                5: 0x61  // a\n            },\n            extractor: extractGIF\n        },\n        {\n            name: \"Portable Network Graphics image\",\n            extension: \"png\",\n            mime: \"image/png\",\n            description: \"\",\n            signature: {\n                0: 0x89,\n                1: 0x50, // PNG\n                2: 0x4e,\n                3: 0x47,\n                4: 0x0d,\n                5: 0x0a,\n                6: 0x1a,\n                7: 0x0a\n            },\n            extractor: extractPNG\n        },\n        {\n            name: \"WEBP Image\",\n            extension: \"webp\",\n            mime: \"image/webp\",\n            description: \"\",\n            signature: {\n                8: 0x57,\n                9: 0x45,\n                10: 0x42,\n                11: 0x50\n            },\n            extractor: extractWEBP\n        },\n        {\n            name: \"High Efficiency Image File Format\",\n            extension: \"heic,heif\",\n            mime: \"image/heif\",\n            description: \"\",\n            signature: {\n                0: 0x00,\n                1: 0x00,\n                2: 0x00,\n                3: [0x24, 0x18],\n                4: 0x66, // ftypheic\n                5: 0x74,\n                6: 0x79,\n                7: 0x70,\n                8: 0x68,\n                9: 0x65,\n                10: 0x69,\n                11: 0x63\n            },\n            extractor: null\n        },\n        {\n            name: \"Camera Image File Format\",\n            extension: \"crw\",\n            mime: \"image/x-canon-crw\",\n            description: \"\",\n            signature: {\n                6: 0x48, // HEAPCCDR\n                7: 0x45,\n                8: 0x41,\n                9: 0x50,\n                10: 0x43,\n                11: 0x43,\n                12: 0x44,\n                13: 0x52\n            },\n            extractor: null\n        },\n        { // Place before tiff check\n            name: \"Canon CR2 raw image\",\n            extension: \"cr2\",\n            mime: \"image/x-canon-cr2\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x49,\n                    1: 0x49,\n                    2: 0x2a,\n                    3: 0x0,\n                    8: 0x43,\n                    9: 0x52\n                },\n                {\n                    0: 0x4d,\n                    1: 0x4d,\n                    2: 0x0,\n                    3: 0x2a,\n                    8: 0x43,\n                    9: 0x52\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"Tagged Image File Format image\",\n            extension: \"tif\",\n            mime: \"image/tiff\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x49,\n                    1: 0x49,\n                    2: 0x2a,\n                    3: 0x0\n                },\n                {\n                    0: 0x4d,\n                    1: 0x4d,\n                    2: 0x0,\n                    3: 0x2a\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"Bitmap image\",\n            extension: \"bmp\",\n            mime: \"image/bmp\",\n            description: \"\",\n            signature: {\n                0: 0x42,\n                1: 0x4d,\n                7: 0x0,\n                9: 0x0,\n                14: [0x0c, 0x28, 0x38, 0x40, 0x6c, 0x7c],\n                15: 0x0,\n                16: 0x0,\n                17: 0x0\n            },\n            extractor: extractBMP\n        },\n        {\n            name: \"JPEG Extended Range image\",\n            extension: \"jxr\",\n            mime: \"image/vnd.ms-photo\",\n            description: \"\",\n            signature: {\n                0: 0x49,\n                1: 0x49,\n                2: 0xbc\n            },\n            extractor: null\n        },\n        {\n            name: \"Photoshop image\",\n            extension: \"psd\",\n            mime: \"image/vnd.adobe.photoshop\",\n            description: \"\",\n            signature: {\n                0: 0x38, // 8BPS\n                1: 0x42,\n                2: 0x50,\n                3: 0x53,\n                4: 0x0,\n                5: 0x1,\n                6: 0x0,\n                7: 0x0,\n                8: 0x0,\n                9: 0x0,\n                10: 0x0,\n                11: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"Photoshop Large Document\",\n            extension: \"psb\",\n            mime: \"application/x-photoshop\",\n            description: \"\",\n            signature: {\n                0: 0x38, // 8BPS\n                1: 0x42,\n                2: 0x50,\n                3: 0x53,\n                4: 0x0,\n                5: 0x2,\n                6: 0x0,\n                7: 0x0,\n                8: 0x0,\n                9: 0x0,\n                10: 0x0,\n                11: 0x0,\n                12: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"Paint Shop Pro image\",\n            extension: \"psp\",\n            mime: \"image/psp\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x50, // Paint Shop Pro Im\n                    1: 0x61,\n                    2: 0x69,\n                    3: 0x6e,\n                    4: 0x74,\n                    5: 0x20,\n                    6: 0x53,\n                    7: 0x68,\n                    8: 0x6f,\n                    9: 0x70,\n                    10: 0x20,\n                    11: 0x50,\n                    12: 0x72,\n                    13: 0x6f,\n                    14: 0x20,\n                    15: 0x49,\n                    16: 0x6d\n                },\n                {\n                    0: 0x7e,\n                    1: 0x42,\n                    2: 0x4b,\n                    3: 0x0\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"The GIMP image\",\n            extension: \"xcf\",\n            mime: \"image/x-xcf\",\n            description: \"\",\n            signature: {\n                0: 0x67, // gimp xcf\n                1: 0x69,\n                2: 0x6d,\n                3: 0x70,\n                4: 0x20,\n                5: 0x78,\n                6: 0x63,\n                7: 0x66,\n                8: 0x20,\n                9: [0x66, 0x76],\n                10: [0x69, 0x30],\n                11: [0x6c, 0x30],\n                12: [0x65, 0x31, 0x32, 0x33]\n            },\n            extractor: null\n        },\n        {\n            name: \"Icon image\",\n            extension: \"ico\",\n            mime: \"image/x-icon\",\n            description: \"\",\n            signature: {\n                0: 0x0,\n                1: 0x0,\n                2: 0x1,\n                3: 0x0,\n                4: [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15],\n                5: 0x0,\n                6: [0x10, 0x20, 0x30, 0x40, 0x80],\n                7: [0x10, 0x20, 0x30, 0x40, 0x80],\n                9: 0x0,\n                10: [0x0, 0x1]\n            },\n            extractor: extractICO\n        },\n        {\n            name: \"Radiance High Dynamic Range image\",\n            extension: \"hdr\",\n            mime: \"image/vnd.radiance\",\n            description: \"\",\n            signature: {\n                0: 0x23, // #?RADIANCE\n                1: 0x3f,\n                2: 0x52,\n                3: 0x41,\n                4: 0x44,\n                5: 0x49,\n                6: 0x41,\n                7: 0x4e,\n                8: 0x43,\n                9: 0x45,\n                10: 0x0a\n            },\n            extractor: null\n        },\n        {\n            name: \"Sony ARW image\",\n            extension: \"arw\",\n            mime: \"image/x-raw\",\n            description: \"\",\n            signature: {\n                0: 0x05,\n                1: 0x0,\n                2: 0x0,\n                3: 0x0,\n                4: 0x41,\n                5: 0x57,\n                6: 0x31,\n                7: 0x2e\n            },\n            extractor: null\n        },\n        {\n            name: \"Fujifilm Raw Image\",\n            extension: \"raf\",\n            mime: \"image/x-raw\",\n            description: \"\",\n            signature: {\n                0: 0x46, // FUJIFILMCCD-RAW\n                1: 0x55,\n                2: 0x4a,\n                3: 0x49,\n                4: 0x46,\n                5: 0x49,\n                6: 0x4c,\n                7: 0x4d,\n                8: 0x43,\n                9: 0x43,\n                10: 0x44,\n                11: 0x2d,\n                12: 0x52,\n                13: 0x41,\n                14: 0x57\n            },\n            extractor: null\n        },\n        {\n            name: \"Minolta RAW image\",\n            extension: \"mrw\",\n            mime: \"image/x-raw\",\n            description: \"\",\n            signature: {\n                0: 0x0,\n                1: 0x4d, // MRM\n                2: 0x52,\n                3: 0x4d\n            },\n            extractor: null\n        },\n        {\n            name: \"Adobe Bridge Thumbnail Cache\",\n            extension: \"bct\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x6c,\n                1: 0x6e,\n                2: 0x62,\n                3: 0x74,\n                4: 0x02,\n                5: 0x0,\n                6: 0x0,\n                7: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"Microsoft Document Imaging\",\n            extension: \"mdi\",\n            mime: \"image/vnd.ms-modi\",\n            description: \"\",\n            signature: {\n                0: 0x45,\n                1: 0x50,\n                2: 0x2a,\n                3: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Joint Photographic Experts Group image (under Base64)\",\n            extension: \"B64\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x2f,\n                1: 0x39,\n                2: 0x6a,\n                3: 0x2f,\n                4: 0x34\n            },\n            extractor: null\n        },\n        {\n            name: \"Portable Network Graphics image (under Base64)\",\n            extension: \"B64\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x69,\n                1: 0x56,\n                2: 0x42,\n                3: 0x4f,\n                4: 0x52,\n                5: 0x77,\n                6: 0x30\n            },\n            extractor: null\n        },\n        {\n            name: \"AutoCAD Drawing\",\n            extension: \"dwg,123d\",\n            mime: \"application/acad\",\n            description: \"\",\n            signature: {\n                0: 0x41,\n                1: 0x43,\n                2: 0x31,\n                3: 0x30,\n                4: [0x30, 0x31],\n                5: [0x30, 0x31, 0x32, 0x33, 0x34, 0x35],\n                6: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"AutoCAD Drawing\",\n            extension: \"dwg,dwt\",\n            mime: \"application/acad\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x41,\n                    1: 0x43,\n                    2: 0x31,\n                    3: 0x30,\n                    4: 0x31,\n                    5: 0x38,\n                    6: 0x00\n                },\n                {\n                    0: 0x41,\n                    1: 0x43,\n                    2: 0x31,\n                    3: 0x30,\n                    4: 0x32,\n                    5: 0x34,\n                    6: 0x00\n                },\n                {\n                    0: 0x41,\n                    1: 0x43,\n                    2: 0x31,\n                    3: 0x30,\n                    4: 0x32,\n                    5: 0x37,\n                    6: 0x00\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"Targa Image\",\n            extension: \"tga\",\n            mime: \"image/x-targa\",\n            description: \"\",\n            signature: [\n                { // This signature is not at the beginning of the file. The extractor works backwards.\n                    0: 0x54,\n                    1: 0x52,\n                    2: 0x55,\n                    3: 0x45,\n                    4: 0x56,\n                    5: 0x49,\n                    6: 0x53,\n                    7: 0x49,\n                    8: 0x4f,\n                    9: 0x4e,\n                    10: 0x2d,\n                    11: 0x58,\n                    12: 0x46,\n                    13: 0x49,\n                    14: 0x4c,\n                    15: 0x45,\n                    16: 0x2e\n                }\n            ],\n            extractor: extractTARGA\n        }\n    ],\n    \"Video\": [\n        { // Place before webm\n            name: \"Matroska Multimedia Container\",\n            extension: \"mkv\",\n            mime: \"video/x-matroska\",\n            description: \"\",\n            signature: {\n                31: 0x6d,\n                32: 0x61,\n                33: 0x74,\n                34: 0x72,\n                35: 0x6f,\n                36: 0x73,\n                37: 0x6b,\n                38: 0x61\n            },\n            extractor: null\n        },\n        {\n            name: \"WEBM video\",\n            extension: \"webm\",\n            mime: \"video/webm\",\n            description: \"\",\n            signature: {\n                0: 0x1a,\n                1: 0x45,\n                2: 0xdf,\n                3: 0xa3\n            },\n            extractor: null\n        },\n        { // Place before MPEG-4\n            name: \"Flash MP4 video\",\n            extension: \"f4v\",\n            mime: \"video/mp4\",\n            description: \"\",\n            signature: {\n                4: 0x66,\n                5: 0x74,\n                6: 0x79,\n                7: 0x70,\n                8: [0x66, 0x46],\n                9: 0x34,\n                10: [0x76, 0x56],\n                11: 0x20\n            },\n            extractor: null\n        },\n        {\n            name: \"MPEG-4 video\",\n            extension: \"mp4\",\n            mime: \"video/mp4\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x0,\n                    1: 0x0,\n                    2: 0x0,\n                    3: [0x18, 0x20],\n                    4: 0x66,\n                    5: 0x74,\n                    6: 0x79,\n                    7: 0x70\n                },\n                {\n                    0: 0x33, // 3gp5\n                    1: 0x67,\n                    2: 0x70,\n                    3: 0x35\n                },\n                {\n                    0: 0x0,\n                    1: 0x0,\n                    2: 0x0,\n                    3: 0x1c,\n                    4: 0x66,\n                    5: 0x74,\n                    6: 0x79,\n                    7: 0x70,\n                    8: 0x6d,\n                    9: 0x70,\n                    10: 0x34,\n                    11: 0x32,\n                    16: 0x6d, // mp41mp42isom\n                    17: 0x70,\n                    18: 0x34,\n                    19: 0x31,\n                    20: 0x6d,\n                    21: 0x70,\n                    22: 0x34,\n                    23: 0x32,\n                    24: 0x69,\n                    25: 0x73,\n                    26: 0x6f,\n                    27: 0x6d\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"M4V video\",\n            extension: \"m4v\",\n            mime: \"video/x-m4v\",\n            description: \"\",\n            signature: {\n                0: 0x0,\n                1: 0x0,\n                2: 0x0,\n                3: 0x1c,\n                4: 0x66,\n                5: 0x74,\n                6: 0x79,\n                7: 0x70,\n                8: 0x4d,\n                9: 0x34,\n                10: 0x56\n            },\n            extractor: null\n        },\n        {\n            name: \"Quicktime video\",\n            extension: \"mov\",\n            mime: \"video/quicktime\",\n            description: \"\",\n            signature: {\n                0: 0x0,\n                1: 0x0,\n                2: 0x0,\n                3: 0x14,\n                4: 0x66,\n                5: 0x74,\n                6: 0x79,\n                7: 0x70\n            },\n            extractor: null\n        },\n        {\n            name: \"Audio Video Interleave\",\n            extension: \"avi\",\n            mime: \"video/x-msvideo\",\n            description: \"\",\n            signature: {\n                0: 0x52,\n                1: 0x49,\n                2: 0x46,\n                3: 0x46,\n                8: 0x41,\n                9: 0x56,\n                10: 0x49\n            },\n            extractor: null\n        },\n        {\n            name: \"Windows Media Video\",\n            extension: \"wmv\",\n            mime: \"video/x-ms-wmv\",\n            description: \"\",\n            signature: {\n                0: 0x30,\n                1: 0x26,\n                2: 0xb2,\n                3: 0x75,\n                4: 0x8e,\n                5: 0x66,\n                6: 0xcf,\n                7: 0x11,\n                8: 0xa6,\n                9: 0xd9\n            },\n            extractor: null\n        },\n        {\n            name: \"MPEG video\",\n            extension: \"mpg\",\n            mime: \"video/mpeg\",\n            description: \"\",\n            signature: {\n                0: 0x0,\n                1: 0x0,\n                2: 0x1,\n                3: 0xba\n            },\n            extractor: null\n        },\n        {\n            name: \"Flash Video\",\n            extension: \"flv\",\n            mime: \"video/x-flv\",\n            description: \"\",\n            signature: {\n                0: 0x46,\n                1: 0x4c,\n                2: 0x56,\n                3: 0x1\n            },\n            extractor: extractFLV\n        },\n        {\n            name: \"OGG Video\",\n            extension: \"ogv,ogm,opus,ogx\",\n            mime: \"video/ogg\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x4f, // OggS\n                    1: 0x67,\n                    2: 0x67,\n                    3: 0x53,\n                    4: 0x00,\n                    5: 0x02,\n                    28: 0x01,\n                    29: 0x76, // video\n                    30: 0x69,\n                    31: 0x64,\n                    32: 0x65,\n                    33: 0x6f\n                },\n                {\n                    0: 0x4f, // OggS\n                    1: 0x67,\n                    2: 0x67,\n                    3: 0x53,\n                    4: 0x00,\n                    5: 0x02,\n                    28: 0x80,\n                    29: 0x74, // theora\n                    30: 0x68,\n                    31: 0x65,\n                    32: 0x6f,\n                    33: 0x72,\n                    34: 0x61\n                },\n                {\n                    0: 0x4f, // OggS\n                    1: 0x67,\n                    2: 0x67,\n                    3: 0x53,\n                    4: 0x00,\n                    5: 0x02,\n                    28: 0x66, // fishead\n                    29: 0x69,\n                    30: 0x73,\n                    31: 0x68,\n                    32: 0x65,\n                    33: 0x61,\n                    34: 0x64\n                }\n            ],\n            extractor: null\n        },\n    ],\n    \"Audio\": [\n        {\n            name: \"Waveform Audio\",\n            extension: \"wav\",\n            mime: \"audio/x-wav\",\n            description: \"\",\n            signature: {\n                0: 0x52,\n                1: 0x49,\n                2: 0x46,\n                3: 0x46,\n                8: 0x57,\n                9: 0x41,\n                10: 0x56,\n                11: 0x45\n            },\n            extractor: extractWAV\n        },\n        {\n            name: \"OGG audio\",\n            extension: \"ogg\",\n            mime: \"audio/ogg\",\n            description: \"\",\n            signature: {\n                0: 0x4f,\n                1: 0x67,\n                2: 0x67,\n                3: 0x53\n            },\n            extractor: null\n        },\n        {\n            name: \"Musical Instrument Digital Interface audio\",\n            extension: \"midi\",\n            mime: \"audio/midi\",\n            description: \"\",\n            signature: {\n                0: 0x4d,\n                1: 0x54,\n                2: 0x68,\n                3: 0x64\n            },\n            extractor: null\n        },\n        {\n            name: \"MPEG-3 audio\",\n            extension: \"mp3\",\n            mime: \"audio/mpeg\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x49,\n                    1: 0x44,\n                    2: 0x33\n                },\n                {\n                    0: 0xff,\n                    1: 0xfb\n                }\n            ],\n            extractor: extractMP3\n        },\n        {\n            name: \"MPEG-4 Part 14 audio\",\n            extension: \"m4a\",\n            mime: \"audio/m4a\",\n            description: \"\",\n            signature: [\n                {\n                    4: 0x66,\n                    5: 0x74,\n                    6: 0x79,\n                    7: 0x70,\n                    8: 0x4d,\n                    9: 0x34,\n                    10: 0x41\n                },\n                {\n                    0: 0x4d,\n                    1: 0x34,\n                    2: 0x41,\n                    3: 0x20\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"Free Lossless Audio Codec\",\n            extension: \"flac\",\n            mime: \"audio/x-flac\",\n            description: \"\",\n            signature: {\n                0: 0x66,\n                1: 0x4c,\n                2: 0x61,\n                3: 0x43\n            },\n            extractor: null\n        },\n        {\n            name: \"Adaptive Multi-Rate audio codec\",\n            extension: \"amr\",\n            mime: \"audio/amr\",\n            description: \"\",\n            signature: {\n                0: 0x23,\n                1: 0x21,\n                2: 0x41,\n                3: 0x4d,\n                4: 0x52,\n                5: 0x0a\n            },\n            extractor: null\n        },\n        {\n            name: \"Audacity\",\n            extension: \"au\",\n            mime: \"audio/x-au\",\n            description: \"\",\n            signature: {\n                0: 0x64, // dns.\n                1: 0x6e,\n                2: 0x73,\n                3: 0x2e,\n\n                24: 0x41, // AudacityBlockFile\n                25: 0x75,\n                26: 0x64,\n                27: 0x61,\n                28: 0x63,\n                29: 0x69,\n                30: 0x74,\n                31: 0x79,\n                32: 0x42,\n                33: 0x6c,\n                34: 0x6f,\n                35: 0x63,\n                36: 0x6b,\n                37: 0x46,\n                38: 0x69,\n                39: 0x6c,\n                40: 0x65\n            },\n            extractor: null\n        },\n        {\n            name: \"Audacity Block\",\n            extension: \"auf\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x41, // AudacityBlockFile\n                1: 0x75,\n                2: 0x64,\n                3: 0x61,\n                4: 0x63,\n                5: 0x69,\n                6: 0x74,\n                7: 0x79,\n                8: 0x42,\n                9: 0x6c,\n                10: 0x6f,\n                11: 0x63,\n                12: 0x6b,\n                13: 0x46,\n                14: 0x69,\n                15: 0x6c,\n                16: 0x65\n            },\n            extractor: null\n        },\n        {\n            name: \"Audio Interchange File\",\n            extension: \"aif\",\n            mime: \"audio/x-aiff\",\n            description: \"\",\n            signature: {\n                0: 0x46, // FORM\n                1: 0x4f,\n                2: 0x52,\n                3: 0x4d,\n                8: 0x41, // AIFF\n                9: 0x49,\n                10: 0x46,\n                11: 0x46\n            },\n            extractor: null\n        },\n        {\n            name: \"Audio Interchange File (compressed)\",\n            extension: \"aifc\",\n            mime: \"audio/x-aifc\",\n            description: \"\",\n            signature: {\n                0: 0x46, // FORM\n                1: 0x4f,\n                2: 0x52,\n                3: 0x4d,\n                8: 0x41, // AIFC\n                9: 0x49,\n                10: 0x46,\n                11: 0x43\n            },\n            extractor: null\n        }\n    ],\n    \"Documents\": [\n        {\n            name: \"Portable Document Format\",\n            extension: \"pdf\",\n            mime: \"application/pdf\",\n            description: \"\",\n            signature: {\n                0: 0x25,\n                1: 0x50,\n                2: 0x44,\n                3: 0x46\n            },\n            extractor: extractPDF\n        },\n        {\n            name: \"Portable Document Format (under Base64)\",\n            extension: \"B64\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x41,\n                1: 0x4a,\n                2: 0x56,\n                3: 0x42,\n                4: 0x45,\n                5: 0x52,\n                6: 0x69\n            },\n            extractor: null\n        },\n        { // Place before PostScript\n            name: \"Adobe PostScript\",\n            extension: \"ps,eps,ai,pfa\",\n            mime: \"application/postscript\",\n            description: \"\",\n            signature: {\n                0: 0x25,\n                1: 0x21,\n                2: 0x50,\n                3: 0x53,\n                4: 0x2d,\n                5: 0x41,\n                6: 0x64,\n                7: 0x6f,\n                8: 0x62,\n                9: 0x65\n            },\n            extractor: null\n        },\n        {\n            name: \"PostScript\",\n            extension: \"ps\",\n            mime: \"application/postscript\",\n            description: \"\",\n            signature: {\n                0: 0x25,\n                1: 0x21\n            },\n            extractor: null\n        },\n        {\n            name: \"Encapsulated PostScript\",\n            extension: \"eps,ai\",\n            mime: \"application/eps\",\n            description: \"\",\n            signature: {\n                0: 0xc5,\n                1: 0xd0,\n                2: 0xd3,\n                3: 0xc6\n            },\n            extractor: null\n        },\n        {\n            name: \"Rich Text Format\",\n            extension: \"rtf\",\n            mime: \"application/rtf\",\n            description: \"\",\n            signature: {\n                0: 0x7b,\n                1: 0x5c,\n                2: 0x72,\n                3: 0x74\n            },\n            extractor: extractRTF\n        },\n        {\n            name: \"Microsoft Office document/OLE2\",\n            extension: \"ole2,doc,xls,dot,ppt,xla,ppa,pps,pot,msi,sdw,db,vsd,msg\",\n            mime: \"application/msword,application/vnd.ms-excel,application/vnd.ms-powerpoint\",\n            description: \"Microsoft Office documents\",\n            signature: {\n                0: 0xd0,\n                1: 0xcf,\n                2: 0x11,\n                3: 0xe0,\n                4: 0xa1,\n                5: 0xb1,\n                6: 0x1a,\n                7: 0xe1\n            },\n            extractor: null\n        },\n        {\n            name: \"Microsoft Office document/OLE2 (under Base64)\",\n            extension: \"B64\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x30,\n                1: 0x4d,\n                2: 0x38,\n                3: 0x52,\n                4: 0x34,\n                5: 0x4b,\n                6: 0x47,\n                7: 0x78\n            },\n            extractor: null\n        },\n        {\n            name: \"Microsoft Office 2007+ document\",\n            extension: \"docx,xlsx,pptx\",\n            mime: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n            description: \"\",\n            signature: {\n                38: 0x5f, // _Types].xml\n                39: 0x54,\n                40: 0x79,\n                41: 0x70,\n                42: 0x65,\n                43: 0x73,\n                44: 0x5d,\n                45: 0x2e,\n                46: 0x78,\n                47: 0x6d,\n                48: 0x6c\n            },\n            extractor: extractZIP\n        },\n        {\n            name: \"Microsoft Access database\",\n            extension: \"mdb,mda,mde,mdt,fdb,psa\",\n            mime: \"application/msaccess\",\n            description: \"\",\n            signature: {\n                0: 0x00,\n                1: 0x01,\n                2: 0x00,\n                3: 0x00,\n                4: 0x53, // Standard Jet\n                5: 0x74,\n                6: 0x61,\n                7: 0x6e,\n                8: 0x64,\n                9: 0x61,\n                10: 0x72,\n                11: 0x64,\n                12: 0x20,\n                13: 0x4a,\n                14: 0x65,\n                15: 0x74\n            },\n            extractor: null\n        },\n        {\n            name: \"Microsoft Access 2007+ database\",\n            extension: \"accdb,accde,accda,accdu\",\n            mime: \"application/msaccess\",\n            description: \"\",\n            signature: {\n                0: 0x00,\n                1: 0x01,\n                2: 0x00,\n                3: 0x00,\n                4: 0x53, // Standard ACE DB\n                5: 0x74,\n                6: 0x61,\n                7: 0x6e,\n                8: 0x64,\n                9: 0x61,\n                10: 0x72,\n                11: 0x64,\n                12: 0x20,\n                13: 0x41,\n                14: 0x43,\n                15: 0x45,\n                16: 0x20\n            },\n            extractor: null\n        },\n        {\n            name: \"Microsoft OneNote document\",\n            extension: \"one\",\n            mime: \"application/onenote\",\n            description: \"\",\n            signature: {\n                0: 0xe4,\n                1: 0x52,\n                2: 0x5c,\n                3: 0x7b,\n                4: 0x8c,\n                5: 0xd8,\n                6: 0xa7,\n                7: 0x4d,\n                8: 0xae,\n                9: 0xb1,\n                10: 0x53,\n                11: 0x78,\n                12: 0xd0,\n                13: 0x29,\n                14: 0x96,\n                15: 0xd3\n            },\n            extractor: null\n        },\n        {\n            name: \"Outlook Express database\",\n            extension: \"dbx\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0xcf,\n                1: 0xad,\n                2: 0x12,\n                3: 0xfe,\n                4: [0x30, 0xc5, 0xc6, 0xc7],\n                11: 0x11\n            },\n            extractor: null\n        },\n        {\n            name: \"Personal Storage Table (Outlook)\",\n            extension: \"pst,ost,fdb,pab\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x21, // !BDN\n                1: 0x42,\n                2: 0x44,\n                3: 0x4e\n            },\n            extractor: null\n        },\n        {\n            name: \"Microsoft Exchange Database\",\n            extension: \"edb\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                4: 0xef,\n                5: 0xcd,\n                6: 0xab,\n                7: 0x89,\n                8: [0x20, 0x23],\n                9: 0x06,\n                10: 0x00,\n                11: 0x00,\n                12: [0x00, 0x01],\n                13: 0x00,\n                14: 0x00,\n                15: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"WordPerfect document\",\n            extension: \"wpd,wp,wp5,wp6,wpp,bk!,wcm\",\n            mime: \"application/wordperfect\",\n            description: \"\",\n            signature: {\n                0: 0xff,\n                1: 0x57,\n                2: 0x50,\n                3: 0x43,\n                7: [0x00, 0x01, 0x02],\n                8: 0x01,\n                9: 0x0a\n            },\n            extractor: null\n        },\n        {\n            name: \"EPUB e-book\",\n            extension: \"epub\",\n            mime: \"application/epub+zip\",\n            description: \"\",\n            signature: {\n                0: 0x50,\n                1: 0x4b,\n                2: 0x3,\n                3: 0x4,\n                30: 0x6d, // mimetypeapplication/epub_zip\n                31: 0x69,\n                32: 0x6d,\n                33: 0x65,\n                34: 0x74,\n                35: 0x79,\n                36: 0x70,\n                37: 0x65,\n                38: 0x61,\n                39: 0x70,\n                40: 0x70,\n                41: 0x6c,\n                42: 0x69,\n                43: 0x63,\n                44: 0x61,\n                45: 0x74,\n                46: 0x69,\n                47: 0x6f,\n                48: 0x6e,\n                49: 0x2f,\n                50: 0x65,\n                51: 0x70,\n                52: 0x75,\n                53: 0x62,\n                54: 0x2b,\n                55: 0x7a,\n                56: 0x69,\n                57: 0x70\n            },\n            extractor: extractZIP\n        },\n    ],\n    \"Applications\": [\n        {\n            name: \"Windows Portable Executable\",\n            extension: \"exe,dll,drv,vxd,sys,ocx,vbx,com,fon,scr\",\n            mime: \"application/vnd.microsoft.portable-executable\",\n            description: \"\",\n            signature: {\n                0: 0x4d,\n                1: 0x5a,\n                3: [0x0, 0x1, 0x2],\n                5: [0x0, 0x1, 0x2]\n            },\n            extractor: extractMZPE\n        },\n        {\n            name: \"Executable and Linkable Format\",\n            extension: \"elf,bin,axf,o,prx,so\",\n            mime: \"application/x-executable\",\n            description: \"Executable and Linkable Format file. No standard file extension.\",\n            signature: {\n                0: 0x7f,\n                1: 0x45,\n                2: 0x4c,\n                3: 0x46\n            },\n            extractor: extractELF\n        },\n        {\n            name: \"MacOS Mach-O object\",\n            extension: \"dylib\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0xca,\n                    1: 0xfe,\n                    2: 0xba,\n                    3: 0xbe,\n                    4: 0x00,\n                    5: 0x00,\n                    6: 0x00,\n                    7: [0x01, 0x02, 0x03]\n                },\n                {\n                    0: 0xce,\n                    1: 0xfa,\n                    2: 0xed,\n                    3: 0xfe,\n                    4: 0x07,\n                    5: 0x00,\n                    6: 0x00,\n                    7: 0x00,\n                    8: [0x01, 0x02, 0x03]\n                }\n            ],\n            extractor: extractMACHO\n        },\n        {\n            name: \"MacOS Mach-O 64-bit object\",\n            extension: \"dylib\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0xcf,\n                1: 0xfa,\n                2: 0xed,\n                3: 0xfe\n            },\n            extractor: extractMACHO\n        },\n        {\n            name: \"Adobe Flash\",\n            extension: \"swf\",\n            mime: \"application/x-shockwave-flash\",\n            description: \"\",\n            signature: {\n                0: [0x43, 0x46],\n                1: 0x57,\n                2: 0x53,\n            },\n            extractor: null\n        },\n        {\n            name: \"Java Class\",\n            extension: \"class\",\n            mime: \"application/java-vm\",\n            description: \"\",\n            signature: {\n                0: 0xca,\n                1: 0xfe,\n                2: 0xba,\n                3: 0xbe\n            },\n            extractor: null\n        },\n        {\n            name: \"Dalvik Executable\",\n            extension: \"dex\",\n            mime: \"application/octet-stream\",\n            description: \"Dalvik Executable as used by Android\",\n            signature: {\n                0: 0x64,\n                1: 0x65,\n                2: 0x78,\n                3: 0x0a,\n                4: 0x30,\n                5: 0x33,\n                6: 0x35,\n                7: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"Google Chrome Extension\",\n            extension: \"crx\",\n            mime: \"application/crx\",\n            description: \"Google Chrome extension or packaged app\",\n            signature: {\n                0: 0x43,\n                1: 0x72,\n                2: 0x32,\n                3: 0x34\n            },\n            extractor: null\n        },\n    ],\n    \"Archives\": [\n        {\n            name: \"PKZIP archive\",\n            extension: \"zip\",\n            mime: \"application/zip\",\n            description: \"\",\n            signature: {\n                0: 0x50,\n                1: 0x4b,\n                2: [0x3, 0x5, 0x7],\n                3: [0x4, 0x6, 0x8]\n            },\n            extractor: extractZIP\n        },\n        {\n            name: \"PKZIP archive (under Base64)\",\n            extension: \"B64\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x55,\n                1: 0x45,\n                2: 0x73,\n                3: 0x44,\n                4: 0x42,\n                5: 0x42\n            },\n            extractor: null\n        },\n        {\n            name: \"TAR archive\",\n            extension: \"tar\",\n            mime: \"application/x-tar\",\n            description: \"\",\n            signature: {\n                257: 0x75, // ustar\n                258: 0x73,\n                259: 0x74,\n                260: 0x61,\n                261: 0x72\n            },\n            extractor: extractTAR\n        },\n        {\n            name: \"Roshal Archive\",\n            extension: \"rar\",\n            mime: \"application/x-rar-compressed\",\n            description: \"\",\n            signature: {\n                0: 0x52,\n                1: 0x61,\n                2: 0x72,\n                3: 0x21,\n                4: 0x1a,\n                5: 0x7,\n                6: [0x0, 0x1]\n            },\n            extractor: null\n        },\n        {\n            name: \"Gzip\",\n            extension: \"gz\",\n            mime: \"application/gzip\",\n            description: \"\",\n            signature: {\n                0: 0x1f,\n                1: 0x8b,\n                2: 0x8\n            },\n            extractor: extractGZIP\n        },\n        {\n            name: \"Bzip2\",\n            extension: \"bz2\",\n            mime: \"application/x-bzip2\",\n            description: \"\",\n            signature: {\n                0: 0x42,\n                1: 0x5a,\n                2: 0x68\n            },\n            extractor: extractBZIP2\n        },\n        {\n            name: \"7zip\",\n            extension: \"7z\",\n            mime: \"application/x-7z-compressed\",\n            description: \"\",\n            signature: {\n                0: 0x37,\n                1: 0x7a,\n                2: 0xbc,\n                3: 0xaf,\n                4: 0x27,\n                5: 0x1c\n            },\n            extractor: null\n        },\n        {\n            name: \"Zlib Deflate\",\n            extension: \"zlib\",\n            mime: \"application/x-deflate\",\n            description: \"\",\n            signature: {\n                0: 0x78,\n                1: [0x1, 0x9c, 0xda, 0x5e]\n            },\n            extractor: extractZlib\n        },\n        {\n            name: \"xz compression\",\n            extension: \"xz\",\n            mime: \"application/x-xz\",\n            description: \"\",\n            signature: {\n                0: 0xfd,\n                1: 0x37,\n                2: 0x7a,\n                3: 0x58,\n                4: 0x5a,\n                5: 0x0\n            },\n            extractor: extractXZ\n        },\n        {\n            name: \"Tarball\",\n            extension: \"tar.z\",\n            mime: \"application/x-gtar\",\n            description: \"\",\n            signature: {\n                0: 0x1f,\n                1: [0x9d, 0xa0]\n            },\n            extractor: null\n        },\n        {\n            name: \"ISO disk image\",\n            extension: \"iso\",\n            mime: \"application/octet-stream\",\n            description: \"ISO 9660 CD/DVD image file\",\n            signature: [\n                {\n                    0x8001: 0x43,\n                    0x8002: 0x44,\n                    0x8003: 0x30,\n                    0x8004: 0x30,\n                    0x8005: 0x31\n                },\n                {\n                    0x8801: 0x43,\n                    0x8802: 0x44,\n                    0x8803: 0x30,\n                    0x8804: 0x30,\n                    0x8805: 0x31\n                },\n                {\n                    0x9001: 0x43,\n                    0x9002: 0x44,\n                    0x9003: 0x30,\n                    0x9004: 0x30,\n                    0x9005: 0x31\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"Virtual Machine Disk\",\n            extension: \"vmdk\",\n            mime: \"application/vmdk,application/x-virtualbox-vmdk\",\n            description: \"\",\n            signature: {\n                0: 0x4b,\n                1: 0x44,\n                2: 0x4d,\n                3: 0x56,\n                5: 0x00,\n                6: 0x00,\n                7: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Virtual Hard Drive\",\n            extension: \"vhd\",\n            mime: \"application/x-vhd\",\n            description: \"\",\n            signature: {\n                0: 0x63, // conectix\n                1: 0x6f,\n                2: 0x6e,\n                3: 0x65,\n                4: 0x63,\n                5: 0x74,\n                6: 0x69,\n                7: 0x78\n            },\n            extractor: null\n        },\n        {\n            name: \"Macintosh disk image\",\n            extension: \"dmf,dmg\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x78,\n                1: 0x01,\n                2: 0x73,\n                3: 0x0d,\n                4: 0x62,\n                5: 0x62,\n                6: 0x60,\n                7: 0x60\n            },\n            extractor: null\n        },\n        {\n            name: \"ARJ Archive\",\n            extension: \"arj\",\n            mime: \"application/x-arj-compressed\",\n            description: \"\",\n            signature: {\n                0: 0x60,\n                1: 0xea,\n                8: [0x0, 0x10, 0x14],\n                9: 0x0,\n                10: 0x2\n            },\n            extractor: null\n        },\n        {\n            name: \"WinAce Archive\",\n            extension: \"ace\",\n            mime: \"application/x-ace-compressed\",\n            description: \"\",\n            signature: {\n                7: 0x2a, // **ACE**\n                8: 0x2a,\n                9: 0x41,\n                10: 0x43,\n                11: 0x45,\n                12: 0x2a,\n                13: 0x2a\n            },\n            extractor: null\n        },\n        {\n            name: \"Macintosh BinHex Encoded File\",\n            extension: \"hqx\",\n            mime: \"application/mac-binhex\",\n            description: \"\",\n            signature: {\n                11: 0x6d,  // must be converted with BinHex\n                12: 0x75,\n                13: 0x73,\n                14: 0x74,\n                15: 0x20,\n                16: 0x62,\n                17: 0x65,\n                18: 0x20,\n                19: 0x63,\n                20: 0x6f,\n                21: 0x6e,\n                22: 0x76,\n                23: 0x65,\n                24: 0x72,\n                25: 0x74,\n                26: 0x65,\n                27: 0x64,\n                28: 0x20,\n                29: 0x77,\n                30: 0x69,\n                31: 0x74,\n                32: 0x68,\n                33: 0x20,\n                34: 0x42,\n                35: 0x69,\n                36: 0x6e,\n                37: 0x48,\n                38: 0x65,\n                39: 0x78\n            },\n            extractor: null\n        },\n        {\n            name: \"ALZip Archive\",\n            extension: \"alz\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x41, // ALZ\n                1: 0x4c,\n                2: 0x5a,\n                3: 0x01,\n                4: 0x0a,\n                5: 0x0,\n                6: 0x0,\n                7: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"KGB Compressed Archive\",\n            extension: \"kgb\",\n            mime: \"application/x-kgb-compressed\",\n            description: \"\",\n            signature: {\n                0: 0x4b, // KGB_arch -\n                1: 0x47,\n                2: 0x42,\n                3: 0x5f,\n                4: 0x61,\n                5: 0x72,\n                6: 0x63,\n                7: 0x68,\n                8: 0x20,\n                9: 0x2d\n            },\n            extractor: null\n        },\n        {\n            name: \"Microsoft Cabinet\",\n            extension: \"cab\",\n            mime: \"vnd.ms-cab-compressed\",\n            description: \"\",\n            signature: {\n                0: 0x4d,\n                1: 0x53,\n                2: 0x43,\n                3: 0x46,\n                4: 0x00,\n                5: 0x00,\n                6: 0x00,\n                7: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Jar Archive\",\n            extension: \"jar\",\n            mime: \"application/java-archive\",\n            description: \"\",\n            signature: {\n                0: 0x5f,\n                1: 0x27,\n                2: 0xa8,\n                3: 0x89\n            },\n            extractor: null\n        },\n        {\n            name: \"Jar Archive\",\n            extension: \"jar\",\n            mime: \"application/java-archive\",\n            description: \"\",\n            signature: {\n                0: 0x50,\n                1: 0x4B,\n                2: 0x03,\n                3: 0x04,\n                4: 0x14,\n                5: 0x00,\n                6: 0x08,\n                7: 0x00,\n                8: 0x08,\n                9: 0x00\n            },\n            extractor: extractZIP\n        },\n        {\n            name: \"lzop compressed\",\n            extension: \"lzop,lzo\",\n            mime: \"application/x-lzop\",\n            description: \"\",\n            signature: {\n                0: 0x89,\n                1: 0x4c, // LZO\n                2: 0x5a,\n                3: 0x4f,\n                4: 0x00,\n                5: 0x0d,\n                6: 0x0a,\n                7: 0x1a\n            },\n            extractor: extractLZOP\n        },\n        {\n            name: \"Linux deb package\",\n            extension: \"deb\",\n            mime: \"application/vnd.debian.binary-package\",\n            description: \"\",\n            signature: {\n                0: 0x21,\n                1: 0x3C,\n                2: 0x61,\n                3: 0x72,\n                4: 0x63,\n                5: 0x68,\n                6: 0x3e\n            },\n            extractor: extractDEB\n        },\n        {\n            name: \"Apple Disk Image\",\n            extension: \"dmg\",\n            mime: \"application/x-apple-diskimage\",\n            description: \"\",\n            signature: {\n                0: 0x78,\n                1: 0x01,\n                2: 0x73,\n                3: 0x0d,\n                4: 0x62,\n                5: 0x62,\n                6: 0x60\n            },\n            extractor: null\n        }\n    ],\n    \"Miscellaneous\": [\n        {\n            name: \"UTF-8 text\",\n            extension: \"txt\",\n            mime: \"text/plain\",\n            description: \"UTF-8 encoded Unicode byte order mark, commonly but not exclusively seen in text files.\",\n            signature: {\n                0: 0xef,\n                1: 0xbb,\n                2: 0xbf\n            },\n            extractor: null\n        },\n        { // Place before UTF-16 LE text\n            name: \"UTF-32 LE text\",\n            extension: \"utf32le\",\n            mime: \"charset/utf32le\",\n            description: \"Little-endian UTF-32 encoded Unicode byte order mark.\",\n            signature: {\n                0: 0xff,\n                1: 0xfe,\n                2: 0x00,\n                3: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"UTF-16 LE text\",\n            extension: \"utf16le\",\n            mime: \"charset/utf16le\",\n            description: \"Little-endian UTF-16 encoded Unicode byte order mark.\",\n            signature: {\n                0: 0xff,\n                1: 0xfe\n            },\n            extractor: null\n        },\n        {\n            name: \"Web Open Font Format\",\n            extension: \"woff\",\n            mime: \"application/font-woff\",\n            description: \"\",\n            signature: {\n                0: 0x77,\n                1: 0x4f,\n                2: 0x46,\n                3: 0x46,\n                4: 0x0,\n                5: 0x1,\n                6: 0x0,\n                7: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"Web Open Font Format 2\",\n            extension: \"woff2\",\n            mime: \"application/font-woff\",\n            description: \"\",\n            signature: {\n                0: 0x77,\n                1: 0x4f,\n                2: 0x46,\n                3: 0x32,\n                4: 0x0,\n                5: 0x1,\n                6: 0x0,\n                7: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"Embedded OpenType font\",\n            extension: \"eot\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: [\n                {\n                    8: 0x2,\n                    9: 0x0,\n                    10: 0x1,\n                    34: 0x4c,\n                    35: 0x50\n                },\n                {\n                    8: 0x1,\n                    9: 0x0,\n                    10: 0x0,\n                    34: 0x4c,\n                    35: 0x50\n                },\n                {\n                    8: 0x2,\n                    9: 0x0,\n                    10: 0x2,\n                    34: 0x4c,\n                    35: 0x50\n                },\n            ],\n            extractor: null\n        },\n        {\n            name: \"TrueType Font\",\n            extension: \"ttf\",\n            mime: \"application/font-sfnt\",\n            description: \"\",\n            signature: {\n                0: 0x0,\n                1: 0x1,\n                2: 0x0,\n                3: 0x0,\n                4: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"OpenType Font\",\n            extension: \"otf\",\n            mime: \"application/font-sfnt\",\n            description: \"\",\n            signature: {\n                0: 0x4f,\n                1: 0x54,\n                2: 0x54,\n                3: 0x4f,\n                4: 0x0\n            },\n            extractor: null\n        },\n        {\n            name: \"SQLite\",\n            extension: \"sqlite\",\n            mime: \"application/x-sqlite3\",\n            description: \"\",\n            signature: {\n                0: 0x53,\n                1: 0x51,\n                2: 0x4c,\n                3: 0x69\n            },\n            extractor: extractSQLITE\n        },\n        {\n            name: \"BitTorrent link\",\n            extension: \"torrent\",\n            mime: \"application/x-bittorrent\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x64, // d8:announce##:\n                    1: 0x38,\n                    2: 0x3a,\n                    3: 0x61,\n                    4: 0x6e,\n                    5: 0x6e,\n                    6: 0x6f,\n                    7: 0x75,\n                    8: 0x6e,\n                    9: 0x63,\n                    10: 0x65,\n                    11: 0x23,\n                    12: 0x23,\n                    13: 0x3a\n                },\n                {\n                    0: 0x64, // d4:infod\n                    1: 0x34,\n                    2: 0x3a,\n                    3: 0x69,\n                    4: 0x6e,\n                    5: 0x66,\n                    6: 0x6f,\n                    7: 0x64,\n                    8: [0x34, 0x35, 0x36],\n                    9: 0x3a\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"Cryptocurrency wallet\",\n            extension: \"wallet\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x00,\n                1: 0x00,\n                2: 0x00,\n                3: 0x00,\n                4: 0x01,\n                5: 0x00,\n                6: 0x00,\n                7: 0x00,\n                8: 0x00,\n                9: 0x00,\n                10: 0x00,\n                11: 0x00,\n                12: 0x62,\n                13: 0x31,\n                14: 0x05,\n                15: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Registry fragment\",\n            extension: \"hbin\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x68, // hbin\n                1: 0x62,\n                2: 0x69,\n                3: 0x6e,\n                4: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Registry script\",\n            extension: \"rgs\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x48, // HKCR\n                1: 0x4b,\n                2: 0x43,\n                3: 0x52,\n                4: 0x0d,\n                5: 0x0a,\n                6: 0x5c,\n                7: 0x7b\n            },\n            extractor: null\n        },\n        {\n            name: \"WinNT Registry Hive\",\n            extension: \"registry\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x72,\n                1: 0x65,\n                2: 0x67,\n                3: 0x66\n            },\n            extractor: null\n        },\n        {\n            name: \"Windows Event Log\",\n            extension: \"evt\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x30,\n                1: 0x00,\n                2: 0x00,\n                3: 0x00,\n                4: 0x4c,\n                5: 0x66,\n                6: 0x4c,\n                7: 0x65\n            },\n            extractor: extractEVT\n        },\n        {\n            name: \"Windows Event Log\",\n            extension: \"evtx\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x45, // ElfFile\n                1: 0x6c,\n                2: 0x66,\n                3: 0x46,\n                4: 0x69,\n                5: 0x6c,\n                6: 0x65\n            },\n            extractor: extractEVTX\n        },\n        {\n            name: \"Windows Pagedump\",\n            extension: \"dmp\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x50, // PAGEDU(MP|64)\n                1: 0x41,\n                2: 0x47,\n                3: 0x45,\n                4: 0x44,\n                5: 0x55,\n                6: [0x4d, 0x36],\n                7: [0x50, 0x34]\n            },\n            extractor: extractDMP\n        },\n        {\n            name: \"Windows Prefetch\",\n            extension: \"pf\",\n            mime: \"application/x-pf\",\n            description: \"\",\n            signature: {\n                0: [0x11, 0x17, 0x1a],\n                1: 0x0,\n                2: 0x0,\n                3: 0x0,\n                4: 0x53,\n                5: 0x43,\n                6: 0x43,\n                7: 0x41\n            },\n            extractor: extractPF\n        },\n        {\n            name: \"Windows Prefetch (Win 10)\",\n            extension: \"pf\",\n            mime: \"application/x-pf\",\n            description: \"\",\n            signature: {\n                0: 0x4d,\n                1: 0x41,\n                2: 0x4d,\n                3: 0x04,\n                7: 0x0\n            },\n            extractor: extractPFWin10\n        },\n        {\n            name: \"PList (XML)\",\n            extension: \"plist\",\n            mime: \"application/xml\",\n            description: \"\",\n            signature: {\n                39: 0x3c, // <!DOCTYPE plist\n                40: 0x21,\n                41: 0x44,\n                42: 0x4f,\n                43: 0x43,\n                44: 0x54,\n                45: 0x59,\n                46: 0x50,\n                47: 0x45,\n                48: 0x20,\n                49: 0x70,\n                50: 0x6c,\n                51: 0x69,\n                52: 0x73,\n                53: 0x74\n            },\n            extractor: extractPListXML\n        },\n        {\n            name: \"PList (binary)\",\n            extension: \"bplist,plist,ipmeta,abcdp,mdbackup,mdinfo,strings,nib,ichat,qtz,webbookmark,webhistory\",\n            mime: \"application/x-plist\",\n            description: \"\",\n            signature: {\n                0: 0x62, // bplist00\n                1: 0x70,\n                2: 0x6c,\n                3: 0x69,\n                4: 0x73,\n                5: 0x74,\n                6: 0x30,\n                7: 0x30\n            },\n            extractor: null\n        },\n        {\n            name: \"MacOS X Keychain\",\n            extension: \"keychain\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x6b, // kych\n                1: 0x79,\n                2: 0x63,\n                3: 0x68,\n                4: 0x00,\n                5: 0x01\n            },\n            extractor: extractMacOSXKeychain\n        },\n        {\n            name: \"TCP Packet\",\n            extension: \"tcp\",\n            mime: \"application/tcp\",\n            description: \"\",\n            signature: {\n                12: 0x08,\n                13: 0x00,\n                14: 0x45,\n                15: 0x00,\n                21: 0x00,\n                22: b => b >= 0x01 && b <= 0x80,\n                23: 0x06\n            },\n            extractor: null\n        },\n        {\n            name: \"UDP Packet\",\n            extension: \"udp\",\n            mime: \"application/udp\",\n            description: \"\",\n            signature: {\n                12: 0x08,\n                13: 0x00,\n                14: 0x45,\n                15: 0x00,\n                16: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05],\n                22: b => b >= 0x01 && b <= 0x80,\n                23: 0x11\n            },\n            extractor: null\n        },\n        {\n            name: \"Compiled HTML\",\n            extension: \"chm,chw,chi\",\n            mime: \"application/vnd.ms-htmlhelp\",\n            description: \"\",\n            signature: {\n                0: 0x49, // ITSF\n                1: 0x54,\n                2: 0x53,\n                3: 0x46,\n                4: 0x03,\n                5: 0x00,\n                6: 0x00,\n                7: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Windows Password\",\n            extension: \"pwl\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0xe3,\n                1: 0x82,\n                2: 0x85,\n                3: 0x96\n            },\n            extractor: null\n        },\n        {\n            name: \"Bitlocker recovery key\",\n            extension: \"bitlocker\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0xff,\n                1: 0xfe,\n                2: 0x42,\n                3: 0x00,\n                4: 0x69,\n                5: 0x00,\n                6: 0x74,\n                7: 0x00,\n                8: 0x4c,\n                9: 0x00,\n                10: 0x6f,\n                11: 0x00,\n                12: 0x63,\n                13: 0x00,\n                14: 0x6b,\n                15: 0x00,\n                16: 0x65,\n                17: 0x00,\n                18: 0x72,\n                19: 0x00,\n                20: 0x20,\n                21: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Certificate\",\n            extension: \"cer,cat,p7b,p7c,p7m,p7s,swz,rsa,crl,crt,der\",\n            mime: \"application/pkix-cert\",\n            description: \"\",\n            signature: {\n                0: 0x30,\n                1: 0x82,\n                4: [0x06, 0x0a, 0x30]\n            },\n            extractor: null\n        },\n        {\n            name: \"Certificate\",\n            extension: \"cat,swz,p7m\",\n            mime: \"application/vnd.ms-pki.seccat\",\n            description: \"\",\n            signature: {\n                0: 0x30,\n                1: 0x83,\n                2: b => b !== 0x00,\n                5: 0x06,\n                6: 0x09\n            },\n            extractor: null\n        },\n        {\n            name: \"PGP pubring\",\n            extension: \"pkr,gpg\",\n            mime: \"application/pgp-keys\",\n            description: \"\",\n            signature: {\n                0: 0x99,\n                1: 0x01,\n                2: [0x0d, 0xa2],\n                3: 0x04\n            },\n            extractor: null\n        },\n        {\n            name: \"PGP secring\",\n            extension: \"skr\",\n            mime: \"application/pgp-keys\",\n            description: \"\",\n            signature: [\n                {\n                    0: 0x95,\n                    1: 0x01,\n                    2: 0xcf,\n                    3: 0x04\n                },\n                {\n                    0: 0x95,\n                    1: 0x03,\n                    2: 0xc6,\n                    3: 0x04\n                },\n                {\n                    0: 0x95,\n                    1: 0x05,\n                    2: 0x86,\n                    3: 0x04\n                }\n            ],\n            extractor: null\n        },\n        {\n            name: \"PGP Safe\",\n            extension: \"pgd\",\n            mime: \"application/pgp-keys\",\n            description: \"\",\n            signature: {\n                0: 0x50, // PGPdMAIN\n                1: 0x47,\n                2: 0x50,\n                3: 0x64,\n                4: 0x4d,\n                5: 0x41,\n                6: 0x49,\n                7: 0x4e,\n                8: 0x60,\n                9: 0x01,\n                10: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Task Scheduler\",\n            extension: \"job\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: [0x00, 0x01, 0x02, 0x03],\n                1: [0x05, 0x06],\n                2: 0x01,\n                3: 0x00,\n                20: 0x46,\n                21: 0x00\n            },\n            extractor: null\n        },\n        {\n            name: \"Windows Shortcut\",\n            extension: \"lnk\",\n            mime: \"application/x-ms-shortcut\",\n            description: \"\",\n            signature: {\n                0: 0x4c,\n                1: 0x00,\n                2: 0x00,\n                3: 0x00,\n                4: 0x01,\n                5: 0x14,\n                6: 0x02,\n                7: 0x00,\n                8: 0x00,\n                9: 0x00,\n                10: 0x00,\n                11: 0x00,\n                12: 0xc0,\n                13: 0x00,\n                14: 0x00,\n                15: 0x00,\n                16: 0x00,\n                17: 0x00,\n                18: 0x00,\n                19: 0x46\n            },\n            extractor: extractLNK\n        },\n        {\n            name: \"Bash\",\n            extension: \"bash\",\n            mime: \"application/bash\",\n            description: \"\",\n            signature: {\n                0: 0x23, // #!/bin/bash\n                1: 0x21,\n                2: 0x2f,\n                3: 0x62,\n                4: 0x69,\n                5: 0x6e,\n                6: 0x2f,\n                7: 0x62,\n                8: 0x61,\n                9: 0x73,\n                10: 0x68,\n            },\n            extractor: null\n        },\n        {\n            name: \"Shell\",\n            extension: \"sh\",\n            mime: \"application/sh\",\n            description: \"\",\n            signature: {\n                0: 0x23, // #!/bin/sh\n                1: 0x21,\n                2: 0x2f,\n                3: 0x62,\n                4: 0x69,\n                5: 0x6e,\n                6: 0x2f,\n                7: 0x73,\n                8: 0x68,\n            },\n            extractor: null\n        },\n        {\n            name: \"Python\",\n            extension: \"py,pyc,pyd,pyo,pyw,pyz\",\n            mime: \"application/python\",\n            description: \"\",\n            signature: {\n                0: 0x23, // #!/usr/bin/python(2|3)\n                1: 0x21,\n                2: 0x2f,\n                3: 0x75,\n                4: 0x73,\n                5: 0x72,\n                6: 0x2f,\n                7: 0x62,\n                8: 0x69,\n                9: 0x6e,\n                10: 0x2f,\n                11: 0x70,\n                12: 0x79,\n                13: 0x74,\n                14: 0x68,\n                15: 0x6f,\n                16: 0x6e,\n                17: [0x32, 0x33, 0xa, 0xd],\n            },\n            extractor: null\n        },\n        {\n            name: \"Ruby\",\n            extension: \"rb\",\n            mime: \"application/ruby\",\n            description: \"\",\n            signature: {\n                0: 0x23, // #!/usr/bin/ruby\n                1: 0x21,\n                2: 0x2f,\n                3: 0x75,\n                4: 0x73,\n                5: 0x72,\n                6: 0x2f,\n                7: 0x62,\n                8: 0x69,\n                9: 0x6e,\n                10: 0x2f,\n                11: 0x72,\n                12: 0x75,\n                13: 0x62,\n                14: 0x79,\n            },\n            extractor: null\n        },\n        {\n            name: \"perl\",\n            extension: \"pl,pm,t,pod\",\n            mime: \"application/perl\",\n            description: \"\",\n            signature: {\n                0: 0x23, // #!/usr/bin/perl\n                1: 0x21,\n                2: 0x2f,\n                3: 0x75,\n                4: 0x73,\n                5: 0x72,\n                6: 0x2f,\n                7: 0x62,\n                8: 0x69,\n                9: 0x6e,\n                10: 0x2f,\n                11: 0x70,\n                12: 0x65,\n                13: 0x72,\n                14: 0x6c,\n            },\n            extractor: null\n        },\n        {\n            name: \"php\",\n            extension: \"php,phtml,php3,php4,php5,php7,phps,php-s,pht,phar\",\n            mime: \"application/php\",\n            description: \"\",\n            signature: {\n                0: 0x3c, // <?php\n                1: 0x3f,\n                2: 0x70,\n                3: 0x68,\n                4: 0x70,\n            },\n            extractor: null\n        },\n        {\n            name: \"Smile\",\n            extension: \"sml\",\n            mime: \"\tapplication/x-jackson-smile\",\n            description: \"\",\n            signature: {\n                0: 0x3a,\n                1: 0x29,\n                2: 0xa\n            },\n            extractor: null\n        },\n        {\n            name: \"Lua Bytecode\",\n            extension: \"luac\",\n            mime: \"application/x-lua\",\n            description: \"\",\n            signature: {\n                0: 0x1b,\n                1: 0x4c,\n                2: 0x75,\n                3: 0x61\n            },\n            extractor: null\n        },\n        {\n            name: \"WebAssembly binary\",\n            extension: \"wasm\",\n            mime: \"application/octet-stream\",\n            description: \"\",\n            signature: {\n                0: 0x00,\n                1: 0x61,\n                2: 0x73,\n                3: 0x6d\n            },\n            extractor: null\n        }\n    ]\n};\n\n\n/**\n * JPEG extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractJPEG(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    while (stream.hasMore()) {\n        const marker = stream.getBytes(2);\n        if (marker[0] !== 0xff) throw new Error(`Invalid marker while parsing JPEG at pos ${stream.position}: ${marker}`);\n\n        let segmentSize = 0;\n        switch (marker[1]) {\n            // No length\n            case 0xd8: // Start of Image\n            case 0x01: // For temporary use in arithmetic coding\n                break;\n            case 0xd9: // End found\n                return stream.carve();\n\n            // Variable size segment\n            case 0xc0: // Start of frame (Baseline DCT)\n            case 0xc1: // Start of frame (Extended sequential DCT)\n            case 0xc2: // Start of frame (Progressive DCT)\n            case 0xc3: // Start of frame (Lossless sequential)\n            case 0xc4: // Define Huffman Table\n            case 0xc5: // Start of frame (Differential sequential DCT)\n            case 0xc6: // Start of frame (Differential progressive DCT)\n            case 0xc7: // Start of frame (Differential lossless)\n            case 0xc8: // Reserved for JPEG extensions\n            case 0xc9: // Start of frame (Extended sequential DCT)\n            case 0xca: // Start of frame (Progressive DCT)\n            case 0xcb: // Start of frame (Lossless sequential)\n            case 0xcc: // Define arithmetic conditioning table\n            case 0xcd: // Start of frame (Differential sequential DCT)\n            case 0xce: // Start of frame (Differential progressive DCT)\n            case 0xcf: // Start of frame (Differential lossless)\n            case 0xdb: // Define Quantization Table\n            case 0xde: // Define hierarchical progression\n            case 0xe0: // Application-specific\n            case 0xe1: // Application-specific\n            case 0xe2: // Application-specific\n            case 0xe3: // Application-specific\n            case 0xe4: // Application-specific\n            case 0xe5: // Application-specific\n            case 0xe6: // Application-specific\n            case 0xe7: // Application-specific\n            case 0xe8: // Application-specific\n            case 0xe9: // Application-specific\n            case 0xea: // Application-specific\n            case 0xeb: // Application-specific\n            case 0xec: // Application-specific\n            case 0xed: // Application-specific\n            case 0xee: // Application-specific\n            case 0xef: // Application-specific\n            case 0xfe: // Comment\n                segmentSize = stream.readInt(2, \"be\");\n                stream.position += segmentSize - 2;\n                break;\n\n            // 1 byte\n            case 0xdf: // Expand reference image\n                stream.position++;\n                break;\n\n            // 2 bytes\n            case 0xdc: // Define number of lines\n            case 0xdd: // Define restart interval\n                stream.position += 2;\n                break;\n\n            // Start scan\n            case 0xda: // Start of scan\n                segmentSize = stream.readInt(2, \"be\");\n                stream.position += segmentSize - 2;\n                stream.continueUntil(0xff);\n                break;\n\n            // Continue through encoded data\n            case 0x00: // Byte stuffing\n            case 0xd0: // Restart\n            case 0xd1: // Restart\n            case 0xd2: // Restart\n            case 0xd3: // Restart\n            case 0xd4: // Restart\n            case 0xd5: // Restart\n            case 0xd6: // Restart\n            case 0xd7: // Restart\n                stream.continueUntil(0xff);\n                break;\n\n            default:\n                stream.continueUntil(0xff);\n                break;\n        }\n    }\n\n    throw new Error(\"Unable to parse JPEG successfully\");\n}\n\n\n/**\n * GIF extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractGIF(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to application extension block.\n    stream.continueUntil([0x21, 0xff]);\n\n    // Move to Graphic Control Extension for frame #1.\n    stream.continueUntil([0x21, 0xf9]);\n    stream.moveForwardsBy(2);\n\n    while (stream.hasMore()) {\n        // Move to Image descriptor.\n        stream.moveForwardsBy(stream.readInt(1) + 1);\n\n        // Move past Image descriptor to the image data.\n        stream.moveForwardsBy(11);\n\n        // Loop until next Graphic Control Extension.\n        while (!Array.from(stream.getBytes(2)).equals([0x21, 0xf9])) {\n            stream.moveBackwardsBy(2);\n            stream.moveForwardsBy(stream.readInt(1));\n            if (!stream.readInt(1))\n                break;\n            stream.moveBackwardsBy(1);\n        }\n\n        // When the end of the file is [0x00, 0x3b], end.\n        if (stream.readInt(1) === 0x3b)\n            break;\n\n        stream.moveForwardsBy(1);\n    }\n    return stream.carve();\n}\n\n\n/**\n * Portable executable extractor.\n * Assumes that the offset refers to an MZ header.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractMZPE(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Read pointer to PE header\n    stream.moveTo(0x3c);\n    const peAddress = stream.readInt(4, \"le\");\n\n    // Move to PE header\n    stream.moveTo(peAddress);\n\n    // Get number of sections\n    stream.moveForwardsBy(6);\n    const numSections = stream.readInt(2, \"le\");\n\n    // Read Optional Header Magic to determine the state of the image file\n    // 0x10b = normal executable, 0x107 = ROM image, 0x20b = PE32+ executable\n    stream.moveForwardsBy(16);\n    const optionalMagic = stream.readInt(2, \"le\");\n    const pe32Plus = optionalMagic === 0x20b;\n\n    // Move to Data Directory\n    const dataDirectoryOffset = pe32Plus ? 112 : 96;\n    stream.moveForwardsBy(dataDirectoryOffset - 2);\n\n    // Read Certificate Table address and size (IMAGE_DIRECTORY_ENTRY_SECURITY)\n    stream.moveForwardsBy(32);\n    const certTableAddress = stream.readInt(4, \"le\");\n    const certTableSize = stream.readInt(4, \"le\");\n\n    // PE files can contain extra data appended to the end of the file called an \"overlay\".\n    // This data is not covered by the PE header and could be any arbitrary format, so its\n    // length cannot be determined without contextual information.\n    // However, the Attribute Certificate Table is stored in the overlay - usually right at\n    // the end. Therefore, if this table is defined, we can use its offset and size to carve\n    // out the entire PE file, including the overlay.\n    // If the Certificate Table is not defined, we continue to parse the PE file as best we\n    // can up to the end of the final section, not including any appended data in the overlay.\n    if (certTableAddress > 0) {\n        stream.moveTo(certTableAddress + certTableSize);\n        return stream.carve();\n    }\n\n    // Move past Optional Header to Section Header\n    stream.moveForwardsBy(88);\n\n    // Move to final section header\n    stream.moveForwardsBy((numSections - 1) * 0x28);\n\n    // Get raw data info\n    stream.moveForwardsBy(16);\n    const rawDataSize = stream.readInt(4, \"le\");\n    const rawDataAddress = stream.readInt(4, \"le\");\n\n    // Move to end of final section\n    stream.moveTo(rawDataAddress + rawDataSize);\n\n    return stream.carve();\n}\n\n\n/**\n * PDF extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractPDF(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Find end-of-file marker (%%EOF)\n    stream.continueUntil([0x25, 0x25, 0x45, 0x4f, 0x46]);\n    stream.moveForwardsBy(5);\n    stream.consumeIf(0x0d);\n    stream.consumeIf(0x0a);\n\n    return stream.carve();\n}\n\n\n/**\n * ZIP extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractZIP(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Find End of central directory record\n    stream.continueUntil([0x50, 0x4b, 0x05, 0x06]);\n\n    // Get comment length and consume\n    stream.moveForwardsBy(20);\n    const commentLength = stream.readInt(2, \"le\");\n    stream.moveForwardsBy(commentLength);\n\n    return stream.carve();\n}\n\n\n/**\n * MACHO extractor\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractMACHO(bytes, offset) {\n\n    // Magic bytes.\n    const MHCIGAM64 = \"207250237254\";\n    const MHMAGIC64 = \"254237250207\";\n    const MHCIGAM = \"206250237254\";\n\n\n    /**\n     * Checks to see if the file is 64-bit.\n     *\n     * @param {string} magic\n     * @returns {bool}\n     */\n    function isMagic64(magic) {\n        return magic === MHCIGAM64 || magic === MHMAGIC64;\n    }\n\n\n    /**\n     * Checks the endianness of the file.\n     *\n     * @param {string} magic\n     * @returns {bool}\n     */\n    function shouldSwapBytes(magic) {\n        return magic === MHCIGAM || magic === MHCIGAM64;\n    }\n\n\n    /**\n     * Jumps through segment information and calculates the sum of the segement sizes.\n     *\n     * @param {Stream} stream\n     * @param {number} offset\n     * @param {string} isSwap\n     * @param {number} ncmds\n     * @returns {number}\n     */\n    function dumpSegmentCommands(stream, offset, isSwap, ncmds) {\n        let total = 0;\n        const LCSEGEMENT64 = 0x19;\n        const LCSEGEMENT = 0x1;\n\n        for (let i = 0; i < ncmds; i++) {\n\n            // Move to start of segment.\n            stream.moveTo(offset);\n            const cmd = stream.readInt(4, isSwap);\n            if (cmd === LCSEGEMENT64) {\n\n                // Move to size of segment field.\n                stream.moveTo(offset + 48);\n\n                // Extract size of segement.\n                total += stream.readInt(8, isSwap);\n                stream.moveTo(offset + 4);\n\n                // Move to offset of next segment.\n                offset += stream.readInt(4, isSwap);\n            } else if (cmd === LCSEGEMENT) {\n                stream.moveTo(offset + 36);\n\n                // Extract size of segement.\n                total += stream.readInt(4, isSwap);\n                stream.moveTo(offset + 4);\n                offset += stream.readInt(4, isSwap);\n            }\n        }\n        return total;\n    }\n\n\n    /**\n     * Reads the number of command segments.\n     *\n     * @param {Stream} stream\n     * @param {bool} is64\n     * @param {string} isSwap\n     * @returns {number}\n     */\n    function dumpMachHeader(stream, is64, isSwap) {\n        let loadCommandsOffset = 28;\n        if (is64)\n            loadCommandsOffset += 4;\n\n        // Move to number of commands field.\n        stream.moveTo(16);\n        const ncmds = stream.readInt(4, isSwap);\n        return dumpSegmentCommands(stream, loadCommandsOffset, isSwap, ncmds);\n    }\n\n\n    const stream = new Stream(bytes.slice(offset));\n    const magic = stream.getBytes(4).join(\"\");\n\n    // Move to the end of the final segment.\n    stream.moveTo(dumpMachHeader(stream, isMagic64(magic), shouldSwapBytes(magic) ? \"le\" : \"be\"));\n    return stream.carve();\n}\n\n\n/**\n * TAR extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractTAR(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n    while (stream.hasMore()) {\n\n        // Move to ustar identifier.\n        stream.moveForwardsBy(0x101);\n        if (stream.getBytes(5).join(\"\") !== [0x75, 0x73, 0x74, 0x61, 0x72].join(\"\")) {\n            // Reverse back to the end of the last section.\n            stream.moveBackwardsBy(0x106);\n            break;\n        }\n\n        // Move back to file size field.\n        stream.moveBackwardsBy(0x8a);\n        let fsize = 0;\n\n        // Read file size field.\n        stream.getBytes(11).forEach((element, index) => {\n            fsize += (element - 48).toString();\n        });\n\n        // Round number up from octet to nearest 512.\n        fsize = (Math.ceil(parseInt(fsize, 8) / 512) * 512);\n\n        // Move forwards to the end of that file.\n        stream.moveForwardsBy(fsize + 0x179);\n    }\n    stream.consumeWhile(0x00);\n    return stream.carve();\n}\n\n\n/**\n * PNG extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractPNG(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move past signature to first chunk\n    stream.moveForwardsBy(8);\n\n    let chunkSize = 0,\n        chunkType = \"\";\n\n    while (chunkType !== \"IEND\") {\n        chunkSize = stream.readInt(4, \"be\");\n        chunkType = stream.readString(4);\n\n        // Chunk data size + CRC checksum\n        stream.moveForwardsBy(chunkSize + 4);\n    }\n\n\n    return stream.carve();\n}\n\n\n/**\n * WEBP extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractWEBP(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to file size offset.\n    stream.moveForwardsBy(4);\n\n    // Read file size field.\n    const fileSize = stream.readInt(4, \"le\");\n\n    // Move to end of file.\n    // There is no need to minus 8 from the size as the size factors in the offset.\n    stream.moveForwardsBy(fileSize);\n\n    return stream.carve();\n}\n\n\n/**\n * BMP extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractBMP(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move past header\n    stream.moveForwardsBy(2);\n\n    // Read full file size\n    const bmpSize = stream.readInt(4, \"le\");\n\n    // Move to end of file (file size minus header and size field)\n    stream.moveForwardsBy(bmpSize - 6);\n\n    return stream.carve();\n}\n\n\n/**\n * ICO extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n */\nexport function extractICO(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to number of files there are.\n    stream.moveTo(4);\n\n    // Read the number of files stored in the ICO\n    const numberFiles = stream.readInt(2, \"le\");\n\n    // Move forward to the last file header.\n    stream.moveForwardsBy(8 + ((numberFiles-1) * 16));\n    const fileSize = stream.readInt(4, \"le\");\n    const fileOffset = stream.readInt(4, \"le\");\n\n    // Move to the end of the last file.\n    stream.moveTo(fileOffset + fileSize);\n    return stream.carve();\n}\n\n\n/**\n * TARGA extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n */\nexport function extractTARGA(bytes, offset) {\n    // Need all the bytes since we do not know how far up the image goes.\n    const stream = new Stream(bytes);\n    stream.moveTo(offset - 8);\n\n    // Read in the offsets of the possible areas.\n    const extensionOffset = stream.readInt(4, \"le\");\n    const developerOffset = stream.readInt(4, \"le\");\n\n    stream.moveBackwardsBy(8);\n\n    /**\n     * Moves backwards in the stream until it meet bytes that are the same as the amount of bytes moved.\n     *\n     * @param {number} sizeOfSize\n     * @param {number} maxSize\n     */\n    function moveBackwardsUntilSize(maxSize, sizeOfSize) {\n        for (let i = 0; i < maxSize; i++) {\n            stream.moveBackwardsBy(1);\n\n            // Read in sizeOfSize amount of bytes in.\n            const size = stream.readInt(sizeOfSize, \"le\") - 1;\n            stream.moveBackwardsBy(sizeOfSize);\n\n            // If the size matches.\n            if (size === i)\n                break;\n        }\n    }\n\n    /**\n     * Moves backwards in the stream until we meet bytes(when calculated) that are the same as the amount of bytes moved.\n     */\n    function moveBackwardsUntilImageSize() {\n        stream.moveBackwardsBy(5);\n\n        // The documentation said that 0x100000 was the largest the file could be.\n        for (let i = 0; i < 0x100000; i++) {\n\n            // (Height * Width * pixel depth in bits)/8\n            const total = (stream.readInt(2, \"le\") * stream.readInt(2, \"le\") * stream.readInt(1))/8;\n            if (total === i-1)\n                break;\n\n            stream.moveBackwardsBy(6);\n        }\n    }\n\n    if (extensionOffset || developerOffset) {\n        if (extensionOffset) {\n            // Size is stored in two bytes hence the maximum is 0xffff.\n            moveBackwardsUntilSize(0xffff, 2);\n\n            // Move to where we think the start of the file is.\n            stream.moveBackwardsBy(extensionOffset);\n        } else if (developerOffset) {\n            // Size is stored in 4 bytes hence the maxiumum is 0xffffffff.\n            moveBackwardsUntilSize(0xffffffff, 4);\n\n            // Size is stored in byte position 6 so have to move back.\n            stream.moveBackwardsBy(6);\n\n            // Move to where we think the start of the file is.\n            stream.moveBackwardsBy(developerOffset);\n        }\n    } else {\n        // Move backwards until size === number of bytes passed.\n        moveBackwardsUntilImageSize();\n\n        // Move backwards over the reaminder of the header + the 5 we borrowed in moveBackwardsUntilImageSize().\n        stream.moveBackwardsBy(0xc+5);\n    }\n\n    return stream.carve(stream.position, offset+0x12);\n}\n\n\n/**\n * WAV extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractWAV(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to file size field.\n    stream.moveTo(4);\n\n    // Move to file size.\n    stream.moveTo(stream.readInt(4, \"le\") + 8);\n\n    return stream.carve();\n}\n\n\n/**\n * MP3 extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractMP3(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Constants for flag byte.\n    const bitRateIndexes = [\"free\", 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, \"bad\"];\n\n    const samplingRateFrequencyIndex = [44100, 48000, 32000, \"reserved\"];\n\n    // ID3 tag, move over it.\n    if ((stream.getBytes(3).toString() === [0x49, 0x44, 0x33].toString())) {\n        stream.moveTo(6);\n        const tagSize = (stream.readInt(1) << 21) | (stream.readInt(1) << 14) | (stream.readInt(1) << 7) | stream.readInt(1);\n        stream.moveForwardsBy(tagSize);\n    } else {\n        stream.moveTo(0);\n    }\n\n    // Loop over all the frame headers in the file.\n    while (stream.hasMore()) {\n\n        // If it has an old TAG frame at the end of it, fixed size, 128 bytes.\n        if (stream.getBytes(3) === [0x54, 0x41, 0x47].toString()) {\n            stream.moveForwardsBy(125);\n            break;\n        }\n\n        // If not start of frame.\n        if (stream.getBytes(2).toString() !== [0xff, 0xfb].toString()) {\n            stream.moveBackwardsBy(2);\n            break;\n        }\n\n        // Read flag byte.\n        const flags = stream.readInt(1);\n\n        // Extract frame bit rate from flag byte.\n        const bitRate = bitRateIndexes[flags >> 4];\n\n        // Extract frame sample rate from flag byte.\n        const sampleRate = samplingRateFrequencyIndex[(flags & 0x0f) >> 2];\n\n        // Padding if the frame size is not a multiple of the bitrate.\n        const padding = (flags & 0x02) >> 1;\n\n        // Things that are either not standard or undocumented.\n        if (bitRate === \"free\" || bitRate === \"bad\" || sampleRate === \"reserved\") {\n            stream.moveBackwardsBy(1);\n            break;\n        }\n\n        // Formula: FrameLength = (144 * BitRate / SampleRate ) + Padding\n        const frameSize = Math.floor(((144 * bitRate) / sampleRate) + padding);\n\n        // If the next move goes past the end of the bytestream then extract the entire bytestream.\n        // We assume complete frames in the above formula because there is no field that suggests otherwise.\n        if ((stream.position + frameSize) > stream.length) {\n            stream.moveTo(stream.length);\n            break;\n        } else {\n            stream.moveForwardsBy(frameSize - 3);\n        }\n    }\n    return stream.carve();\n}\n\n\n/**\n * FLV extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractFLV(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move past signature, version and flags\n    stream.moveForwardsBy(5);\n\n    // Read header size\n    const headerSize = stream.readInt(4, \"be\");\n\n    // Skip through the rest of the header\n    stream.moveForwardsBy(headerSize - 9);\n\n    let tagSize = -11; // Fake size of previous tag header\n    while (stream.hasMore()) {\n        const prevTagSize = stream.readInt(4, \"be\");\n        const tagType = stream.readInt(1);\n\n        if ([8, 9, 18].indexOf(tagType) < 0) {\n            // This tag is not valid\n            stream.moveBackwardsBy(1);\n            break;\n        }\n\n        if (prevTagSize !== (tagSize + 11)) {\n            // Previous tag was not valid, reverse back over this header\n            // and the previous tag body and header\n            stream.moveBackwardsBy(tagSize + 11 + 5);\n            break;\n        }\n\n        tagSize = stream.readInt(3, \"be\");\n\n        // Move past the rest of the tag header and payload\n        stream.moveForwardsBy(7 + tagSize);\n    }\n\n    return stream.carve();\n}\n\n\n/**\n * RTF extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractRTF(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    let openTags = 0;\n\n    if (stream.readInt(1) !== 0x7b) { // {\n        throw new Error(\"Not a valid RTF file\");\n    } else {\n        openTags++;\n    }\n\n    while (openTags > 0 && stream.hasMore()) {\n        switch (stream.readInt(1)) {\n            case 0x7b: // {\n                openTags++;\n                break;\n            case 0x7d: // }\n                openTags--;\n                break;\n            case 0x5c: // \\\n                // Consume any more escapes and then skip over the next character\n                stream.consumeIf(0x5c);\n                stream.position++;\n                break;\n            default:\n                break;\n        }\n    }\n\n    return stream.carve();\n}\n\n\n/**\n * SQLITE extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractSQLITE(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Extract the size of the page.\n    stream.moveTo(16);\n    const pageSize = stream.readInt(2);\n\n    // Extract the number of pages.\n    stream.moveTo(28);\n    const numPages = stream.readInt(4);\n\n    // Move to the end of all the pages.\n    stream.moveTo(pageSize*numPages);\n\n    return stream.carve();\n}\n\n\n/**\n * PList (XML) extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractPListXML(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    let braceCount = 0;\n\n    // Continue to the first (<plist).\n    stream.continueUntil([0x3c, 0x70, 0x6c, 0x69, 0x73, 0x74]);\n    stream.moveForwardsBy(6);\n    braceCount++;\n\n    // While we have an unequal amount of braces.\n    while (braceCount > 0 && stream.hasMore()) {\n        if (stream.readInt(1) === 0x3c) {\n\n            // If we hit an <plist.\n            if (stream.getBytes(5).join(\"\") === [0x70, 0x6c, 0x69, 0x73, 0x74].join(\"\")) {\n                braceCount++;\n            } else {\n                stream.moveBackwardsBy(5);\n            }\n\n            // If we hit an </plist>.\n            if (stream.getBytes(7).join(\"\") === [0x2f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x3e].join(\"\")) {\n                braceCount--;\n            } else {\n                stream.moveBackwardsBy(7);\n            }\n        }\n    }\n    stream.consumeIf(0x0a);\n\n    return stream.carve();\n}\n\n\n/**\n * MacOS X Keychain Extactor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractMacOSXKeychain(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to size field.\n    stream.moveTo(0x14);\n\n    // Move forwards by size.\n    stream.moveForwardsBy(stream.readInt(4));\n\n    return stream.carve();\n}\n\n\n/**\n * OLE2 extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractOLE2(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n    const entries = [\n        [[0x52, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x72, 0x00, 0x79], 19, \"Root Entry\"],\n        [[0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6b], 15, \"Workbook\"],\n        [[0x43, 0x00, 0x75, 0x00, 0x72, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x55, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72],  23,  \"Current User\"],\n        [[0x50, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x65, 0x00, 0x72, 0x00, 0x50, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74], 37, \"PowerPoint Document\"],\n        [[0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74], 23, \"WordDocument\"],\n        [[0x44, 0x00, 0x61, 0x00, 0x74, 0x00, 0x61], 7, \"Data\"],\n        [[0x50, 0x00, 0x69, 0x00, 0x63, 0x00, 0x74, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73], 15, \"Pictures\"],\n        [[0x31, 0x00, 0x54, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65], 11, \"1Table\"],\n        [[0x05, 0x00, 0x53, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x79, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e], 37, \"SummaryInformation\"],\n        [[0x05, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x53, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x79, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e], 53, \"DocumentSummaryInformation\"],\n        [[0x43, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x4f, 0x00, 0x62, 0x00, 0x6a], 13, \"Comp Obj\"],\n        [[0x01, 0x00], 2, \"Entry\"]\n    ];\n    let endianness = \"le\";\n\n    // Move to endianess field.\n    stream.moveForwardsBy(28);\n    if (stream.readInt(2, endianness) === 0xfffe)\n        endianness = \"be\";\n\n    // Calculate the size of the normal sectors.\n    const sizeOfSector = 2 ** stream.readInt(2, endianness);\n\n    // Move to root directory offset field.\n    stream.moveTo(48);\n\n    // Read root directory offset.\n    const rootStuff  = stream.readInt(4, endianness);\n\n    // Calculate root directory offset.\n    let total = 512 + (rootStuff * sizeOfSector);\n    stream.moveTo(total);\n\n    // While valid directory entries.\n    let found = true;\n    while (found) {\n        found = false;\n\n        // Attempt to determine what directory entry it is.\n        for (const element of entries) {\n\n            // If the byte pattern matches.\n            if (stream.getBytes(element[1]).join(\"\") === element[0].join(\"\")) {\n                stream.moveBackwardsBy(element[1]);\n                found = true;\n\n                // Move forwards by the size of the comp obj.\n                if (element[2] === \"Comp Obj\") {\n\n                    // The size of the Comp Obj entry - 128. Since we add 128 later.\n                    total += 128 * 6;\n                    stream.moveTo(total);\n                } else if (element[2] === \"Entry\") {\n\n                    // If there is an entry move backwards by 126 to then move forwards by 128. Hence a total displacement of 2.\n                    stream.moveBackwardsBy(126);\n                }\n                break;\n            }\n            stream.moveBackwardsBy(element[1]);\n        }\n\n        // If we have found a valid entry, move forwards by 128.\n        if (found) {\n\n            // Every entry is at least 128 in size, some are bigger which is dealt with by the above if statement.\n            total += 128;\n            stream.moveForwardsBy(128);\n        }\n    }\n\n    // Round up to a multiple of 512.\n    total = Math.ceil(total / 512) * 512;\n\n    stream.moveTo(total);\n    return stream.carve();\n}\n\n\n/**\n * GZIP extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractGZIP(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n\n    /* HEADER */\n\n    // Skip over signature and compression method\n    stream.moveForwardsBy(3);\n\n    // Read flags\n    const flags = stream.readInt(1);\n\n    // Skip over last modification time\n    stream.moveForwardsBy(4);\n\n    // Read compression flags\n    stream.readInt(1);\n\n    // Skip over OS\n    stream.moveForwardsBy(1);\n\n\n    /* OPTIONAL HEADERS */\n\n    // Extra fields\n    if (flags & 0x4) {\n        const extraFieldsSize = stream.readInt(2, \"le\");\n        stream.moveForwardsby(extraFieldsSize);\n    }\n\n    // Original filename\n    if (flags & 0x8) {\n        stream.continueUntil(0x00);\n        stream.moveForwardsBy(1);\n    }\n\n    // Comment\n    if (flags & 0x10) {\n        stream.continueUntil(0x00);\n        stream.moveForwardsBy(1);\n    }\n\n    // Checksum\n    if (flags & 0x2) {\n        stream.moveForwardsBy(2);\n    }\n\n\n    /* DEFLATE DATA */\n\n    parseDEFLATE(stream);\n\n\n    /* FOOTER */\n\n    // Skip over checksum and size of original uncompressed input\n    stream.moveForwardsBy(8);\n\n    return stream.carve();\n}\n\n\n/**\n * BZIP2 extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractBZIP2(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // The EOFs shifted between all possible combinations.\n    const lookingfor = [\n        [0x77, 0x24, 0x53, 0x85, 0x09],\n        [0xee, 0x48, 0xa7, 0x0a, 0x12],\n        [0xdc, 0x91, 0x4e, 0x14, 0x24],\n        [0xb9, 0x22, 0x9c, 0x28, 0x48],\n        [0x72, 0x45, 0x38, 0x50, 0x90],\n        [0xbb, 0x92, 0x29, 0xc2, 0x84],\n        [0x5d, 0xc9, 0x14, 0xe1, 0x42],\n        [0x2e, 0xe4, 0x8a, 0x70, 0xa1],\n        [0x17, 0x72, 0x45, 0x38, 0x50]\n    ];\n\n    for (let i = 0; i < lookingfor.length; i++) {\n        // Continue until an EOF.\n        stream.continueUntil(lookingfor[i]);\n        if (stream.getBytes(5).join(\"\") === lookingfor[i].join(\"\"))\n            break;\n\n        // Jump back to the start if invalid EOF.\n        stream.moveTo(0);\n    }\n    stream.moveForwardsBy(4);\n    return stream.carve();\n}\n\n\n/**\n * Zlib extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractZlib(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Skip over CMF\n    stream.moveForwardsBy(1);\n\n    // Read flags\n    const flags = stream.readInt(1);\n\n    // Skip over preset dictionary checksum\n    if (flags & 0x20) {\n        stream.moveForwardsBy(4);\n    }\n\n    // Parse DEFLATE stream\n    parseDEFLATE(stream);\n\n    // Skip over final checksum\n    stream.moveForwardsBy(4);\n\n    return stream.carve();\n}\n\n\n/**\n * XZ extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {string}\n */\nexport function extractXZ(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move forward to EOF marker\n    stream.continueUntil([0x00, 0x00, 0x00, 0x00, 0x04, 0x59, 0x5a]);\n\n    // Move over EOF marker\n    stream.moveForwardsBy(7);\n\n    return stream.carve();\n}\n\n\n/**\n * DEB extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n */\nexport function extractDEB(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move past !<arch>\n    stream.moveForwardsBy(8);\n    while (stream.hasMore()) {\n\n        // Move to size field.\n        stream.moveForwardsBy(48);\n        let fsize= \"\";\n\n        // Convert size to a usable number.\n        for (const elem of stream.getBytes(10)) {\n            fsize += String.fromCharCode(elem);\n        }\n        fsize = parseInt(fsize.trim(), 10);\n\n        // Move past `\\n\n        stream.moveForwardsBy(2);\n        stream.moveForwardsBy(fsize);\n    }\n    return stream.carve();\n}\n\n\n/**\n * ELF extractor.\n *\n * @param {Uint8Array} bytes\n * @param {number} offset\n * @returns {Uint8Array}\n */\nexport function extractELF(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Skip over magic number\n    stream.moveForwardsBy(4);\n\n    // Read architecture (x86 == 1, x64 == 2)\n    const x86 = stream.readInt(1) === 1;\n\n    // Read endianness (1 == little, 2 == big)\n    const endian = stream.readInt(1) === 1 ? \"le\" : \"be\";\n\n    // Skip over header values\n    stream.moveForwardsBy(x86 ? 26 : 34);\n\n    // Read section header table offset\n    const shoff = x86 ? stream.readInt(4, endian) : stream.readInt(8, endian);\n\n    // Skip over flags, header size and program header size and entries\n    stream.moveForwardsBy(10);\n\n    // Read section header table entry size\n    const shentsize = stream.readInt(2, endian);\n\n    // Read number of entries in the section header table\n    const shnum = stream.readInt(2, endian);\n\n    // Jump to section header table\n    stream.moveTo(shoff);\n\n    // Move past each section header\n    stream.moveForwardsBy(shentsize * shnum);\n\n    return stream.carve();\n}\n\n\n// Construct required Huffman Tables\nconst fixedLiteralTableLengths = new Array(288);\nfor (let i = 0; i < fixedLiteralTableLengths.length; i++) {\n    fixedLiteralTableLengths[i] =\n        (i <= 143) ? 8 :\n            (i <= 255) ? 9 :\n                (i <= 279) ? 7 :\n                    8;\n}\nconst fixedLiteralTable = buildHuffmanTable(fixedLiteralTableLengths);\nconst fixedDistanceTableLengths = new Array(30).fill(5);\nconst fixedDistanceTable = buildHuffmanTable(fixedDistanceTableLengths);\nconst huffmanOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];\n\n/**\n * Steps through a DEFLATE stream\n *\n * @param {Stream} stream\n */\nfunction parseDEFLATE(stream) {\n    // Parse DEFLATE data\n    let finalBlock = 0;\n\n    while (!finalBlock) {\n        // Read header\n        finalBlock = stream.readBits(1, \"le\");\n        const blockType = stream.readBits(2, \"le\");\n\n        if (blockType === 0) {\n            /* No compression */\n\n            // Consume the rest of the current byte\n            stream.moveForwardsBy(1);\n            // Read the block length value\n            const blockLength = stream.readInt(2, \"le\");\n            // Move to the end of this block\n            stream.moveForwardsBy(2 + blockLength);\n        } else if (blockType === 1) {\n            /* Fixed Huffman */\n\n            parseHuffmanBlock(stream, fixedLiteralTable, fixedDistanceTable);\n        } else if (blockType === 2) {\n            /* Dynamic Huffman */\n\n            // Read the number of liternal and length codes\n            const hlit = stream.readBits(5, \"le\") + 257;\n            // Read the number of distance codes\n            const hdist = stream.readBits(5, \"le\") + 1;\n            // Read the number of code lengths\n            const hclen = stream.readBits(4, \"le\") + 4;\n\n            // Parse code lengths\n            const codeLengths = new Uint8Array(huffmanOrder.length);\n            for (let i = 0; i < hclen; i++) {\n                codeLengths[huffmanOrder[i]] = stream.readBits(3, \"le\");\n            }\n\n            // Parse length table\n            const codeLengthsTable = buildHuffmanTable(codeLengths);\n            const lengthTable = new Uint8Array(hlit + hdist);\n\n            let code, repeat, prev;\n            for (let i = 0; i < hlit + hdist;) {\n                code = readHuffmanCode(stream, codeLengthsTable);\n                switch (code) {\n                    case 16:\n                        repeat = 3 + stream.readBits(2, \"le\");\n                        while (repeat--) lengthTable[i++] = prev;\n                        break;\n                    case 17:\n                        repeat = 3 + stream.readBits(3, \"le\");\n                        while (repeat--) lengthTable[i++] = 0;\n                        prev = 0;\n                        break;\n                    case 18:\n                        repeat = 11 + stream.readBits(7, \"le\");\n                        while (repeat--) lengthTable[i++] = 0;\n                        prev = 0;\n                        break;\n                    default:\n                        lengthTable[i++] = code;\n                        prev = code;\n                        break;\n                }\n            }\n\n            const dynamicLiteralTable = buildHuffmanTable(lengthTable.subarray(0, hlit));\n            const dynamicDistanceTable = buildHuffmanTable(lengthTable.subarray(hlit));\n\n            parseHuffmanBlock(stream, dynamicLiteralTable, dynamicDistanceTable);\n        } else {\n            throw new Error(`Invalid block type while parsing DEFLATE stream at pos ${stream.position}`);\n        }\n    }\n\n    // Consume final byte if it has not been fully consumed yet\n    if (stream.bitPos > 0)\n        stream.moveForwardsBy(1);\n}\n\n\n// Static length tables\nconst lengthExtraTable = [\n    0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0\n];\nconst distanceExtraTable = [\n    0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13\n];\n\n/**\n * Parses a Huffman Block given the literal and distance tables\n *\n * @param {Stream} stream\n * @param {Uint32Array} litTab\n * @param {Uint32Array} distTab\n */\nfunction parseHuffmanBlock(stream, litTab, distTab) {\n    let code;\n    let loops = 0;\n    while ((code = readHuffmanCode(stream, litTab))) {\n        // console.log(\"Code: \" + code + \" (\" + Utils.chr(code) + \") \" + Utils.bin(code));\n\n        // End of block\n        if (code === 256) break;\n\n        // Detect probably infinite loops\n        if (++loops > 10000)\n            throw new Error(\"Caught in probable infinite loop while parsing Huffman Block\");\n\n        // Literal\n        if (code < 256) continue;\n\n        // Length code\n        stream.readBits(lengthExtraTable[code - 257], \"le\");\n\n        // Dist code\n        code = readHuffmanCode(stream, distTab);\n        stream.readBits(distanceExtraTable[code], \"le\");\n    }\n}\n\n\n/**\n * Builds a Huffman table given the relevant code lengths\n *\n * @param {Array} lengths\n * @returns {Array} result\n * @returns {Uint32Array} result.table\n * @returns {number} result.maxCodeLength\n * @returns {number} result.minCodeLength\n */\nfunction buildHuffmanTable(lengths) {\n    const maxCodeLength = Math.max.apply(Math, lengths);\n    const minCodeLength = Math.min.apply(Math, lengths);\n    const size = 1 << maxCodeLength;\n    const table = new Uint32Array(size);\n\n    for (let bitLength = 1, code = 0, skip = 2; bitLength <= maxCodeLength;) {\n        for (let i = 0; i < lengths.length; i++) {\n            if (lengths[i] === bitLength) {\n                let reversed, rtemp, j;\n                for (reversed = 0, rtemp = code, j = 0; j < bitLength; j++) {\n                    reversed = (reversed << 1) | (rtemp & 1);\n                    rtemp >>= 1;\n                }\n\n                const value = (bitLength << 16) | i;\n                for (let j = reversed; j < size; j += skip) {\n                    table[j] = value;\n                }\n\n                code++;\n            }\n        }\n\n        bitLength++;\n        code <<= 1;\n        skip <<= 1;\n    }\n\n    return [table, maxCodeLength, minCodeLength];\n}\n\n\n/**\n * Reads the next Huffman code from the stream, given the relevant code table\n *\n * @param {Stream} stream\n * @param {Uint32Array} table\n * @returns {number}\n */\nfunction readHuffmanCode(stream, table) {\n    const [codeTable, maxCodeLength] = table;\n\n    // Read max length\n    const bitsBuf = stream.readBits(maxCodeLength, \"le\");\n    const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];\n    const codeLength = codeWithLength >>> 16;\n\n    if (codeLength > maxCodeLength) {\n        throw new Error(`Invalid Huffman Code length while parsing DEFLATE block at pos ${stream.position}: ${codeLength}`);\n    }\n\n    stream.moveBackwardsByBits(maxCodeLength - codeLength);\n\n    return codeWithLength & 0xffff;\n}\n\n\n/**\n * EVTX extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractEVTX(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to first ELFCHNK.\n    stream.moveTo(0x28);\n    const total = stream.readInt(4, \"le\") - 0x2c;\n    stream.moveForwardsBy(total);\n\n    while (stream.hasMore()) {\n        // Loop through ELFCHNKs.\n        if (stream.getBytes(7).join(\"\") !== [0x45, 0x6c, 0x66, 0x43, 0x68, 0x6e, 0x6b].join(\"\"))\n            break;\n        stream.moveForwardsBy(0xfff9);\n    }\n    stream.consumeWhile(0x00);\n    return stream.carve();\n}\n\n\n/**\n * EVT extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractEVT(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Extract offset of EOF.\n    stream.moveTo(0x14);\n    const eofOffset = stream.readInt(4, \"le\");\n    stream.moveTo(eofOffset);\n\n    // Extract the size of the EOF.\n    const eofSize = stream.readInt(4, \"le\");\n\n    // Move past EOF.\n    stream.moveForwardsBy(eofSize-4);\n    return stream.carve();\n}\n\n\n/**\n * DMP extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractDMP(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to fileSize field.\n    stream.moveTo(0x70);\n\n    // Multiply number of pages by page size. Plus 1 since the header is a page.\n    stream.moveTo((stream.readInt(4, \"le\") + 1) * 0x1000);\n\n    return stream.carve();\n}\n\n\n/**\n * PF extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractPF(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to file size.\n    stream.moveTo(12);\n    stream.moveTo(stream.readInt(4, \"be\"));\n\n    return stream.carve();\n}\n\n\n/**\n * PF (Win 10) extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractPFWin10(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Read in file size.\n    stream.moveTo(stream.readInt(4, \"be\"));\n\n    return stream.carve();\n}\n\n\n/**\n * LNK extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractLNK(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Move to file size field.\n    stream.moveTo(0x34);\n    stream.moveTo(stream.readInt(4, \"le\"));\n\n    return stream.carve();\n}\n\n\n/**\n * LZOP extractor.\n *\n * @param {Uint8Array} bytes\n * @param {Number} offset\n * @returns {Uint8Array}\n */\nexport function extractLZOP(bytes, offset) {\n    const stream = new Stream(bytes.slice(offset));\n\n    // Flag bits.\n    const F_ADLER32_D = 0x00000001;\n    const F_ADLER32_C = 0x00000002;\n    const F_CRC32_D = 0x00000100;\n    const F_CRC32_C = 0x00000200;\n    const F_H_FILTER = 0x00000800;\n    const F_H_EXTRA_FIELD = 0x00000040;\n\n    let numCheckSumC = 0, numCheckSumD = 0;\n\n    // Move over magic bytes.\n    stream.moveForwardsBy(9);\n\n    const version = stream.readInt(2, \"be\");\n\n    // Move to flag register offset.\n    stream.moveForwardsBy(6);\n    const flags = stream.readInt(4, \"be\");\n\n    if (version & F_H_FILTER)\n        stream.moveForwardsBy(4);\n\n    if (flags & F_ADLER32_C)\n        numCheckSumC++;\n\n    if (flags & F_CRC32_C)\n        numCheckSumC++;\n\n    if (flags & F_ADLER32_D)\n        numCheckSumD++;\n\n    if (flags & F_CRC32_D)\n        numCheckSumD++;\n\n    // Move over the mode, mtime_low\n    stream.moveForwardsBy(8);\n\n    if (version >= 0x0940)\n        stream.moveForwardsBy(4);\n\n    const fnameSize = stream.readInt(1, \"be\");\n\n    // Move forwards by size of file name and the following 4 byte checksum.\n    stream.moveForwardsBy(fnameSize);\n\n    if (flags & F_H_EXTRA_FIELD) {\n        const extraSize = stream.readInt(4, \"be\");\n        stream.moveForwardsBy(extraSize);\n    }\n\n    // Move past checksum.\n    stream.moveForwardsBy(4);\n\n    while (stream.hasMore()) {\n        const uncompSize = stream.readInt(4, \"be\");\n\n        // If data has no length, break.\n        if (uncompSize === 0)\n            break;\n\n        const compSize = stream.readInt(4, \"be\");\n\n        const numCheckSumSkip = (uncompSize === compSize) ? numCheckSumD : numCheckSumD + numCheckSumC;\n\n        // skip forwards by compressed data size and the size of the checksum(s).\n        stream.moveForwardsBy(compSize + (numCheckSumSkip * 4));\n    }\n    return stream.carve();\n\n}\n"
  },
  {
    "path": "src/core/lib/FileType.mjs",
    "content": "/**\n * File type functions\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n *\n */\nimport {FILE_SIGNATURES} from \"./FileSignatures.mjs\";\nimport {sendStatusMessage} from \"../Utils.mjs\";\n\n\n/**\n * Checks whether a signature matches a buffer.\n *\n * @param {Object|Object[]} sig - A dictionary of offsets with values assigned to them.\n *   These values can be numbers for static checks, arrays of potential valid matches,\n *   or bespoke functions to check the validity of the buffer value at that offset.\n * @param {Uint8Array} buf\n * @param {number} [offset=0] Where in the buffer to start searching from\n * @returns {boolean}\n */\nfunction signatureMatches(sig, buf, offset=0) {\n    // Using a length check seems to be more performant than `sig instanceof Array`\n    if (sig.length) {\n        // sig is an Array - return true if any of them match\n        // The following `reduce` method is nice, but performance matters here, so we\n        // opt for a faster, if less elegant, for loop.\n        // return sig.reduce((acc, s) => acc || bytesMatch(s, buf, offset), false);\n        for (let i = 0; i < sig.length; i++) {\n            if (bytesMatch(sig[i], buf, offset)) return true;\n        }\n        return false;\n    } else {\n        return bytesMatch(sig, buf, offset);\n    }\n}\n\n\n/**\n * Checks whether a set of bytes match the given buffer.\n *\n * @param {Object} sig - A dictionary of offsets with values assigned to them.\n *   These values can be numbers for static checks, arrays of potential valid matches,\n *   or bespoke functions to check the validity of the buffer value at that offset.\n * @param {Uint8Array} buf\n * @param {number} [offset=0] Where in the buffer to start searching from\n * @returns {boolean}\n */\nfunction bytesMatch(sig, buf, offset=0) {\n    for (const sigoffset in sig) {\n        const pos = parseInt(sigoffset, 10) + offset;\n        switch (typeof sig[sigoffset]) {\n            case \"number\": // Static check\n                if (buf[pos] !== sig[sigoffset])\n                    return false;\n                break;\n            case \"object\": // Array of options\n                if (sig[sigoffset].indexOf(buf[pos]) < 0)\n                    return false;\n                break;\n            case \"function\": // More complex calculation\n                if (!sig[sigoffset](buf[pos]))\n                    return false;\n                break;\n            default:\n                throw new Error(`Unrecognised signature type at offset ${sigoffset}`);\n        }\n    }\n    return true;\n}\n\n\n/**\n * Given a buffer, detects magic byte sequences at specific positions and returns the\n * extension and mime type.\n *\n * @param {Uint8Array|ArrayBuffer} buf\n * @param {string[]} [categories=All] - Which categories of file to look for\n * @returns {Object[]} types\n * @returns {string} type.name - Name of file type\n * @returns {string} type.ext - File extension\n * @returns {string} type.mime - Mime type\n * @returns {string} [type.desc] - Description\n */\nexport function detectFileType(buf, categories=Object.keys(FILE_SIGNATURES)) {\n    if (buf instanceof ArrayBuffer) {\n        buf = new Uint8Array(buf);\n    }\n\n    if (!(buf && buf.length > 1)) {\n        return [];\n    }\n\n    const matchingFiles = [];\n    const signatures = {};\n\n    for (const cat in FILE_SIGNATURES) {\n        if (categories.includes(cat)) {\n            signatures[cat] = FILE_SIGNATURES[cat];\n        }\n    }\n\n    for (const cat in signatures) {\n        const category = signatures[cat];\n\n        category.forEach(filetype => {\n            if (signatureMatches(filetype.signature, buf)) {\n                matchingFiles.push(filetype);\n            }\n        });\n    }\n    return matchingFiles;\n}\n\n\n/**\n * Given a buffer, searches for magic byte sequences at all possible positions and returns\n * the extensions and mime types.\n *\n * @param {Uint8Array} buf\n * @param {string[]} [categories=All] - Which categories of file to look for\n * @returns {Object[]} foundFiles\n * @returns {number} foundFiles.offset - The position in the buffer at which this file was found\n * @returns {Object} foundFiles.fileDetails\n * @returns {string} foundFiles.fileDetails.name - Name of file type\n * @returns {string} foundFiles.fileDetails.ext - File extension\n * @returns {string} foundFiles.fileDetails.mime - Mime type\n * @returns {string} [foundFiles.fileDetails.desc] - Description\n */\nexport function scanForFileTypes(buf, categories=Object.keys(FILE_SIGNATURES)) {\n    if (!(buf && buf.length > 1)) {\n        return [];\n    }\n\n    const foundFiles = [];\n    const signatures = {};\n\n    for (const cat in FILE_SIGNATURES) {\n        if (categories.includes(cat)) {\n            signatures[cat] = FILE_SIGNATURES[cat];\n        }\n    }\n\n    for (const cat in signatures) {\n        const category = signatures[cat];\n\n        for (let i = 0; i < category.length; i++) {\n            const filetype = category[i];\n            const sigs = filetype.signature.length ? filetype.signature : [filetype.signature];\n\n            sigs.forEach(sig => {\n                let pos = 0;\n                while ((pos = locatePotentialSig(buf, sig, pos)) >= 0) {\n                    if (bytesMatch(sig, buf, pos)) {\n                        sendStatusMessage(`Found potential signature for ${filetype.name} at pos ${pos}`);\n                        foundFiles.push({\n                            offset: pos,\n                            fileDetails: filetype\n                        });\n                    }\n                    pos++;\n                }\n            });\n        }\n    }\n\n    // Return found files in order of increasing offset\n    return foundFiles.sort((a, b) => {\n        return a.offset - b.offset;\n    });\n}\n\n\n/**\n * Fastcheck function to quickly scan the buffer for the first byte in a signature.\n *\n * @param {Uint8Array} buf - The buffer to search\n * @param {Object} sig - A single signature object (Not an array of signatures)\n * @param {number} offset - Where to start search from\n * @returns {number} The position of the match or -1 if one cannot be found.\n */\nfunction locatePotentialSig(buf, sig, offset) {\n    // Find values for first key and value in sig\n    const k = parseInt(Object.keys(sig)[0], 10);\n    const v = Object.values(sig)[0];\n    switch (typeof v) {\n        case \"number\":\n            return buf.indexOf(v, offset + k) - k;\n        case \"object\":\n            for (let i = offset + k; i < buf.length; i++) {\n                if (v.indexOf(buf[i]) >= 0) return i - k;\n            }\n            return -1;\n        case \"function\":\n            for (let i = offset + k; i < buf.length; i++) {\n                if (v(buf[i])) return i - k;\n            }\n            return -1;\n        default:\n            throw new Error(\"Unrecognised signature type\");\n    }\n}\n\n\n/**\n * Detects whether the given buffer is a file of the type specified.\n *\n * @param {string|RegExp} type\n * @param {Uint8Array|ArrayBuffer} buf\n * @returns {string|false} The mime type or false if the type does not match\n */\nexport function isType(type, buf) {\n    const types = detectFileType(buf);\n\n    if (!types.length) return false;\n\n    if (typeof type === \"string\") {\n        return types.reduce((acc, t) => {\n            const mime = t.mime.startsWith(type) ? t.mime : false;\n            return acc || mime;\n        }, false);\n    } else if (type instanceof RegExp) {\n        return types.reduce((acc, t) => {\n            const mime = type.test(t.mime) ? t.mime : false;\n            return acc || mime;\n        }, false);\n    } else {\n        throw new Error(\"Invalid type input.\");\n    }\n}\n\n\n/**\n * Detects whether the given buffer contains an image file.\n *\n * @param {Uint8Array|ArrayBuffer} buf\n * @returns {string|false} The mime type or false if the type does not match\n */\nexport function isImage(buf) {\n    return isType(\"image\", buf);\n}\n\n\n/**\n * Attempts to extract a file from a data stream given its offset and extractor function.\n *\n * @param {Uint8Array} bytes\n * @param {Object} fileDetail\n * @param {string} fileDetail.mime\n * @param {string} fileDetail.extension\n * @param {Function} fileDetail.extractor\n * @param {number} offset\n * @returns {File}\n */\nexport function extractFile(bytes, fileDetail, offset) {\n    if (fileDetail.extractor) {\n        sendStatusMessage(`Attempting to extract ${fileDetail.name} at pos ${offset}...`);\n        const fileData = fileDetail.extractor(bytes, offset);\n        const ext = fileDetail.extension.split(\",\")[0];\n        return new File([fileData], `extracted_at_0x${offset.toString(16)}.${ext}`, {\n            type: fileDetail.mime\n        });\n    }\n\n    throw new Error(`No extraction algorithm available for \"${fileDetail.mime}\" files`);\n}\n"
  },
  {
    "path": "src/core/lib/FlowControl.mjs",
    "content": "/**\n * Flow control functions\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/**\n * Returns the index of a label.\n *\n * @param {Object} state - The current state of the recipe.\n * @param {string} name - The label name to look for.\n * @returns {number}\n */\nexport function getLabelIndex(name, state) {\n    return state.opList.findIndex((operation) => {\n        return (operation.name === \"Label\") && (name === operation.ingValues[0]);\n    });\n}\n"
  },
  {
    "path": "src/core/lib/FuzzyMatch.mjs",
    "content": "/**\n * LICENSE\n *\n *   This software is dual-licensed to the public domain and under the following\n *   license: you are granted a perpetual, irrevocable license to copy, modify,\n *   publish, and distribute this file as you see fit.\n *\n * VERSION\n *   0.1.0  (2016-03-28)  Initial release\n *\n * AUTHOR\n *   Forrest Smith\n *\n * CONTRIBUTORS\n *   J�rgen Tjern� - async helper\n *   Anurag Awasthi - updated to 0.2.0\n */\n\nexport const DEFAULT_WEIGHTS = {\n    sequentialBonus: 15, // bonus for adjacent matches\n    separatorBonus: 30, // bonus if match occurs after a separator\n    camelBonus: 30, // bonus if match is uppercase and prev is lower\n    firstLetterBonus: 15, // bonus if the first letter is matched\n\n    leadingLetterPenalty: -5, // penalty applied for every letter in str before the first match\n    maxLeadingLetterPenalty: -15, // maximum penalty for leading letters\n    unmatchedLetterPenalty: -1\n};\n\n/**\n * Does a fuzzy search to find pattern inside a string.\n * @param {string} pattern        pattern to search for\n * @param {string} str            string which is being searched\n * @param {boolean} global        whether to search for all matches or just one\n * @returns [boolean, number]       a boolean which tells if pattern was\n *                                  found or not and a search score\n */\nexport function fuzzyMatch(pattern, str, global=false, weights=DEFAULT_WEIGHTS) {\n    const recursionCount = 0;\n    const recursionLimit = 10;\n    const matches = [];\n    const maxMatches = 256;\n\n    if (!global) {\n        return fuzzyMatchRecursive(\n            pattern,\n            str,\n            0 /* patternCurIndex */,\n            0 /* strCurrIndex */,\n            null /* srcMatches */,\n            matches,\n            maxMatches,\n            0 /* nextMatch */,\n            recursionCount,\n            recursionLimit,\n            weights\n        );\n    }\n\n    // Return all matches\n    let foundMatch = true,\n        score,\n        idxs,\n        strCurrIndex = 0;\n    const results = [];\n\n    while (foundMatch) {\n        [foundMatch, score, idxs] = fuzzyMatchRecursive(\n            pattern,\n            str,\n            0 /* patternCurIndex */,\n            strCurrIndex,\n            null /* srcMatches */,\n            matches,\n            maxMatches,\n            0 /* nextMatch */,\n            recursionCount,\n            recursionLimit,\n            weights\n        );\n        if (foundMatch) results.push([foundMatch, score, [...idxs]]);\n        strCurrIndex = idxs[idxs.length - 1] + 1;\n    }\n    return results;\n}\n\n/**\n * Recursive helper function\n */\nfunction fuzzyMatchRecursive(\n    pattern,\n    str,\n    patternCurIndex,\n    strCurrIndex,\n    srcMatches,\n    matches,\n    maxMatches,\n    nextMatch,\n    recursionCount,\n    recursionLimit,\n    weights\n) {\n    let outScore = 0;\n\n    // Return if recursion limit is reached.\n    if (++recursionCount >= recursionLimit) {\n        return [false, outScore, []];\n    }\n\n    // Return if we reached ends of strings.\n    if (patternCurIndex === pattern.length || strCurrIndex === str.length) {\n        return [false, outScore, []];\n    }\n\n    // Recursion params\n    let recursiveMatch = false;\n    let bestRecursiveMatches = [];\n    let bestRecursiveScore = 0;\n\n    // Loop through pattern and str looking for a match.\n    let firstMatch = true;\n    while (patternCurIndex < pattern.length && strCurrIndex < str.length) {\n        // Match found.\n        if (\n            pattern[patternCurIndex].toLowerCase() === str[strCurrIndex].toLowerCase()\n        ) {\n            if (nextMatch >= maxMatches) {\n                return [false, outScore, []];\n            }\n\n            if (firstMatch && srcMatches) {\n                matches = [...srcMatches];\n                firstMatch = false;\n            }\n\n            const [matched, recursiveScore, recursiveMatches] = fuzzyMatchRecursive(\n                pattern,\n                str,\n                patternCurIndex,\n                strCurrIndex + 1,\n                matches,\n                recursiveMatches,\n                maxMatches,\n                nextMatch,\n                recursionCount,\n                recursionLimit,\n                weights\n            );\n\n            if (matched) {\n                // Pick best recursive score.\n                if (!recursiveMatch || recursiveScore > bestRecursiveScore) {\n                    bestRecursiveMatches = [...recursiveMatches];\n                    bestRecursiveScore = recursiveScore;\n                }\n                recursiveMatch = true;\n            }\n\n            matches[nextMatch++] = strCurrIndex;\n            ++patternCurIndex;\n        }\n        ++strCurrIndex;\n    }\n\n    const matched = patternCurIndex === pattern.length;\n\n    if (matched) {\n        outScore = 100;\n\n        // Apply leading letter penalty\n        let penalty = weights.leadingLetterPenalty * matches[0];\n        penalty =\n            penalty < weights.maxLeadingLetterPenalty ?\n                weights.maxLeadingLetterPenalty :\n                penalty;\n        outScore += penalty;\n\n        // Apply unmatched penalty\n        const unmatched = str.length - nextMatch;\n        outScore += weights.unmatchedLetterPenalty * unmatched;\n\n        // Apply ordering bonuses\n        for (let i = 0; i < nextMatch; i++) {\n            const currIdx = matches[i];\n\n            if (i > 0) {\n                const prevIdx = matches[i - 1];\n                if (currIdx === prevIdx + 1) {\n                    outScore += weights.sequentialBonus;\n                }\n            }\n\n            // Check for bonuses based on neighbor character value.\n            if (currIdx > 0) {\n                // Camel case\n                const neighbor = str[currIdx - 1];\n                const curr = str[currIdx];\n                if (\n                    neighbor !== neighbor.toUpperCase() &&\n                    curr !== curr.toLowerCase()\n                ) {\n                    outScore += weights.camelBonus;\n                }\n                const isNeighbourSeparator = neighbor === \"_\" || neighbor === \" \";\n                if (isNeighbourSeparator) {\n                    outScore += weights.separatorBonus;\n                }\n            } else {\n                // First letter\n                outScore += weights.firstLetterBonus;\n            }\n        }\n\n        // Return best result\n        if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {\n            // Recursive score is better than \"this\"\n            matches = bestRecursiveMatches;\n            outScore = bestRecursiveScore;\n            return [true, outScore, matches];\n        } else if (matched) {\n            // \"this\" score is better than recursive\n            return [true, outScore, matches];\n        } else {\n            return [false, outScore, matches];\n        }\n    }\n    return [false, outScore, matches];\n}\n\n/**\n * Turns a list of match indexes into a list of match ranges\n *\n * @author n1474335 [n1474335@gmail.com]\n * @param [number] matches\n * @returns [[number]]\n */\nexport function calcMatchRanges(matches) {\n    const ranges = [];\n    let start = matches[0],\n        curr = start;\n\n    matches.forEach(m => {\n        if (m === curr || m === curr + 1) curr = m;\n        else {\n            ranges.push([start, curr - start + 1]);\n            start = m;\n            curr = m;\n        }\n    });\n\n    ranges.push([start, curr - start + 1]);\n    return ranges;\n}\n"
  },
  {
    "path": "src/core/lib/Hash.mjs",
    "content": "/**\n * Hashing resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport CryptoApi from \"crypto-api/src/crypto-api.mjs\";\n\n\n/**\n * Generic hash function.\n *\n * @param {string} name\n * @param {ArrayBuffer} input\n * @param {Object} [options={}]\n * @returns {string}\n */\nexport function runHash(name, input, options={}) {\n    const msg = Utils.arrayBufferToStr(input, false),\n        hasher = CryptoApi.getHasher(name, options);\n    hasher.update(msg);\n    return CryptoApi.encoder.toHex(hasher.finalize());\n}\n\n"
  },
  {
    "path": "src/core/lib/Hex.mjs",
    "content": "/**\n * Hexadecimal functions.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n\n/**\n * Convert a byte array into a hex string.\n *\n * @param {byteArray|Uint8Array|ArrayBuffer} data\n * @param {string} [delim=\" \"]\n * @param {number} [padding=2]\n * @returns {string}\n *\n * @example\n * // returns \"0a 14 1e\"\n * toHex([10,20,30]);\n *\n * // returns \"0a:14:1e\"\n * toHex([10,20,30], \":\");\n *\n * // returns \"0x0a,0x14,0x1e\"\n * toHex([10,20,30], \"0x\", 2, \",\")\n */\nexport function toHex(data, delim=\" \", padding=2, extraDelim=\"\", lineSize=0) {\n    if (!data) return \"\";\n    if (data instanceof ArrayBuffer) data = new Uint8Array(data);\n\n    let output = \"\";\n    const prepend = (delim === \"0x\" || delim === \"\\\\x\" || delim === \"%\");\n\n    for (let i = 0; i < data.length; i++) {\n        const hex = data[i].toString(16).padStart(padding, \"0\");\n        output += prepend ? delim + hex : hex + delim;\n\n        if (extraDelim) {\n            output += extraDelim;\n        }\n        // Add LF after each lineSize amount of bytes but not at the end\n        if ((i !== data.length - 1) && ((i + 1) % lineSize === 0)) {\n            output += \"\\n\";\n        }\n    }\n\n    // Remove the extraDelim at the end (if there is one)\n    // and remove the delim at the end, but if it's prepended there's nothing to remove\n    const rTruncLen = extraDelim.length + (prepend ? 0 : delim.length);\n    if (rTruncLen) {\n        // If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing\n        return output.slice(0, -rTruncLen);\n    } else {\n        return output;\n    }\n}\n\n\n/**\n * Convert a byte array into a hex string as efficiently as possible with no options.\n *\n * @param {byteArray|Uint8Array|ArrayBuffer} data\n * @returns {string}\n *\n * @example\n * // returns \"0a141e\"\n * toHex([10,20,30]);\n */\nexport function toHexFast(data) {\n    if (!data) return \"\";\n    if (data instanceof ArrayBuffer) data = new Uint8Array(data);\n\n    const output = [];\n\n    for (let i = 0; i < data.length; i++) {\n        output.push((data[i] >>> 4).toString(16));\n        output.push((data[i] & 0x0f).toString(16));\n    }\n\n    return output.join(\"\");\n}\n\n\n/**\n * Convert a hex string into a byte array.\n *\n * @param {string} data\n * @param {string} [delim]\n * @param {number} [byteLen=2]\n * @returns {byteArray}\n *\n * @example\n * // returns [10,20,30]\n * fromHex(\"0a 14 1e\");\n *\n * // returns [10,20,30]\n * fromHex(\"0a:14:1e\", \"Colon\");\n */\nexport function fromHex(data, delim=\"Auto\", byteLen=2) {\n    if (byteLen < 1 || Math.round(byteLen) !== byteLen)\n        throw new OperationError(\"Byte length must be a positive integer\");\n\n    if (delim !== \"None\") {\n        const delimRegex = delim === \"Auto\" ? /[^a-f\\d]|0x/gi : Utils.regexRep(delim);\n        data = data.split(delimRegex);\n    } else {\n        data = [data];\n    }\n\n    const output = [];\n    for (let i = 0; i < data.length; i++) {\n        for (let j = 0; j < data[i].length; j += byteLen) {\n            output.push(parseInt(data[i].substr(j, byteLen), 16));\n        }\n    }\n    return output;\n}\n\n\n/**\n * To Hexadecimal delimiters.\n */\nexport const TO_HEX_DELIM_OPTIONS = [\"Space\", \"Percent\", \"Comma\", \"Semi-colon\", \"Colon\", \"Line feed\", \"CRLF\", \"0x\", \"0x with comma\", \"\\\\x\", \"None\"];\n\n\n/**\n * From Hexadecimal delimiters.\n */\nexport const FROM_HEX_DELIM_OPTIONS = [\"Auto\"].concat(TO_HEX_DELIM_OPTIONS);\n"
  },
  {
    "path": "src/core/lib/IP.mjs",
    "content": "/**\n * IP resources.\n *\n * @author picapi\n * @author n1474335 [n1474335@gmail.com]\n * @author Klaxon [klaxon@veyr.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Parses an IPv4 CIDR range (e.g. 192.168.0.0/24) and displays information about it.\n *\n * @param {RegExp} cidr\n * @param {boolean} includeNetworkInfo\n * @param {boolean} enumerateAddresses\n * @param {boolean} allowLargeList\n * @returns {string}\n */\nexport function ipv4CidrRange(cidr, includeNetworkInfo, enumerateAddresses, allowLargeList) {\n    const network = strToIpv4(cidr[1]),\n        cidrRange = parseInt(cidr[2], 10);\n    let output = \"\";\n\n    if (cidrRange < 0 || cidrRange > 31) {\n        throw new OperationError(\"IPv4 CIDR must be less than 32\");\n    }\n\n    const mask = ~(0xFFFFFFFF >>> cidrRange),\n        ip1 = network & mask,\n        ip2 = ip1 | ~mask;\n\n    if (includeNetworkInfo) {\n        output += \"Network: \" + ipv4ToStr(network) + \"\\n\";\n        output += \"CIDR: \" + cidrRange + \"\\n\";\n        output += \"Mask: \" + ipv4ToStr(mask) + \"\\n\";\n        output += \"Range: \" + ipv4ToStr(ip1) + \" - \" + ipv4ToStr(ip2) + \"\\n\";\n        output += \"Total addresses in range: \" + (((ip2 - ip1) >>> 0) + 1) + \"\\n\\n\";\n    }\n\n    if (enumerateAddresses) {\n        if (cidrRange >= 16 || allowLargeList) {\n            output += generateIpv4Range(ip1, ip2).join(\"\\n\");\n        } else {\n            output += _LARGE_RANGE_ERROR;\n        }\n    }\n    return output;\n}\n\n/**\n * Parses an IPv6 CIDR range (e.g. ff00::/48) and displays information about it.\n *\n * @param {RegExp} cidr\n * @param {boolean} includeNetworkInfo\n * @returns {string}\n */\nexport function ipv6CidrRange(cidr, includeNetworkInfo) {\n    let output = \"\";\n    const network = strToIpv6(cidr[1]),\n        cidrRange = parseInt(cidr[cidr.length-1], 10);\n\n    if (cidrRange < 0 || cidrRange > 127) {\n        throw new OperationError(\"IPv6 CIDR must be less than 128\");\n    }\n\n    const ip1 = new Array(8),\n        ip2 = new Array(8),\n        total = new Array(128);\n\n    const mask = genIpv6Mask(cidrRange);\n    let totalDiff = \"\";\n\n\n    for (let i = 0; i < 8; i++) {\n        ip1[i] = network[i] & mask[i];\n        ip2[i] = ip1[i] | (~mask[i] & 0x0000FFFF);\n        totalDiff = (ip2[i] - ip1[i]).toString(2);\n\n        if (totalDiff !== \"0\") {\n            for (let n = 0; n < totalDiff.length; n++) {\n                total[i*16 + 16-(totalDiff.length-n)] = totalDiff[n];\n            }\n        }\n    }\n\n    if (includeNetworkInfo) {\n        output += \"Network: \" + ipv6ToStr(network) + \"\\n\";\n        output += \"Shorthand: \" + ipv6ToStr(network, true) + \"\\n\";\n        output += \"CIDR: \" + cidrRange + \"\\n\";\n        output += \"Mask: \" + ipv6ToStr(mask) + \"\\n\";\n        output += \"Range: \" + ipv6ToStr(ip1) + \" - \" + ipv6ToStr(ip2) + \"\\n\";\n        output += \"Total addresses in range: \" + (parseInt(total.join(\"\"), 2) + 1) + \"\\n\\n\";\n    }\n\n    return output;\n}\n\n/**\n * Parses an IPv4 hyphenated range (e.g. 192.168.0.0 - 192.168.0.255) and displays information\n * about it.\n *\n * @param {RegExp} range\n * @param {boolean} includeNetworkInfo\n * @param {boolean} enumerateAddresses\n * @param {boolean} allowLargeList\n * @returns {string}\n */\nexport function ipv4HyphenatedRange(range, includeNetworkInfo, enumerateAddresses, allowLargeList) {\n    const ip1 = strToIpv4(range[0].split(\"-\")[0].trim()),\n        ip2 = strToIpv4(range[0].split(\"-\")[1].trim());\n\n    let output = \"\";\n\n    // Calculate mask\n    let diff = ip1 ^ ip2,\n        cidr = 32,\n        mask = 0;\n\n    while (diff !== 0) {\n        diff >>= 1;\n        cidr--;\n        mask = (mask << 1) | 1;\n    }\n\n    mask = ~mask >>> 0;\n    const network = ip1 & mask,\n        subIp1 = network & mask,\n        subIp2 = subIp1 | ~mask;\n\n    if (includeNetworkInfo) {\n        output += `Minimum subnet required to hold this range:\n\\tNetwork: ${ipv4ToStr(network)}\n\\tCIDR: ${cidr}\n\\tMask: ${ipv4ToStr(mask)}\n\\tSubnet range: ${ipv4ToStr(subIp1)} - ${ipv4ToStr(subIp2)}\n\\tTotal addresses in subnet: ${(((subIp2 - subIp1) >>> 0) + 1)}\n\nRange: ${ipv4ToStr(ip1)} - ${ipv4ToStr(ip2)}\nTotal addresses in range: ${(((ip2 - ip1) >>> 0) + 1)}\n\n`;\n    }\n\n    if (enumerateAddresses) {\n        if (((ip2 - ip1) >>> 0) <= 65536 || allowLargeList) {\n            output += generateIpv4Range(ip1, ip2).join(\"\\n\");\n        } else {\n            output += _LARGE_RANGE_ERROR;\n        }\n    }\n    return output;\n}\n\n/**\n * Parses an IPv6 hyphenated range (e.g. ff00:: - ffff::) and displays information about it.\n *\n * @param {RegExp} range\n * @param {boolean} includeNetworkInfo\n * @returns {string}\n */\nexport function ipv6HyphenatedRange(range, includeNetworkInfo) {\n    const ip1 = strToIpv6(range[0].split(\"-\")[0].trim()),\n        ip2 = strToIpv6(range[0].split(\"-\")[1].trim()),\n        total = new Array(128).fill();\n\n    let output = \"\",\n        t = \"\",\n        i;\n\n    for (i = 0; i < 8; i++) {\n        t = (ip2[i] - ip1[i]).toString(2);\n        if (t !== \"0\") {\n            for (let n = 0; n < t.length; n++) {\n                total[i*16 + 16-(t.length-n)] = t[n];\n            }\n        }\n    }\n\n    if (includeNetworkInfo) {\n        output += \"Range: \" + ipv6ToStr(ip1) + \" - \" + ipv6ToStr(ip2) + \"\\n\";\n        output += \"Shorthand range: \" + ipv6ToStr(ip1, true) + \" - \" + ipv6ToStr(ip2, true) + \"\\n\";\n        output += \"Total addresses in range: \" + (parseInt(total.join(\"\"), 2) + 1) + \"\\n\\n\";\n    }\n\n    return output;\n}\n\n/**\n * Parses a list of IPv4 addresses separated by a new line (\\n) and displays information\n * about it.\n *\n * @param {RegExp} list\n * @param {boolean} includeNetworkInfo\n * @param {boolean} enumerateAddresses\n * @param {boolean} allowLargeList\n * @returns {string}\n */\nexport function ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList) {\n\n    let ipv4List = match[0].split(\"\\n\");\n    ipv4List = ipv4List.filter(Boolean);\n\n    const ipv4CidrList = ipv4List.filter(function(a) {\n        return a.includes(\"/\");\n    });\n    for (let i = 0; i < ipv4CidrList.length; i++) {\n        const network = strToIpv4(ipv4CidrList[i].split(\"/\")[0]);\n        const cidrRange = parseInt(ipv4CidrList[i].split(\"/\")[1], 10);\n        if (cidrRange < 0 || cidrRange > 31) {\n            throw new OperationError(\"IPv4 CIDR must be less than 32\");\n        }\n        const mask = ~(0xFFFFFFFF >>> cidrRange),\n            cidrIp1 = network & mask,\n            cidrIp2 = cidrIp1 | ~mask;\n        ipv4List.splice(ipv4List.indexOf(ipv4CidrList[i]), 1);\n        ipv4List.push(ipv4ToStr(cidrIp1), ipv4ToStr(cidrIp2));\n    }\n\n    ipv4List = ipv4List.sort(ipv4Compare);\n    const ip1 = ipv4List[0];\n    const ip2 = ipv4List[ipv4List.length - 1];\n    const range = [ip1 + \" - \" + ip2];\n    return ipv4HyphenatedRange(range, includeNetworkInfo, enumerateAddresses, allowLargeList);\n}\n\n/**\n * Parses a list of IPv6 addresses separated by a new line (\\n) and displays information\n * about it.\n *\n * @param {RegExp} list\n * @param {boolean} includeNetworkInfo\n * @returns {string}\n */\nexport function ipv6ListedRange(match, includeNetworkInfo) {\n\n    let ipv6List = match[0].split(\"\\n\");\n    ipv6List = ipv6List.filter(function(str) {\n        return str.trim();\n    });\n    for (let i =0; i < ipv6List.length; i++) {\n        ipv6List[i] = ipv6List[i].trim();\n    }\n    const ipv6CidrList = ipv6List.filter(function(a) {\n        return a.includes(\"/\");\n    });\n\n    for (let i = 0; i < ipv6CidrList.length; i++) {\n\n        const network = strToIpv6(ipv6CidrList[i].split(\"/\")[0]);\n        const cidrRange = parseInt(ipv6CidrList[i].split(\"/\")[1], 10);\n\n        if (cidrRange < 0 || cidrRange > 127) {\n            throw new OperationError(\"IPv6 CIDR must be less than 128\");\n        }\n\n        const cidrIp1 = new Array(8),\n            cidrIp2 = new Array(8);\n\n        const mask = genIpv6Mask(cidrRange);\n\n        for (let j = 0; j < 8; j++) {\n            cidrIp1[j] = network[j] & mask[j];\n            cidrIp2[j] = cidrIp1[j] | (~mask[j] & 0x0000FFFF);\n        }\n        ipv6List.splice(ipv6List.indexOf(ipv6CidrList[i]), 1);\n        ipv6List.push(ipv6ToStr(cidrIp1), ipv6ToStr(cidrIp2));\n    }\n    ipv6List = ipv6List.sort(ipv6Compare);\n    const ip1 = ipv6List[0];\n    const ip2 = ipv6List[ipv6List.length - 1];\n    const range = [ip1 + \" - \" + ip2];\n    return ipv6HyphenatedRange(range, includeNetworkInfo);\n}\n\n/**\n * Converts an IPv4 address from string format to numerical format.\n *\n * @param {string} ipStr\n * @returns {number}\n *\n * @example\n * // returns 168427520\n * strToIpv4(\"10.10.0.0\");\n */\nexport function strToIpv4(ipStr) {\n    const blocks = ipStr.split(\".\"),\n        numBlocks = parseBlocks(blocks);\n    let result = 0;\n\n    result += numBlocks[0] << 24;\n    result += numBlocks[1] << 16;\n    result += numBlocks[2] << 8;\n    result += numBlocks[3];\n\n    return result;\n\n    /**\n     * Converts a list of 4 numeric strings in the range 0-255 to a list of numbers.\n     */\n    function parseBlocks(blocks) {\n        if (blocks.length !== 4)\n            throw new OperationError(\"More than 4 blocks.\");\n\n        const numBlocks = [];\n        for (let i = 0; i < 4; i++) {\n            numBlocks[i] = parseInt(blocks[i], 10);\n            if (numBlocks[i] < 0 || numBlocks[i] > 255)\n                throw new OperationError(\"Block out of range.\");\n        }\n        return numBlocks;\n    }\n}\n\n/**\n * Converts an IPv4 address from numerical format to string format.\n *\n * @param {number} ipInt\n * @returns {string}\n *\n * @example\n * // returns \"10.10.0.0\"\n * ipv4ToStr(168427520);\n */\nexport function ipv4ToStr(ipInt) {\n    const blockA = (ipInt >> 24) & 255,\n        blockB = (ipInt >> 16) & 255,\n        blockC = (ipInt >> 8) & 255,\n        blockD = ipInt & 255;\n\n    return blockA + \".\" + blockB + \".\" + blockC + \".\" + blockD;\n}\n\n\n/**\n * Converts an IPv6 address from string format to numerical array format.\n *\n * @param {string} ipStr\n * @returns {number[]}\n *\n * @example\n * // returns [65280, 0, 0, 0, 0, 0, 4369, 8738]\n * strToIpv6(\"ff00::1111:2222\");\n */\nexport function strToIpv6(ipStr) {\n    let j = 0;\n    const blocks = ipStr.split(\":\"),\n        numBlocks = parseBlocks(blocks),\n        ipv6 = new Array(8);\n\n    for (let i = 0; i < 8; i++) {\n        if (isNaN(numBlocks[j])) {\n            ipv6[i] = 0;\n            if (i === (8-numBlocks.slice(j).length)) j++;\n        } else {\n            ipv6[i] = numBlocks[j];\n            j++;\n        }\n    }\n    return ipv6;\n\n    /**\n     * Converts a list of 3-8 numeric hex strings in the range 0-65535 to a list of numbers.\n     */\n    function parseBlocks(blocks) {\n        if (blocks.length < 3 || blocks.length > 8)\n            throw new OperationError(\"Badly formatted IPv6 address.\");\n        const numBlocks = [];\n        for (let i = 0; i < blocks.length; i++) {\n            numBlocks[i] = parseInt(blocks[i], 16);\n            if (numBlocks[i] < 0 || numBlocks[i] > 65535)\n                throw new OperationError(\"Block out of range.\");\n        }\n        return numBlocks;\n    }\n}\n\n/**\n * Converts an IPv6 address from numerical array format to string format.\n *\n * @param {number[]} ipv6\n * @param {boolean} compact - Whether or not to return the address in shorthand or not\n * @returns {string}\n *\n * @example\n * // returns \"ff00::1111:2222\"\n * ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], true);\n *\n * // returns \"ff00:0000:0000:0000:0000:0000:1111:2222\"\n * ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], false);\n */\nexport function ipv6ToStr(ipv6, compact) {\n    let output = \"\",\n        i = 0;\n\n    if (compact) {\n        let start = -1,\n            end = -1,\n            s = 0,\n            e = -1;\n\n        for (i = 0; i < 8; i++) {\n            if (ipv6[i] === 0 && e === (i-1)) {\n                e = i;\n            } else if (ipv6[i] === 0) {\n                s = i; e = i;\n            }\n            if (e >= 0 && (e-s) > (end - start)) {\n                start = s;\n                end = e;\n            }\n        }\n\n        for (i = 0; i < 8; i++) {\n            if (i !== start) {\n                output += Utils.hex(ipv6[i], 1) + \":\";\n            } else {\n                output += \":\";\n                i = end;\n                if (end === 7) output += \":\";\n            }\n        }\n        if (output[0] === \":\")\n            output = \":\" + output;\n    } else {\n        for (i = 0; i < 8; i++) {\n            output += Utils.hex(ipv6[i], 4) + \":\";\n        }\n    }\n    return output.slice(0, output.length-1);\n}\n\n/**\n * Generates a list of IPv4 addresses in string format between two given numerical values.\n *\n * @param {number} ip\n * @param {number} endIp\n * @returns {string[]}\n *\n * @example\n * // returns [\"0.0.0.1\", \"0.0.0.2\", \"0.0.0.3\"]\n * IP.generateIpv4Range(1, 3);\n */\nexport function generateIpv4Range(ip, endIp) {\n    const range = [];\n    if (endIp >= ip) {\n        for (; ip <= endIp; ip++) {\n            range.push(ipv4ToStr(ip));\n        }\n    } else {\n        range[0] = \"Second IP address smaller than first.\";\n    }\n    return range;\n}\n\n/**\n * Generates an IPv6 subnet mask given a CIDR value.\n *\n * @param {number} cidr\n * @returns {number[]}\n */\nexport function genIpv6Mask(cidr) {\n    const mask = new Array(8);\n    let shift;\n\n    for (let i = 0; i < 8; i++) {\n        if (cidr > ((i+1)*16)) {\n            mask[i] = 0x0000FFFF;\n        } else {\n            shift = cidr-(i*16);\n            if (shift < 0) shift = 0;\n            mask[i] = ~((0x0000FFFF >>> shift) | 0xFFFF0000);\n        }\n    }\n\n    return mask;\n}\n\n/**\n * Comparison operation for sorting of IPv4 addresses.\n *\n * @param {string} a\n * @param {string} b\n * @returns {number}\n */\nexport function ipv4Compare(a, b) {\n    return strToIpv4(a) - strToIpv4(b);\n}\n\n/**\n * Comparison operation for sorting of IPv6 addresses.\n *\n * @param {string} a\n * @param {string} b\n * @returns {number}\n */\nexport function ipv6Compare(a, b) {\n\n    const a_ = strToIpv6(a),\n        b_ = strToIpv6(b);\n\n    for (let i = 0; i < a_.length; i++) {\n        if (a_[i] !== b_[i]) {\n            return a_[i] - b_[i];\n        }\n    }\n    return 0;\n}\n\nconst _LARGE_RANGE_ERROR = \"The specified range contains more than 65,536 addresses. Running this query could crash your browser. If you want to run it, select the \\\"Allow large queries\\\" option. You are advised to turn off \\\"Auto Bake\\\" whilst editing large ranges.\";\n\n/**\n * A regular expression that matches an IPv4 address\n */\nexport const IPV4_REGEX = /^\\s*((?:\\d{1,3}\\.){3}\\d{1,3})\\s*$/;\n\n/**\n * A regular expression that matches an IPv6 address\n */\nexport const IPV6_REGEX = /^\\s*(((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\4)::|:\\b|(?![\\dA-F])))|(?!\\3\\4)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4}))\\s*$/i;\n\n/**\n * Lookup table for Internet Protocols.\n * Taken from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml\n */\nexport const protocolLookup = {\n    0: {keyword: \"HOPOPT\", protocol: \"IPv6 Hop-by-Hop Option\"},\n    1: {keyword: \"ICMP\", protocol: \"Internet Control Message\"},\n    2: {keyword: \"IGMP\", protocol: \"Internet Group Management\"},\n    3: {keyword: \"GGP\", protocol: \"Gateway-to-Gateway\"},\n    4: {keyword: \"IPv4\", protocol: \"IPv4 encapsulation\"},\n    5: {keyword: \"ST\", protocol: \"Stream\"},\n    6: {keyword: \"TCP\", protocol: \"Transmission Control\"},\n    7: {keyword: \"CBT\", protocol: \"CBT\"},\n    8: {keyword: \"EGP\", protocol: \"Exterior Gateway Protocol\"},\n    9: {keyword: \"IGP\", protocol: \"any private interior gateway (used by Cisco for their IGRP)\"},\n    10: {keyword: \"BBN-RCC-MON\", protocol: \"BBN RCC Monitoring\"},\n    11: {keyword: \"NVP-II\", protocol: \"Network Voice Protocol\"},\n    12: {keyword: \"PUP\", protocol: \"PUP\"},\n    13: {keyword: \"ARGUS (deprecated)\", protocol: \"ARGUS\"},\n    14: {keyword: \"EMCON\", protocol: \"EMCON\"},\n    15: {keyword: \"XNET\", protocol: \"Cross Net Debugger\"},\n    16: {keyword: \"CHAOS\", protocol: \"Chaos\"},\n    17: {keyword: \"UDP\", protocol: \"User Datagram\"},\n    18: {keyword: \"MUX\", protocol: \"Multiplexing\"},\n    19: {keyword: \"DCN-MEAS\", protocol: \"DCN Measurement Subsystems\"},\n    20: {keyword: \"HMP\", protocol: \"Host Monitoring\"},\n    21: {keyword: \"PRM\", protocol: \"Packet Radio Measurement\"},\n    22: {keyword: \"XNS-IDP\", protocol: \"XEROX NS IDP\"},\n    23: {keyword: \"TRUNK-1\", protocol: \"Trunk-1\"},\n    24: {keyword: \"TRUNK-2\", protocol: \"Trunk-2\"},\n    25: {keyword: \"LEAF-1\", protocol: \"Leaf-1\"},\n    26: {keyword: \"LEAF-2\", protocol: \"Leaf-2\"},\n    27: {keyword: \"RDP\", protocol: \"Reliable Data Protocol\"},\n    28: {keyword: \"IRTP\", protocol: \"Internet Reliable Transaction\"},\n    29: {keyword: \"ISO-TP4\", protocol: \"ISO Transport Protocol Class 4\"},\n    30: {keyword: \"NETBLT\", protocol: \"Bulk Data Transfer Protocol\"},\n    31: {keyword: \"MFE-NSP\", protocol: \"MFE Network Services Protocol\"},\n    32: {keyword: \"MERIT-INP\", protocol: \"MERIT Internodal Protocol\"},\n    33: {keyword: \"DCCP\", protocol: \"Datagram Congestion Control Protocol\"},\n    34: {keyword: \"3PC\", protocol: \"Third Party Connect Protocol\"},\n    35: {keyword: \"IDPR\", protocol: \"Inter-Domain Policy Routing Protocol\"},\n    36: {keyword: \"XTP\", protocol: \"XTP\"},\n    37: {keyword: \"DDP\", protocol: \"Datagram Delivery Protocol\"},\n    38: {keyword: \"IDPR-CMTP\", protocol: \"IDPR Control Message Transport Proto\"},\n    39: {keyword: \"TP++\", protocol: \"TP++ Transport Protocol\"},\n    40: {keyword: \"IL\", protocol: \"IL Transport Protocol\"},\n    41: {keyword: \"IPv6\", protocol: \"IPv6 encapsulation\"},\n    42: {keyword: \"SDRP\", protocol: \"Source Demand Routing Protocol\"},\n    43: {keyword: \"IPv6-Route\", protocol: \"Routing Header for IPv6\"},\n    44: {keyword: \"IPv6-Frag\", protocol: \"Fragment Header for IPv6\"},\n    45: {keyword: \"IDRP\", protocol: \"Inter-Domain Routing Protocol\"},\n    46: {keyword: \"RSVP\", protocol: \"Reservation Protocol\"},\n    47: {keyword: \"GRE\", protocol: \"Generic Routing Encapsulation\"},\n    48: {keyword: \"DSR\", protocol: \"Dynamic Source Routing Protocol\"},\n    49: {keyword: \"BNA\", protocol: \"BNA\"},\n    50: {keyword: \"ESP\", protocol: \"Encap Security Payload\"},\n    51: {keyword: \"AH\", protocol: \"Authentication Header\"},\n    52: {keyword: \"I-NLSP\", protocol: \"Integrated Net Layer Security  TUBA\"},\n    53: {keyword: \"SWIPE (deprecated)\", protocol: \"IP with Encryption\"},\n    54: {keyword: \"NARP\", protocol: \"NBMA Address Resolution Protocol\"},\n    55: {keyword: \"MOBILE\", protocol: \"IP Mobility\"},\n    56: {keyword: \"TLSP\", protocol: \"Transport Layer Security Protocol using Kryptonet key management\"},\n    57: {keyword: \"SKIP\", protocol: \"SKIP\"},\n    58: {keyword: \"IPv6-ICMP\", protocol: \"ICMP for IPv6\"},\n    59: {keyword: \"IPv6-NoNxt\", protocol: \"No Next Header for IPv6\"},\n    60: {keyword: \"IPv6-Opts\", protocol: \"Destination Options for IPv6\"},\n    61: {keyword: \"\", protocol: \"any host internal protocol\"},\n    62: {keyword: \"CFTP\", protocol: \"CFTP\"},\n    63: {keyword: \"\", protocol: \"any local network\"},\n    64: {keyword: \"SAT-EXPAK\", protocol: \"SATNET and Backroom EXPAK\"},\n    65: {keyword: \"KRYPTOLAN\", protocol: \"Kryptolan\"},\n    66: {keyword: \"RVD\", protocol: \"MIT Remote Virtual Disk Protocol\"},\n    67: {keyword: \"IPPC\", protocol: \"Internet Pluribus Packet Core\"},\n    68: {keyword: \"\", protocol: \"any distributed file system\"},\n    69: {keyword: \"SAT-MON\", protocol: \"SATNET Monitoring\"},\n    70: {keyword: \"VISA\", protocol: \"VISA Protocol\"},\n    71: {keyword: \"IPCV\", protocol: \"Internet Packet Core Utility\"},\n    72: {keyword: \"CPNX\", protocol: \"Computer Protocol Network Executive\"},\n    73: {keyword: \"CPHB\", protocol: \"Computer Protocol Heart Beat\"},\n    74: {keyword: \"WSN\", protocol: \"Wang Span Network\"},\n    75: {keyword: \"PVP\", protocol: \"Packet Video Protocol\"},\n    76: {keyword: \"BR-SAT-MON\", protocol: \"Backroom SATNET Monitoring\"},\n    77: {keyword: \"SUN-ND\", protocol: \"SUN ND PROTOCOL-Temporary\"},\n    78: {keyword: \"WB-MON\", protocol: \"WIDEBAND Monitoring\"},\n    79: {keyword: \"WB-EXPAK\", protocol: \"WIDEBAND EXPAK\"},\n    80: {keyword: \"ISO-IP\", protocol: \"ISO Internet Protocol\"},\n    81: {keyword: \"VMTP\", protocol: \"VMTP\"},\n    82: {keyword: \"SECURE-VMTP\", protocol: \"SECURE-VMTP\"},\n    83: {keyword: \"VINES\", protocol: \"VINES\"},\n    84: {keyword: \"TTP\", protocol: \"Transaction Transport Protocol\"},\n    85: {keyword: \"NSFNET-IGP\", protocol: \"NSFNET-IGP\"},\n    86: {keyword: \"DGP\", protocol: \"Dissimilar Gateway Protocol\"},\n    87: {keyword: \"TCF\", protocol: \"TCF\"},\n    88: {keyword: \"EIGRP\", protocol: \"EIGRP\"},\n    89: {keyword: \"OSPFIGP\", protocol: \"OSPFIGP\"},\n    90: {keyword: \"Sprite-RPC\", protocol: \"Sprite RPC Protocol\"},\n    91: {keyword: \"LARP\", protocol: \"Locus Address Resolution Protocol\"},\n    92: {keyword: \"MTP\", protocol: \"Multicast Transport Protocol\"},\n    93: {keyword: \"AX.25\", protocol: \"AX.25 Frames\"},\n    94: {keyword: \"IPIP\", protocol: \"IP-within-IP Encapsulation Protocol\"},\n    95: {keyword: \"MICP (deprecated)\", protocol: \"Mobile Internetworking Control Pro.\"},\n    96: {keyword: \"SCC-SP\", protocol: \"Semaphore Communications Sec. Pro.\"},\n    97: {keyword: \"ETHERIP\", protocol: \"Ethernet-within-IP Encapsulation\"},\n    98: {keyword: \"ENCAP\", protocol: \"Encapsulation Header\"},\n    99: {keyword: \"\", protocol: \"any private encryption scheme\"},\n    100: {keyword: \"GMTP\", protocol: \"GMTP\"},\n    101: {keyword: \"IFMP\", protocol: \"Ipsilon Flow Management Protocol\"},\n    102: {keyword: \"PNNI\", protocol: \"PNNI over IP\"},\n    103: {keyword: \"PIM\", protocol: \"Protocol Independent Multicast\"},\n    104: {keyword: \"ARIS\", protocol: \"ARIS\"},\n    105: {keyword: \"SCPS\", protocol: \"SCPS\"},\n    106: {keyword: \"QNX\", protocol: \"QNX\"},\n    107: {keyword: \"A/N\", protocol: \"Active Networks\"},\n    108: {keyword: \"IPComp\", protocol: \"IP Payload Compression Protocol\"},\n    109: {keyword: \"SNP\", protocol: \"Sitara Networks Protocol\"},\n    110: {keyword: \"Compaq-Peer\", protocol: \"Compaq Peer Protocol\"},\n    111: {keyword: \"IPX-in-IP\", protocol: \"IPX in IP\"},\n    112: {keyword: \"VRRP\", protocol: \"Virtual Router Redundancy Protocol\"},\n    113: {keyword: \"PGM\", protocol: \"PGM Reliable Transport Protocol\"},\n    114: {keyword: \"\", protocol: \"any 0-hop protocol\"},\n    115: {keyword: \"L2TP\", protocol: \"Layer Two Tunneling Protocol\"},\n    116: {keyword: \"DDX\", protocol: \"D-II Data Exchange (DDX)\"},\n    117: {keyword: \"IATP\", protocol: \"Interactive Agent Transfer Protocol\"},\n    118: {keyword: \"STP\", protocol: \"Schedule Transfer Protocol\"},\n    119: {keyword: \"SRP\", protocol: \"SpectraLink Radio Protocol\"},\n    120: {keyword: \"UTI\", protocol: \"UTI\"},\n    121: {keyword: \"SMP\", protocol: \"Simple Message Protocol\"},\n    122: {keyword: \"SM (deprecated)\", protocol: \"Simple Multicast Protocol\"},\n    123: {keyword: \"PTP\", protocol: \"Performance Transparency Protocol\"},\n    124: {keyword: \"ISIS over IPv4\", protocol: \"\"},\n    125: {keyword: \"FIRE\", protocol: \"\"},\n    126: {keyword: \"CRTP\", protocol: \"Combat Radio Transport Protocol\"},\n    127: {keyword: \"CRUDP\", protocol: \"Combat Radio User Datagram\"},\n    128: {keyword: \"SSCOPMCE\", protocol: \"\"},\n    129: {keyword: \"IPLT\", protocol: \"\"},\n    130: {keyword: \"SPS\", protocol: \"Secure Packet Shield\"},\n    131: {keyword: \"PIPE\", protocol: \"Private IP Encapsulation within IP\"},\n    132: {keyword: \"SCTP\", protocol: \"Stream Control Transmission Protocol\"},\n    133: {keyword: \"FC\", protocol: \"Fibre Channel\"},\n    134: {keyword: \"RSVP-E2E-IGNORE\", protocol: \"\"},\n    135: {keyword: \"Mobility Header\", protocol: \"\"},\n    136: {keyword: \"UDPLite\", protocol: \"\"},\n    137: {keyword: \"MPLS-in-IP\", protocol: \"\"},\n    138: {keyword: \"manet\", protocol: \"MANET Protocols\"},\n    139: {keyword: \"HIP\", protocol: \"Host Identity Protocol\"},\n    140: {keyword: \"Shim6\", protocol: \"Shim6 Protocol\"},\n    141: {keyword: \"WESP\", protocol: \"Wrapped Encapsulating Security Payload\"},\n    142: {keyword: \"ROHC\", protocol: \"Robust Header Compression\"},\n    253: {keyword: \"\", protocol: \"Use for experimentation and testing\"},\n    254: {keyword: \"\", protocol: \"Use for experimentation and testing\"},\n    255: {keyword: \"Reserved\", protocol: \"\"}\n};\n"
  },
  {
    "path": "src/core/lib/JA4.mjs",
    "content": "/**\n * JA4 resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n *\n * JA4 Copyright 2023 FoxIO, LLC.\n * @license BSD-3-Clause\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { parseTLSRecord, parseHighestSupportedVersion, parseFirstALPNValue } from \"./TLS.mjs\";\nimport { toHexFast } from \"./Hex.mjs\";\nimport { runHash } from \"./Hash.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n\n/**\n * Calculate the JA4 from a given TLS Client Hello Stream\n * @param {Uint8Array} bytes\n * @returns {string}\n */\nexport function toJA4(bytes) {\n    let tlsr = {};\n    try {\n        tlsr = parseTLSRecord(bytes);\n        if (tlsr.handshake.value.handshakeType.value !== 0x01) {\n            throw new Error();\n        }\n    } catch (err) {\n        throw new OperationError(\"Data is not a valid TLS Client Hello. QUIC is not yet supported.\\n\" + err);\n    }\n\n    /* QUIC\n        “q” or “t”, which denotes whether the hello packet is for QUIC or TCP.\n        TODO: Implement QUIC\n    */\n    const ptype = \"t\";\n\n    /* TLS Version\n        TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version\n        is the highest value in the extension. Remember to ignore GREASE values. If the extension doesn’t exist, then\n        the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)\n        should be ignored.\n    */\n    let version = tlsr.handshake.value.helloVersion.value;\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        if (ext.type.value === \"supported_versions\") {\n            version = parseHighestSupportedVersion(ext.value.data);\n            break;\n        }\n    }\n    version = tlsVersionMapper(version);\n\n    /* SNI\n        If the SNI extension (0x0000) exists, then the destination of the connection is a domain, or “d” in the fingerprint.\n        If the SNI does not exist, then the destination is an IP address, or “i”.\n    */\n    let sni = \"i\";\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        if (ext.type.value === \"server_name\") {\n            sni = \"d\";\n            break;\n        }\n    }\n\n    /* Number of Ciphers\n        2 character number of cipher suites, so if there’s 6 cipher suites in the hello packet, then the value should be “06”.\n        If there’s > 99, which there should never be, then output “99”. Remember, ignore GREASE values. They don’t count.\n    */\n    let cipherLen = 0;\n    for (const cs of tlsr.handshake.value.cipherSuites.value) {\n        if (cs.value !== \"GREASE\") cipherLen++;\n    }\n    cipherLen = cipherLen > 99 ? \"99\" : cipherLen.toString().padStart(2, \"0\");\n\n    /* Number of Extensions\n        Same as counting ciphers. Ignore GREASE. Include SNI and ALPN.\n    */\n    let extLen = 0;\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        if (ext.type.value !== \"GREASE\") extLen++;\n    }\n    extLen = extLen > 99 ? \"99\" : extLen.toString().padStart(2, \"0\");\n\n    /* ALPN Extension Value\n        The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value.\n        If there are no ALPN values or no ALPN extension then we print “00” as the value in the fingerprint.\n    */\n    let alpn = \"00\";\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        if (ext.type.value === \"application_layer_protocol_negotiation\") {\n            alpn = alpnFingerprint(parseFirstALPNValue(ext.value.data));\n            break;\n        }\n    }\n\n    /* Cipher hash\n        A 12 character truncated sha256 hash of the list of ciphers sorted in hex order, first 12 characters.\n        The list is created using the 4 character hex values of the ciphers, lower case, comma delimited, ignoring GREASE.\n    */\n    const originalCiphersList = [];\n    for (const cs of tlsr.handshake.value.cipherSuites.value) {\n        if (cs.value !== \"GREASE\") {\n            originalCiphersList.push(toHexFast(cs.data));\n        }\n    }\n    const sortedCiphersList = [...originalCiphersList].sort();\n    const sortedCiphersRaw = sortedCiphersList.join(\",\");\n    const originalCiphersRaw = originalCiphersList.join(\",\");\n    const sortedCiphers = runHash(\n        \"sha256\",\n        Utils.strToArrayBuffer(sortedCiphersRaw)\n    ).substring(0, 12);\n    const originalCiphers = runHash(\n        \"sha256\",\n        Utils.strToArrayBuffer(originalCiphersRaw)\n    ).substring(0, 12);\n\n    /* Extension hash\n        A 12 character truncated sha256 hash of the list of extensions, sorted by hex value, followed by the list of signature\n        algorithms, in the order that they appear (not sorted).\n        The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited, sorted\n        (not in the order they appear). Ignore the SNI extension (0000) and the ALPN extension (0010) as we’ve already captured\n        them in the a section of the fingerprint. These values are omitted so that the same application would have the same b\n        section of the fingerprint regardless of if it were going to a domain, IP, or changing ALPNs.\n    */\n    const originalExtensionsList = [];\n    let signatureAlgorithms = \"\";\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        if (ext.type.value !== \"GREASE\") {\n            originalExtensionsList.push(toHexFast(ext.type.data));\n        }\n        if (ext.type.value === \"signature_algorithms\") {\n            signatureAlgorithms = toHexFast(ext.value.data.slice(2));\n            signatureAlgorithms = signatureAlgorithms.replace(/(.{4})/g, \"$1,\");\n            signatureAlgorithms = signatureAlgorithms.substring(0, signatureAlgorithms.length - 1);\n        }\n    }\n    const sortedExtensionsList = [...originalExtensionsList].filter(e => e !== \"0000\" && e !== \"0010\").sort();\n    const sortedExtensionsRaw = sortedExtensionsList.join(\",\") + \"_\" + signatureAlgorithms;\n    const originalExtensionsRaw = originalExtensionsList.join(\",\") + \"_\" + signatureAlgorithms;\n    const sortedExtensions = runHash(\n        \"sha256\",\n        Utils.strToArrayBuffer(sortedExtensionsRaw)\n    ).substring(0, 12);\n    const originalExtensions = runHash(\n        \"sha256\",\n        Utils.strToArrayBuffer(originalExtensionsRaw)\n    ).substring(0, 12);\n\n    return {\n        \"JA4\":    `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${sortedCiphers}_${sortedExtensions}`,\n        \"JA4_o\":  `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphers}_${originalExtensions}`,\n        \"JA4_r\":  `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${sortedCiphersRaw}_${sortedExtensionsRaw}`,\n        \"JA4_ro\": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphersRaw}_${originalExtensionsRaw}`,\n    };\n}\n\n\n/**\n * Calculate the JA4Server from a given TLS Server Hello Stream\n * @param {Uint8Array} bytes\n * @returns {string}\n */\nexport function toJA4S(bytes) {\n    let tlsr = {};\n    try {\n        tlsr = parseTLSRecord(bytes);\n        if (tlsr.handshake.value.handshakeType.value !== 0x02) {\n            throw new Error();\n        }\n    } catch (err) {\n        throw new OperationError(\"Data is not a valid TLS Server Hello. QUIC is not yet supported.\\n\" + err);\n    }\n\n    /* QUIC\n        “q” or “t”, which denotes whether the hello packet is for QUIC or TCP.\n        TODO: Implement QUIC\n    */\n    const ptype = \"t\";\n\n    /* TLS Version\n        TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version\n        is the highest value in the extension. Remember to ignore GREASE values. If the extension doesn’t exist, then\n        the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)\n        should be ignored.\n    */\n    let version = tlsr.handshake.value.helloVersion.value;\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        if (ext.type.value === \"supported_versions\") {\n            version = parseHighestSupportedVersion(ext.value.data);\n            break;\n        }\n    }\n    version = tlsVersionMapper(version);\n\n    /* Number of Extensions\n        2 character number of cipher suites, so if there’s 6 cipher suites in the hello packet, then the value should be “06”.\n        If there’s > 99, which there should never be, then output “99”.\n    */\n    let extLen = tlsr.handshake.value.extensions.value.length;\n    extLen = extLen > 99 ? \"99\" : extLen.toString().padStart(2, \"0\");\n\n    /* ALPN Extension Chosen Value\n        The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value.\n        If there are no ALPN values or no ALPN extension then we print “00” as the value in the fingerprint.\n    */\n    let alpn = \"00\";\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        if (ext.type.value === \"application_layer_protocol_negotiation\") {\n            alpn = alpnFingerprint(parseFirstALPNValue(ext.value.data));\n            break;\n        }\n    }\n\n    /* Chosen Cipher\n        The hex value of the chosen cipher suite\n    */\n    const cipher = toHexFast(tlsr.handshake.value.cipherSuite.data);\n\n    /* Extension hash\n        A 12 character truncated sha256 hash of the list of extensions.\n        The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited.\n    */\n    const extensionsList = [];\n    for (const ext of tlsr.handshake.value.extensions.value) {\n        extensionsList.push(toHexFast(ext.type.data));\n    }\n    const extensionsRaw = extensionsList.join(\",\");\n    const extensionsHash = runHash(\n        \"sha256\",\n        Utils.strToArrayBuffer(extensionsRaw)\n    ).substring(0, 12);\n\n    return {\n        \"JA4S\":    `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsHash}`,\n        \"JA4S_r\":  `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsRaw}`,\n    };\n}\n\n\n/**\n * Takes a TLS version value and returns a JA4 TLS version string\n * @param {Uint8Array} version - Two byte array of version number\n * @returns {string}\n */\nfunction tlsVersionMapper(version) {\n    switch (version) {\n        case 0x0304: return \"13\"; // TLS 1.3\n        case 0x0303: return \"12\"; // TLS 1.2\n        case 0x0302: return \"11\"; // TLS 1.1\n        case 0x0301: return \"10\"; // TLS 1.0\n        case 0x0300: return \"s3\"; // SSL 3.0\n        case 0x0200: return \"s2\"; // SSL 2.0\n        case 0x0100: return \"s1\"; // SSL 1.0\n        default: return \"00\"; // Unknown\n    }\n}\n\n/**\n * Checks if a byte is ASCII alphanumeric (0-9, A-Z, a-z).\n * @param {number} byte\n * @returns {boolean}\n */\nfunction isAlphanumeric(byte) {\n    return (byte >= 0x30 && byte <= 0x39) ||\n           (byte >= 0x41 && byte <= 0x5A) ||\n           (byte >= 0x61 && byte <= 0x7A);\n}\n\n/**\n * Computes the 2-character ALPN fingerprint from raw ALPN bytes.\n * If both first and last bytes are ASCII alphanumeric, returns their characters.\n * Otherwise, returns first hex digit of first byte + last hex digit of last byte.\n * @param {Uint8Array|null} rawBytes\n * @returns {string}\n */\nfunction alpnFingerprint(rawBytes) {\n    if (!rawBytes || rawBytes.length === 0) return \"00\";\n    const firstByte = rawBytes[0];\n    const lastByte = rawBytes[rawBytes.length - 1];\n    if (isAlphanumeric(firstByte) && isAlphanumeric(lastByte)) {\n        return String.fromCharCode(firstByte) + String.fromCharCode(lastByte);\n    }\n    const firstHex = firstByte.toString(16).padStart(2, \"0\");\n    const lastHex = lastByte.toString(16).padStart(2, \"0\");\n    return firstHex[0] + lastHex[1];\n}\n"
  },
  {
    "path": "src/core/lib/JWT.mjs",
    "content": "/**\n * JWT resources\n *\n * @author mt3571 [mt3571@protonmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\n\n/**\n * List of the JWT algorithms that can be used\n */\nexport const JWT_ALGORITHMS = [\n    \"HS256\",\n    \"HS384\",\n    \"HS512\",\n    \"RS256\",\n    \"RS384\",\n    \"RS512\",\n    \"ES256\",\n    \"ES384\",\n    \"ES512\",\n    \"None\"\n];\n"
  },
  {
    "path": "src/core/lib/LS47.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\n\nconst letters = \"_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()\";\nconst tiles = [];\n\n/**\n * Initialises the tiles with values and positions.\n */\nexport function initTiles() {\n    for (let i = 0; i < 49; i++)\n        tiles.push([letters.charAt(i), [Math.floor(i/7), i % 7]]);\n}\n\n/**\n * Rotates the key \"down\".\n *\n * @param {string} key\n * @param {number} col\n * @param {number} n\n * @returns {string}\n */\nfunction rotateDown(key, col, n) {\n    const lines = [];\n    for (let i = 0; i < 7; i++)\n        lines.push(key.slice(i*7, (i + 1) * 7));\n    const lefts = [];\n    let mids = [];\n    const rights = [];\n    lines.forEach((element) => {\n        lefts.push(element.slice(0, col));\n        mids.push(element.charAt(col));\n        rights.push(element.slice(col+1));\n    });\n    n = (7 - n % 7) % 7;\n    mids = mids.slice(n).concat(mids.slice(0, n));\n    let result = \"\";\n    for (let i = 0; i < 7; i++)\n        result += lefts[i] + mids[i] + rights[i];\n    return result;\n}\n\n/**\n * Rotates the key \"right\".\n *\n * @param {string} key\n * @param {number} row\n * @param {number} n\n * @returns {string}\n */\nfunction rotateRight(key, row, n) {\n    const mid = key.slice(row * 7, (row + 1) * 7);\n    n = (7 - n % 7) % 7;\n    return key.slice(0, 7 * row) + mid.slice(n) + mid.slice(0, n) + key.slice(7 * (row + 1));\n}\n\n/**\n * Finds the position of a letter in the tiles.\n *\n * @param {string} letter\n * @returns {string}\n */\nfunction findIx(letter) {\n    for (let i = 0; i < tiles.length; i++)\n        if (tiles[i][0] === letter)\n            return tiles[i][1];\n    throw new OperationError(\"Letter \" + letter + \" is not included in LS47\");\n}\n\n/**\n * Derives key from the input password.\n *\n * @param {string} password\n * @returns {string}\n */\nexport function deriveKey(password) {\n    let i = 0;\n    let k = letters;\n    for (const c of password) {\n        const [row, col] = findIx(c);\n        k = rotateDown(rotateRight(k, i, col), i, row);\n        i = (i + 1) % 7;\n    }\n    return k;\n}\n\n/**\n * Checks the key is a valid key.\n *\n * @param {string} key\n */\nfunction checkKey(key) {\n    if (key.length !== letters.length)\n        throw new OperationError(\"Wrong key size\");\n    const counts = new Array();\n    for (let i = 0; i < letters.length; i++)\n        counts[letters.charAt(i)] = 0;\n    for (const elem of letters) {\n        if (letters.indexOf(elem) === -1)\n            throw new OperationError(\"Letter \" + elem + \" not in LS47\");\n        counts[elem]++;\n        if (counts[elem] > 1)\n            throw new OperationError(\"Letter duplicated in the key\");\n    }\n}\n\n/**\n * Finds the position of a letter in they key.\n *\n * @param {letter} key\n * @param {string} letter\n * @returns {object}\n */\nfunction findPos (key, letter) {\n    const index = key.indexOf(letter);\n    if (index >= 0 && index < 49)\n        return [Math.floor(index/7), index%7];\n    throw new OperationError(\"Letter \" + letter + \" is not in the key\");\n}\n\n/**\n * Returns the character at the position on the tiles.\n *\n * @param {string} key\n * @param {object} coord\n * @returns {string}\n */\nfunction findAtPos(key, coord) {\n    return key.charAt(coord[1] + (coord[0] * 7));\n}\n\n/**\n * Returns new position by adding two positions.\n *\n * @param {object} a\n * @param {object} b\n * @returns {object}\n */\nfunction addPos(a, b) {\n    return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7];\n}\n\n/**\n * Returns new position by subtracting two positions.\n * Note: We have to manually do the remainder division, since JS does not\n * operate correctly on negative numbers (e.g. -3 % 4 = -3 when it should be 1).\n *\n * @param {object} a\n * @param {object} b\n * @returns {object}\n */\nfunction subPos(a, b) {\n    const asub = a[0] - b[0];\n    const bsub = a[1] - b[1];\n    return [asub - (Math.floor(asub/7) * 7), bsub - (Math.floor(bsub/7) * 7)];\n}\n\n/**\n * Encrypts the plaintext string.\n *\n * @param {string} key\n * @param {string} plaintext\n * @returns {string}\n */\nfunction encrypt(key, plaintext) {\n    checkKey(key);\n    let mp = [0, 0];\n    let ciphertext = \"\";\n    for (const p of plaintext) {\n        const pp = findPos(key, p);\n        const mix = findIx(findAtPos(key, mp));\n        let cp = addPos(pp, mix);\n        const c = findAtPos(key, cp);\n        ciphertext += c;\n        key = rotateRight(key, pp[0], 1);\n        cp = findPos(key, c);\n        key = rotateDown(key, cp[1], 1);\n        mp = addPos(mp, findIx(c));\n    }\n    return ciphertext;\n}\n\n/**\n * Decrypts the ciphertext string.\n *\n * @param {string} key\n * @param {string} ciphertext\n * @returns {string}\n */\nfunction decrypt(key, ciphertext) {\n    checkKey(key);\n    let mp = [0, 0];\n    let plaintext = \"\";\n    for (const c of ciphertext) {\n        let cp = findPos(key, c);\n        const mix = findIx(findAtPos(key, mp));\n        const pp = subPos(cp, mix);\n        const p = findAtPos(key, pp);\n        plaintext += p;\n        key = rotateRight(key, pp[0], 1);\n        cp = findPos(key, c);\n        key = rotateDown(key, cp[1], 1);\n        mp = addPos(mp, findIx(c));\n    }\n    return plaintext;\n}\n\n/**\n * Adds padding to the input.\n *\n * @param {string} key\n * @param {string} plaintext\n * @param {string} signature\n * @param {number} paddingSize\n * @returns {string}\n */\nexport function encryptPad(key, plaintext, signature, paddingSize) {\n    initTiles();\n    checkKey(key);\n    let padding = \"\";\n    for (let i = 0; i < paddingSize; i++) {\n        padding += letters.charAt(Math.floor(Math.random() * letters.length));\n    }\n    return encrypt(key, padding+plaintext+\"---\"+signature);\n}\n\n/**\n * Removes padding from the ouput.\n *\n * @param {string} key\n * @param {string} ciphertext\n * @param {number} paddingSize\n * @returns {string}\n */\nexport function decryptPad(key, ciphertext, paddingSize) {\n    initTiles();\n    checkKey(key);\n    return decrypt(key, ciphertext).slice(paddingSize);\n}\n"
  },
  {
    "path": "src/core/lib/LZNT1.mjs",
    "content": "/**\n *\n * LZNT1 Decompress.\n *\n * @author 0xThiebaut [thiebaut.dev]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n *\n * https://github.com/Velocidex/go-ntfs/blob/master/parser%2Flznt1.go\n */\n\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\nconst COMPRESSED_MASK = 1 << 15,\n    SIZE_MASK = (1 << 12) - 1;\n\n/**\n * @param {number} offset\n * @returns {number}\n */\nfunction getDisplacement(offset) {\n    let result = 0;\n    while (offset >= 0x10) {\n        offset >>= 1;\n        result += 1;\n    }\n    return result;\n}\n\n/**\n * @param {byteArray} compressed\n * @returns {byteArray}\n */\nexport function decompress(compressed) {\n    const decompressed = Array();\n    let coffset = 0;\n\n    while (coffset + 2 <= compressed.length) {\n        const doffset = decompressed.length;\n\n        const blockHeader = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), \"little\");\n        coffset += 2;\n\n        const size = blockHeader & SIZE_MASK;\n        const blockEnd = coffset + size + 1;\n\n        if (size === 0) {\n            break;\n        } else if (compressed.length < coffset + size) {\n            throw new OperationError(\"Malformed LZNT1 stream: Block too small! Has the stream been truncated?\");\n        }\n\n        if ((blockHeader & COMPRESSED_MASK) !== 0) {\n            while (coffset < blockEnd) {\n                let header = compressed[coffset++];\n\n                for (let i = 0; i < 8 && coffset < blockEnd; i++) {\n                    if ((header & 1) === 0) {\n                        decompressed.push(compressed[coffset++]);\n                    } else {\n                        const pointer = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), \"little\");\n                        coffset += 2;\n\n                        const displacement = getDisplacement(decompressed.length - doffset - 1);\n                        const symbolOffset = (pointer >> (12 - displacement)) + 1;\n                        const symbolLength = (pointer & (0xFFF >> displacement)) + 2;\n                        const shiftOffset = decompressed.length - symbolOffset;\n\n                        for (let shiftDelta = 0; shiftDelta < symbolLength + 1; shiftDelta++) {\n                            const shift = shiftOffset + shiftDelta;\n                            if (shift < 0 || decompressed.length <= shift) {\n                                throw new OperationError(\"Malformed LZNT1 stream: Invalid shift!\");\n                            }\n                            decompressed.push(decompressed[shift]);\n                        }\n                    }\n                    header >>= 1;\n                }\n            }\n        } else {\n            decompressed.push(...compressed.slice(coffset, coffset + size + 1));\n            coffset += size + 1;\n        }\n    }\n\n    return decompressed;\n}\n"
  },
  {
    "path": "src/core/lib/LZString.mjs",
    "content": "/**\n * lz-string exports.\n *\n * @author crespyl [peter@crespyl.net]\n * @copyright Peter Jacobs 2021\n * @license Apache-2.0\n */\n\nimport LZString from \"lz-string\";\n\nexport const COMPRESSION_OUTPUT_FORMATS = [\"default\", \"UTF16\", \"Base64\"];\nexport const COMPRESSION_FUNCTIONS = {\n    \"default\": LZString.compress,\n    \"UTF16\":   LZString.compressToUTF16,\n    \"Base64\":  LZString.compressToBase64,\n};\nexport const DECOMPRESSION_FUNCTIONS = {\n    \"default\": LZString.decompress,\n    \"UTF16\":   LZString.decompressFromUTF16,\n    \"Base64\":  LZString.decompressFromBase64,\n};\n"
  },
  {
    "path": "src/core/lib/LoremIpsum.mjs",
    "content": "/**\n * Lorem Ipsum generator.\n *\n * @author Klaxon [klaxon@veyr.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/**\n * Generate lorem ipsum paragraphs.\n *\n * @param {number} length\n * @returns {string}\n */\nexport function GenerateParagraphs(length=3) {\n    const paragraphs = [];\n    while (paragraphs.length < length) {\n        const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);\n        const sentences = [];\n        while (sentences.length < paragraphLength) {\n            const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);\n            const sentence = getWords(sentenceLength);\n            sentences.push(formatSentence(sentence));\n        }\n        paragraphs.push(formatParagraph(sentences));\n    }\n    paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -2);\n    paragraphs[0] = replaceStart(paragraphs[0]);\n    return paragraphs.join(\"\");\n}\n\n\n/**\n * Generate lorem ipsum sentences.\n *\n * @param {number} length\n * @returns {string}\n */\nexport function GenerateSentences(length=3) {\n    const sentences = [];\n    while (sentences.length < length) {\n        const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);\n        const sentence = getWords(sentenceLength);\n        sentences.push(formatSentence(sentence));\n    }\n    const paragraphs = sentencesToParagraphs(sentences);\n    return paragraphs.join(\"\");\n}\n\n\n/**\n * Generate lorem ipsum words.\n *\n * @param {number} length\n * @returns {string}\n */\nexport function GenerateWords(length=3) {\n    const words = getWords(length);\n    const sentences = wordsToSentences(words);\n    const paragraphs = sentencesToParagraphs(sentences);\n    return paragraphs.join(\"\");\n}\n\n\n/**\n * Generate lorem ipsum bytes.\n *\n * @param {number} length\n * @returns {string}\n */\nexport function GenerateBytes(length=3) {\n    const str = GenerateWords(length/3);\n    return str.slice(0, length);\n}\n\n\n/**\n * Get array of randomly selected words from the lorem ipsum wordList.\n *\n * @param {number} length\n * @returns {string[]}\n * @private\n */\nfunction getWords(length=3) {\n    const words = [];\n    let word;\n    let previousWord;\n    while (words.length < length) {\n        do {\n            word = wordList[Math.floor(Math.random() * wordList.length)];\n        } while (previousWord === word);\n        words.push(word);\n        previousWord = word;\n    }\n    return words;\n}\n\n\n/**\n * Convert an array of words into an array of sentences\n *\n * @param {string[]} words\n * @returns {string[]}\n * @private\n */\nfunction wordsToSentences(words) {\n    const sentences = [];\n    while (words.length > 0) {\n        const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);\n        if (sentenceLength <= words.length) {\n            sentences.push(formatSentence(words.splice(0, sentenceLength)));\n        } else {\n            sentences.push(formatSentence(words.splice(0, words.length)));\n        }\n    }\n    return sentences;\n}\n\n\n/**\n * Convert an array of sentences into an array of paragraphs\n *\n * @param {string[]} sentences\n * @returns {string[]}\n * @private\n */\nfunction sentencesToParagraphs(sentences) {\n    const paragraphs = [];\n    while (sentences.length > 0) {\n        const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);\n        paragraphs.push(formatParagraph(sentences.splice(0, paragraphLength)));\n    }\n    paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -1);\n    paragraphs[0] = replaceStart(paragraphs[0]);\n    return paragraphs;\n}\n\n\n/**\n * Format an array of words into a sentence.\n *\n * @param {string[]} words\n * @returns {string}\n * @private\n */\nfunction formatSentence(words) {\n    // 0.35 chance of a  comma being added randomly to the sentence.\n    if (Math.random() < PROBABILITY_OF_A_COMMA) {\n        const pos = Math.round(Math.random()*(words.length-1));\n        words[pos] +=\",\";\n    }\n    let sentence = words.join(\" \");\n    sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1);\n    sentence += \".\";\n    return sentence;\n}\n\n\n/**\n * Format an array of sentences into a paragraph.\n *\n * @param {string[]} sentences\n * @returns {string}\n * @private\n */\nfunction formatParagraph(sentences) {\n    let paragraph = sentences.join(\" \");\n    paragraph += \"\\n\\n\";\n    return paragraph;\n}\n\n\n/**\n * Get a random number based on a mean and standard deviation.\n *\n * @param {number} mean\n * @param {number} stdDev\n * @returns {number}\n * @private\n */\nfunction getRandomLength(mean, stdDev) {\n    let length;\n    do {\n        length =  Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1)*stdDev+mean);\n    } while (length <= 0);\n    return length;\n}\n\n\n/**\n * Replace first 5 words with \"Lorem ipsum dolor sit amet\"\n *\n * @param {string[]} str\n * @returns {string[]}\n * @private\n */\nfunction replaceStart(str) {\n    let words = str.split(\" \");\n    if (words.length > 5) {\n        words.splice(0, 5, \"Lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\");\n        return words.join(\" \");\n    } else {\n        const lorem = [\"Lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\"];\n        words = lorem.slice(0, words.length);\n        str = words.join(\" \");\n        str += \".\";\n        return str;\n    }\n}\n\n\nconst SENTENCE_LENGTH_MEAN = 15;\nconst SENTENCE_LENGTH_STD_DEV = 9;\nconst PARAGRAPH_LENGTH_MEAN = 5;\nconst PARAGRAPH_LENGTH_STD_DEV = 2;\nconst PROBABILITY_OF_A_COMMA = 0.35;\n\nconst wordList = [\n    \"ad\", \"adipisicing\", \"aliqua\", \"aliquip\", \"amet\", \"anim\",\n    \"aute\", \"cillum\", \"commodo\", \"consectetur\", \"consequat\", \"culpa\",\n    \"cupidatat\", \"deserunt\", \"do\", \"dolor\", \"dolore\", \"duis\",\n    \"ea\", \"eiusmod\", \"elit\", \"enim\", \"esse\", \"est\",\n    \"et\", \"eu\", \"ex\", \"excepteur\", \"exercitation\", \"fugiat\",\n    \"id\", \"in\", \"incididunt\", \"ipsum\", \"irure\", \"labore\",\n    \"laboris\", \"laborum\", \"Lorem\", \"magna\", \"minim\", \"mollit\",\n    \"nisi\", \"non\", \"nostrud\", \"nulla\", \"occaecat\", \"officia\",\n    \"pariatur\", \"proident\", \"qui\", \"quis\", \"reprehenderit\", \"sint\",\n    \"sit\", \"sunt\", \"tempor\", \"ullamco\", \"ut\", \"velit\",\n    \"veniam\", \"voluptate\",\n];\n"
  },
  {
    "path": "src/core/lib/Lorenz.mjs",
    "content": "/**\n * Resources required by the Lorenz SZ40/42 and Colossus\n *\n * @author VirtualColossus [martin@virtualcolossus.co.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nexport const SWITCHES = [\n    {name: \"Up (.)\", value: \".\"},\n    {name: \"Centre\", value: \"\"},\n    {name: \"Down (x)\", value: \"x\"}\n];\n\nexport const VALID_ITA2 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ34589+-./\";\n\nexport const ITA2_TABLE = {\n    \"A\": \"11000\",\n    \"B\": \"10011\",\n    \"C\": \"01110\",\n    \"D\": \"10010\",\n    \"E\": \"10000\",\n    \"F\": \"10110\",\n    \"G\": \"01011\",\n    \"H\": \"00101\",\n    \"I\": \"01100\",\n    \"J\": \"11010\",\n    \"K\": \"11110\",\n    \"L\": \"01001\",\n    \"M\": \"00111\",\n    \"N\": \"00110\",\n    \"O\": \"00011\",\n    \"P\": \"01101\",\n    \"Q\": \"11101\",\n    \"R\": \"01010\",\n    \"S\": \"10100\",\n    \"T\": \"00001\",\n    \"U\": \"11100\",\n    \"V\": \"01111\",\n    \"W\": \"11001\",\n    \"X\": \"10111\",\n    \"Y\": \"10101\",\n    \"Z\": \"10001\",\n    \"3\": \"00010\",\n    \"4\": \"01000\",\n    \"9\": \"00100\",\n    \"/\": \"00000\",\n    \" \": \"00100\",\n    \".\": \"00100\",\n    \"8\": \"11111\",\n    \"5\": \"11011\",\n    \"-\": \"11111\",\n    \"+\": \"11011\"\n};\n\nexport const ROTOR_SIZES = {\n    S1: 43,\n    S2: 47,\n    S3: 51,\n    S4: 53,\n    S5: 59,\n    M37: 37,\n    M61: 61,\n    X1: 41,\n    X2: 31,\n    X3: 29,\n    X4: 26,\n    X5: 23\n};\n\n/**\n * Initial rotor patterns\n */\nexport const INIT_PATTERNS = {\n    \"No Pattern\": {\n        \"X\": {\n            1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n        },\n        \"S\": {\n            1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n        },\n        \"M\": {\n            1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n        }\n\n    },\n    \"KH Pattern\": {\n        \"X\": {\n            1: [0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0],\n            2: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0],\n            3: [0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0],\n            4: [1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0],\n            5: [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0]\n        },\n        \"S\": {\n            1: [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1],\n            2: [0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],\n            3: [0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1],\n            4: [0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],\n            5: [1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0]\n        },\n        \"M\":  {\n            1: [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0],\n            2: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0]\n        }\n    },\n    \"ZMUG Pattern\":  {\n        \"X\": {\n            1: [0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0],\n            2: [1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0],\n            3: [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0],\n            4: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1],\n            5: [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1]\n        },\n        \"S\": {\n            1: [1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0],\n            2: [0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1],\n            3: [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1],\n            4: [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1],\n            5: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0]\n        },\n        \"M\": {\n            1: [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1],\n            2: [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1]\n        }\n    },\n    \"BREAM Pattern\": {\n        \"X\": {\n            1: [0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0],\n            2: [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1],\n            3: [1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0],\n            4: [1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0],\n            5: [0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0]\n        },\n        \"S\": {\n            1: [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0],\n            2: [1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0],\n            3: [1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],\n            4: [0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1],\n            5: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]\n        },\n        \"M\": {\n            1: [1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1],\n            2: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1]\n        }\n    }\n};\n"
  },
  {
    "path": "src/core/lib/Magic.mjs",
    "content": "import OperationConfig from \"../config/OperationConfig.json\" assert {type: \"json\"};\nimport Utils, { isWorkerEnvironment } from \"../Utils.mjs\";\nimport Recipe from \"../Recipe.mjs\";\nimport Dish from \"../Dish.mjs\";\nimport {detectFileType, isType} from \"./FileType.mjs\";\nimport {isUTF8} from \"./ChrEnc.mjs\";\nimport chiSquared from \"chi-squared\";\n\n/**\n * A class for detecting encodings, file types and byte frequencies and\n * speculatively executing recipes.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nclass Magic {\n\n    /**\n     * Magic constructor.\n     *\n     * @param {ArrayBuffer} buf\n     * @param {Object[]} [opCriteria]\n     * @param {Object} [prevOp]\n     */\n    constructor(buf, opCriteria=Magic._generateOpCriteria(), prevOp=null) {\n        this.inputBuffer = new Uint8Array(buf);\n        this.inputStr = Utils.arrayBufferToStr(buf);\n        this.opCriteria = opCriteria;\n        this.prevOp = prevOp;\n    }\n\n    /**\n     * Finds operations that claim to be able to decode the input based on various criteria.\n     *\n     * @returns {Object[]}\n     */\n    findMatchingInputOps() {\n        const matches = [],\n            inputEntropy = this.calcEntropy();\n\n        this.opCriteria.forEach(check => {\n            // If the input doesn't lie in the required entropy range, move on\n            if (check.entropyRange &&\n                (inputEntropy < check.entropyRange[0] ||\n                inputEntropy > check.entropyRange[1]))\n                return;\n            // If the input doesn't match the pattern, move on\n            if (check.pattern &&\n                !check.pattern.test(this.inputStr))\n                return;\n\n            matches.push(check);\n        });\n\n        return matches;\n    }\n\n    /**\n     * Attempts to detect the language of the input by comparing its byte frequency\n     * to that of several known languages.\n     *\n     * @param {boolean} [extLang=false] - Extensive language support (false = only check the most\n     *                                    common Internet languages)\n     * @returns {Object[]}\n     */\n    detectLanguage(extLang = false) {\n        if (!this.inputBuffer.length) return [{\n            lang: \"Unknown\",\n            score: Math.MAX_VALUE,\n            probability: Math.MIN_VALUE\n        }];\n\n        const inputFreq = this._freqDist();\n        const langFreqs = extLang ? EXTENSIVE_LANG_FREQS : COMMON_LANG_FREQS;\n        const chiSqrs = [];\n\n        for (const lang in langFreqs) {\n            const [score, prob] = Magic._chiSqr(inputFreq, langFreqs[lang]);\n            chiSqrs.push({\n                lang: lang,\n                score: score,\n                probability: prob\n            });\n        }\n\n        // Sort results so that the most likely match is at the top\n        chiSqrs.sort((a, b) => {\n            return a.score - b.score;\n        });\n\n        return chiSqrs;\n    }\n\n    /**\n     * Detects any matching file types for the input.\n     *\n     * @returns {Object} type\n     * @returns {string} type.ext - File extension\n     * @returns {string} type.mime - Mime type\n     * @returns {string} [type.desc] - Description\n     */\n    detectFileType() {\n        const fileType = detectFileType(this.inputBuffer);\n\n        if (!fileType.length) return null;\n        return {\n            name: fileType[0].name,\n            ext: fileType[0].extension,\n            mime: fileType[0].mime,\n            desc: fileType[0].description\n        };\n    }\n\n    /**\n     * Calculates the Shannon entropy of the input data.\n     *\n     * @returns {number}\n     */\n    calcEntropy(data=this.inputBuffer, standalone=false) {\n        if (!standalone && this.inputEntropy) return this.inputEntropy;\n\n        const prob = this._freqDist(data, standalone);\n        let entropy = 0,\n            p;\n\n        for (let i = 0; i < prob.length; i++) {\n            p = prob[i] / 100;\n            if (p === 0) continue;\n            entropy += p * Math.log(p) / Math.log(2);\n        }\n\n        if (!standalone) this.inputEntropy = -entropy;\n        return -entropy;\n    }\n\n    /**\n     * Generate various simple brute-forced encodings of the data (trucated to 100 bytes).\n     *\n     * @returns {Object[]} - The encoded data and an operation config to generate it.\n     */\n    async bruteForce() {\n        const sample = new Uint8Array(this.inputBuffer).slice(0, 100);\n        const results = [];\n\n        // 1-byte XOR\n        for (let i = 1; i < 256; i++) {\n            results.push({\n                data: sample.map(b => b ^ i).buffer,\n                conf: {\n                    op: \"XOR\",\n                    args: [{\"option\": \"Hex\", \"string\": i.toString(16)}, \"Standard\", false]\n                }\n            });\n        }\n\n        // Bit rotate\n        for (let i = 1; i < 8; i++) {\n            results.push({\n                data: sample.map(b => (b >> i) | ((b & (Math.pow(2, i) - 1)) << (8 - i))).buffer,\n                conf: {\n                    op: \"Rotate right\",\n                    args: [i, false]\n                }\n            });\n        }\n\n        // Character encodings\n        const encodings = OperationConfig[\"Encode text\"].args[0].value;\n\n        /**\n         * Test character encodings and add them if they change the data.\n         */\n        const testEnc = async op => {\n            for (let i = 0; i < encodings.length; i++) {\n                const conf = {\n                    op: op,\n                    args: [encodings[i]]\n                };\n\n                try {\n                    const data = await this._runRecipe([conf], sample.buffer);\n\n                    // Only add to the results if it changed the data\n                    if (!_buffersEqual(data, sample.buffer)) {\n                        results.push({\n                            data: data,\n                            conf: conf\n                        });\n                    }\n                } catch (err) {\n                    continue;\n                }\n            }\n        };\n\n        await testEnc(\"Encode text\");\n        await testEnc(\"Decode text\");\n\n        return results;\n    }\n\n    /**\n     * Checks whether the data passes output criteria for an operation check\n     *\n     * @param {ArrayBuffer} data\n     * @param {Object} criteria\n     * @returns {boolean}\n     */\n    outputCheckPasses(data, criteria) {\n        if (criteria.pattern) {\n            const dataStr = Utils.arrayBufferToStr(data),\n                regex = new RegExp(criteria.pattern, criteria.flags);\n            if (!regex.test(dataStr))\n                return false;\n        }\n        if (criteria.entropyRange) {\n            const dataEntropy = this.calcEntropy(data, true);\n            if (dataEntropy < criteria.entropyRange[0] || dataEntropy > criteria.entropyRange[1])\n                return false;\n        }\n        if (criteria.mime &&\n            !isType(criteria.mime, data))\n            return false;\n\n        return true;\n    }\n\n    /**\n     * Speculatively executes matching operations, recording metadata of each result.\n     *\n     * @param {number} [depth=0] - How many levels to try to execute\n     * @param {boolean} [extLang=false] - Extensive language support (false = only check the most\n     *     common Internet languages)\n     * @param {boolean} [intensive=false] - Run brute-forcing on each branch (significantly affects\n     *     performance)\n     * @param {Object[]} [recipeConfig=[]] - The recipe configuration up to this point\n     * @param {boolean} [useful=false] - Whether the current recipe should be scored highly\n     * @param {string} [crib=null] - The regex crib provided by the user, for filtering the operation\n     *     output\n     * @returns {Object[]} - A sorted list of the recipes most likely to result in correct decoding\n     */\n    async speculativeExecution(\n        depth=0,\n        extLang=false,\n        intensive=false,\n        recipeConfig=[],\n        useful=false,\n        crib=null) {\n\n        // If we have reached the recursion depth, return\n        if (depth < 0) return [];\n\n        // Find any operations that can be run on this data\n        const matchingOps = this.findMatchingInputOps();\n        let results = [];\n\n        // Record the properties of the current data\n        results.push({\n            recipe: recipeConfig,\n            data: this.inputStr.slice(0, 100),\n            languageScores: this.detectLanguage(extLang),\n            fileType: this.detectFileType(),\n            isUTF8: !!isUTF8(this.inputBuffer),\n            entropy: this.calcEntropy(),\n            matchingOps: matchingOps,\n            useful: useful,\n            matchesCrib: crib && crib.test(this.inputStr)\n        });\n        const prevOp = recipeConfig[recipeConfig.length - 1];\n\n        // Execute each of the matching operations, then recursively call the speculativeExecution()\n        // method on the resulting data, recording the properties of each option.\n        await Promise.all(matchingOps.map(async op => {\n            const opConfig = {\n                    op: op.op,\n                    args: op.args\n                },\n                output = await this._runRecipe([opConfig]);\n\n            // If the recipe returned an empty buffer, do not continue\n            if (_buffersEqual(output, new ArrayBuffer())) {\n                return;\n            }\n\n            // If the recipe is repeating and returning the same data, do not continue\n            if (prevOp && op.op === prevOp.op && _buffersEqual(output, this.inputBuffer)) {\n                return;\n            }\n\n            // If the output criteria for this op doesn't match the output, do not continue\n            if (op.output && !this.outputCheckPasses(output, op.output))\n                return;\n\n            const magic = new Magic(output, this.opCriteria, OperationConfig[op.op]),\n                speculativeResults = await magic.speculativeExecution(\n                    depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);\n\n            results = results.concat(speculativeResults);\n        }));\n\n        if (intensive) {\n            // Run brute forcing of various types on the data and create a new branch for each option\n            const bfEncodings = await this.bruteForce();\n\n            await Promise.all(bfEncodings.map(async enc => {\n                const magic = new Magic(enc.data, this.opCriteria, undefined),\n                    bfResults = await magic.speculativeExecution(\n                        depth-1, extLang, false, [...recipeConfig, enc.conf], false, crib);\n\n                results = results.concat(bfResults);\n            }));\n        }\n\n        // Prune branches that result in unhelpful outputs\n        const prunedResults = results.filter(r =>\n            (r.useful || r.data.length > 0) &&          // The operation resulted in \"\"\n            (                                           // One of the following must be true\n                r.languageScores[0].probability > 0 ||    // Some kind of language was found\n                r.fileType ||                             // A file was found\n                r.isUTF8 ||                               // UTF-8 was found\n                r.matchingOps.length ||                   // A matching op was found\n                r.matchesCrib                             // The crib matches\n            )\n        );\n\n        // Return a sorted list of possible recipes along with their properties\n        return prunedResults.sort((a, b) => {\n            // Each option is sorted based on its most likely language (lower is better)\n            let aScore = a.languageScores[0].score,\n                bScore = b.languageScores[0].score;\n\n            // If the result is valid UTF8, its score gets boosted (lower being better)\n            if (a.isUTF8) aScore -= 100;\n            if (b.isUTF8) bScore -= 100;\n\n            // If a recipe results in a file being detected, it receives a relatively good score\n            if (a.fileType && aScore > 500) aScore = 500;\n            if (b.fileType && bScore > 500) bScore = 500;\n\n            // If the option is marked useful, give it a good score\n            if (a.useful && aScore > 100) aScore = 100;\n            if (b.useful && bScore > 100) bScore = 100;\n\n            // Shorter recipes are better, so we add the length of the recipe to the score\n            aScore += a.recipe.length;\n            bScore += b.recipe.length;\n\n            // Lower entropy is \"better\", so we add the entropy to the score\n            aScore += a.entropy;\n            bScore += b.entropy;\n\n            // A result with no recipe but matching ops suggests there are better options\n            if ((!a.recipe.length && a.matchingOps.length) && b.recipe.length)\n                return 1;\n            if ((!b.recipe.length && b.matchingOps.length) && a.recipe.length)\n                return -1;\n\n            return aScore - bScore;\n        });\n    }\n\n    /**\n     * Runs the given recipe over the input buffer and returns the output.\n     *\n     * @param {Object[]} recipeConfig\n     * @param {ArrayBuffer} [input=this.inputBuffer]\n     * @returns {ArrayBuffer}\n     */\n    async _runRecipe(recipeConfig, input=this.inputBuffer) {\n        input = input instanceof ArrayBuffer ? input : input.buffer;\n        const dish = new Dish();\n        dish.set(input, Dish.ARRAY_BUFFER);\n\n        if (isWorkerEnvironment()) self.loadRequiredModules(recipeConfig);\n\n        const recipe = new Recipe(recipeConfig);\n        try {\n            await recipe.execute(dish);\n            // Return an empty buffer if the recipe did not run to completion\n            if (recipe.lastRunOp === recipe.opList[recipe.opList.length - 1]) {\n                return await dish.get(Dish.ARRAY_BUFFER);\n            } else {\n                return new ArrayBuffer();\n            }\n        } catch (err) {\n            // If there are errors, return an empty buffer\n            return new ArrayBuffer();\n        }\n    }\n\n    /**\n     * Calculates the number of times each byte appears in the input as a percentage\n     *\n     * @private\n     * @param {ArrayBuffer} [data]\n     * @param {boolean} [standalone]\n     * @returns {number[]}\n     */\n    _freqDist(data=this.inputBuffer, standalone=false) {\n        if (!standalone && this.freqDist) return this.freqDist;\n\n        const len = data.length,\n            counts = new Array(256).fill(0);\n        let i = len;\n\n        if (!len) {\n            this.freqDist = counts;\n            return this.freqDist;\n        }\n\n        while (i--) {\n            counts[data[i]]++;\n        }\n\n        const result = counts.map(c => {\n            return c / len * 100;\n        });\n\n        if (!standalone) this.freqDist = result;\n        return result;\n    }\n\n    /**\n     * Generates a list of all patterns that operations claim to be able to decode.\n     *\n     * @private\n     * @returns {Object[]}\n     */\n    static _generateOpCriteria() {\n        const opCriteria = [];\n\n        for (const op in OperationConfig) {\n            if (!(\"checks\" in OperationConfig[op]))\n                continue;\n\n            OperationConfig[op].checks.forEach(check => {\n                // Add to the opCriteria list.\n                // Compile the regex here and cache the compiled version so we\n                // don't have to keep calculating it.\n                opCriteria.push({\n                    op: op,\n                    pattern: check.pattern ? new RegExp(check.pattern, check.flags) : null,\n                    args: check.args,\n                    useful: check.useful,\n                    entropyRange: check.entropyRange,\n                    output: check.output\n                });\n            });\n        }\n\n        return opCriteria;\n    }\n\n    /**\n     * Calculates Pearson's Chi-Squared test for two frequency arrays.\n     * https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test\n     *\n     * @private\n     * @param {number[]} observed\n     * @param {number[]} expected\n     * @param {number} ddof - Delta degrees of freedom\n     * @returns {number[]} - The score and the probability\n     */\n    static _chiSqr(observed, expected, ddof=0) {\n        let tmp,\n            score = 0;\n\n        for (let i = 0; i < observed.length; i++) {\n            tmp = observed[i] - expected[i];\n            score += tmp * tmp / expected[i];\n        }\n\n        return [\n            score,\n            1 - chiSquared.cdf(score, observed.length - 1 - ddof)\n        ];\n    }\n\n    /**\n     * Translates ISO 639(-ish) codes to their full language names as used by Wikipedia\n     * Accurate up to 2018-02\n     * Taken from http://wikistats.wmflabs.org/display.php?t=wp\n     *\n     * @param {string} code - ISO 639 code\n     * @returns {string} The full name of the language\n     */\n    static codeToLanguage(code) {\n        return {\n            \"aa\":  \"Afar\",\n            \"ab\":  \"Abkhazian\",\n            \"ace\": \"Acehnese\",\n            \"ady\": \"Adyghe\",\n            \"af\":  \"Afrikaans\",\n            \"ak\":  \"Akan\",\n            \"als\": \"Alemannic\",\n            \"am\":  \"Amharic\",\n            \"an\":  \"Aragonese\",\n            \"ang\": \"Anglo-Saxon\",\n            \"ar\":  \"Arabic\",\n            \"arc\": \"Aramaic\",\n            \"arz\": \"Egyptian Arabic\",\n            \"as\":  \"Assamese\",\n            \"ast\": \"Asturian\",\n            \"atj\": \"Atikamekw\",\n            \"av\":  \"Avar\",\n            \"ay\":  \"Aymara\",\n            \"az\":  \"Azerbaijani\",\n            \"azb\": \"South Azerbaijani\",\n            \"ba\":  \"Bashkir\",\n            \"bar\": \"Bavarian\",\n            \"bat-smg\": \"Samogitian\",\n            \"bcl\": \"Central_Bicolano\",\n            \"be\":  \"Belarusian\",\n            \"be-tarask\": \"Belarusian (Taraškievica)\",\n            \"bg\":  \"Bulgarian\",\n            \"bh\":  \"Bihari\",\n            \"bi\":  \"Bislama\",\n            \"bjn\": \"Banjar\",\n            \"bm\":  \"Bambara\",\n            \"bn\":  \"Bengali\",\n            \"bo\":  \"Tibetan\",\n            \"bpy\": \"Bishnupriya Manipuri\",\n            \"br\":  \"Breton\",\n            \"bs\":  \"Bosnian\",\n            \"bug\": \"Buginese\",\n            \"bxr\": \"Buryat (Russia)\",\n            \"ca\":  \"Catalan\",\n            \"cbk-zam\": \"Zamboanga Chavacano\",\n            \"cdo\": \"Min Dong\",\n            \"ce\":  \"Chechen\",\n            \"ceb\": \"Cebuano\",\n            \"ch\":  \"Chamorro\",\n            \"cho\": \"Choctaw\",\n            \"chr\": \"Cherokee\",\n            \"chy\": \"Cheyenne\",\n            \"ckb\": \"Sorani\",\n            \"co\":  \"Corsican\",\n            \"cr\":  \"Cree\",\n            \"crh\": \"Crimean Tatar\",\n            \"cs\":  \"Czech\",\n            \"csb\": \"Kashubian\",\n            \"cu\":  \"Old Church Slavonic\",\n            \"cv\":  \"Chuvash\",\n            \"cy\":  \"Welsh\",\n            \"da\":  \"Danish\",\n            \"de\":  \"German\",\n            \"din\": \"Dinka\",\n            \"diq\": \"Zazaki\",\n            \"dsb\": \"Lower Sorbian\",\n            \"dty\": \"Doteli\",\n            \"dv\":  \"Divehi\",\n            \"dz\":  \"Dzongkha\",\n            \"ee\":  \"Ewe\",\n            \"el\":  \"Greek\",\n            \"eml\": \"Emilian-Romagnol\",\n            \"en\":  \"English\",\n            \"eo\":  \"Esperanto\",\n            \"es\":  \"Spanish\",\n            \"et\":  \"Estonian\",\n            \"eu\":  \"Basque\",\n            \"ext\": \"Extremaduran\",\n            \"fa\":  \"Persian\",\n            \"ff\":  \"Fula\",\n            \"fi\":  \"Finnish\",\n            \"fiu-vro\": \"Võro\",\n            \"fj\":  \"Fijian\",\n            \"fo\":  \"Faroese\",\n            \"fr\":  \"French\",\n            \"frp\": \"Franco-Provençal/Arpitan\",\n            \"frr\": \"North Frisian\",\n            \"fur\": \"Friulian\",\n            \"fy\":  \"West Frisian\",\n            \"ga\":  \"Irish\",\n            \"gag\": \"Gagauz\",\n            \"gan\": \"Gan\",\n            \"gd\":  \"Scottish Gaelic\",\n            \"gl\":  \"Galician\",\n            \"glk\": \"Gilaki\",\n            \"gn\":  \"Guarani\",\n            \"gom\": \"Goan Konkani\",\n            \"got\": \"Gothic\",\n            \"gu\":  \"Gujarati\",\n            \"gv\":  \"Manx\",\n            \"ha\":  \"Hausa\",\n            \"hak\": \"Hakka\",\n            \"haw\": \"Hawaiian\",\n            \"he\":  \"Hebrew\",\n            \"hi\":  \"Hindi\",\n            \"hif\": \"Fiji Hindi\",\n            \"ho\":  \"Hiri Motu\",\n            \"hr\":  \"Croatian\",\n            \"hsb\": \"Upper Sorbian\",\n            \"ht\":  \"Haitian\",\n            \"hu\":  \"Hungarian\",\n            \"hy\":  \"Armenian\",\n            \"hz\":  \"Herero\",\n            \"ia\":  \"Interlingua\",\n            \"id\":  \"Indonesian\",\n            \"ie\":  \"Interlingue\",\n            \"ig\":  \"Igbo\",\n            \"ii\":  \"Sichuan Yi\",\n            \"ik\":  \"Inupiak\",\n            \"ilo\": \"Ilokano\",\n            \"io\":  \"Ido\",\n            \"is\":  \"Icelandic\",\n            \"it\":  \"Italian\",\n            \"iu\":  \"Inuktitut\",\n            \"ja\":  \"Japanese\",\n            \"jam\": \"Jamaican\",\n            \"jbo\": \"Lojban\",\n            \"jv\":  \"Javanese\",\n            \"ka\":  \"Georgian\",\n            \"kaa\": \"Karakalpak\",\n            \"kab\": \"Kabyle\",\n            \"kbd\": \"Kabardian Circassian\",\n            \"kbp\": \"Kabiye\",\n            \"kg\":  \"Kongo\",\n            \"ki\":  \"Kikuyu\",\n            \"kj\":  \"Kuanyama\",\n            \"kk\":  \"Kazakh\",\n            \"kl\":  \"Greenlandic\",\n            \"km\":  \"Khmer\",\n            \"kn\":  \"Kannada\",\n            \"ko\":  \"Korean\",\n            \"koi\": \"Komi-Permyak\",\n            \"kr\":  \"Kanuri\",\n            \"krc\": \"Karachay-Balkar\",\n            \"ks\":  \"Kashmiri\",\n            \"ksh\": \"Ripuarian\",\n            \"ku\":  \"Kurdish\",\n            \"kv\":  \"Komi\",\n            \"kw\":  \"Cornish\",\n            \"ky\":  \"Kirghiz\",\n            \"la\":  \"Latin\",\n            \"lad\": \"Ladino\",\n            \"lb\":  \"Luxembourgish\",\n            \"lbe\": \"Lak\",\n            \"lez\": \"Lezgian\",\n            \"lg\":  \"Luganda\",\n            \"li\":  \"Limburgish\",\n            \"lij\": \"Ligurian\",\n            \"lmo\": \"Lombard\",\n            \"ln\":  \"Lingala\",\n            \"lo\":  \"Lao\",\n            \"lrc\": \"Northern Luri\",\n            \"lt\":  \"Lithuanian\",\n            \"ltg\": \"Latgalian\",\n            \"lv\":  \"Latvian\",\n            \"mai\": \"Maithili\",\n            \"map-bms\": \"Banyumasan\",\n            \"mdf\": \"Moksha\",\n            \"mg\":  \"Malagasy\",\n            \"mh\":  \"Marshallese\",\n            \"mhr\": \"Meadow Mari\",\n            \"mi\":  \"Maori\",\n            \"min\": \"Minangkabau\",\n            \"mk\":  \"Macedonian\",\n            \"ml\":  \"Malayalam\",\n            \"mn\":  \"Mongolian\",\n            \"mo\":  \"Moldovan\",\n            \"mr\":  \"Marathi\",\n            \"mrj\": \"Hill Mari\",\n            \"ms\":  \"Malay\",\n            \"mt\":  \"Maltese\",\n            \"mus\": \"Muscogee\",\n            \"mwl\": \"Mirandese\",\n            \"my\":  \"Burmese\",\n            \"myv\": \"Erzya\",\n            \"mzn\": \"Mazandarani\",\n            \"na\":  \"Nauruan\",\n            \"nah\": \"Nahuatl\",\n            \"nap\": \"Neapolitan\",\n            \"nds\": \"Low Saxon\",\n            \"nds-nl\": \"Dutch Low Saxon\",\n            \"ne\":  \"Nepali\",\n            \"new\": \"Newar / Nepal Bhasa\",\n            \"ng\":  \"Ndonga\",\n            \"nl\":  \"Dutch\",\n            \"nn\":  \"Norwegian (Nynorsk)\",\n            \"no\":  \"Norwegian (Bokmål)\",\n            \"nov\": \"Novial\",\n            \"nrm\": \"Norman\",\n            \"nso\": \"Northern Sotho\",\n            \"nv\":  \"Navajo\",\n            \"ny\":  \"Chichewa\",\n            \"oc\":  \"Occitan\",\n            \"olo\": \"Livvi-Karelian\",\n            \"om\":  \"Oromo\",\n            \"or\":  \"Oriya\",\n            \"os\":  \"Ossetian\",\n            \"pa\":  \"Punjabi\",\n            \"pag\": \"Pangasinan\",\n            \"pam\": \"Kapampangan\",\n            \"pap\": \"Papiamentu\",\n            \"pcd\": \"Picard\",\n            \"pdc\": \"Pennsylvania German\",\n            \"pfl\": \"Palatinate German\",\n            \"pi\":  \"Pali\",\n            \"pih\": \"Norfolk\",\n            \"pl\":  \"Polish\",\n            \"pms\": \"Piedmontese\",\n            \"pnb\": \"Western Panjabi\",\n            \"pnt\": \"Pontic\",\n            \"ps\":  \"Pashto\",\n            \"pt\":  \"Portuguese\",\n            \"qu\":  \"Quechua\",\n            \"rm\":  \"Romansh\",\n            \"rmy\": \"Romani\",\n            \"rn\":  \"Kirundi\",\n            \"ro\":  \"Romanian\",\n            \"roa-rup\": \"Aromanian\",\n            \"roa-tara\": \"Tarantino\",\n            \"ru\":  \"Russian\",\n            \"rue\": \"Rusyn\",\n            \"rw\":  \"Kinyarwanda\",\n            \"sa\":  \"Sanskrit\",\n            \"sah\": \"Sakha\",\n            \"sc\":  \"Sardinian\",\n            \"scn\": \"Sicilian\",\n            \"sco\": \"Scots\",\n            \"sd\":  \"Sindhi\",\n            \"se\":  \"Northern Sami\",\n            \"sg\":  \"Sango\",\n            \"sh\":  \"Serbo-Croatian\",\n            \"si\":  \"Sinhalese\",\n            \"simple\": \"Simple English\",\n            \"sk\":  \"Slovak\",\n            \"sl\":  \"Slovenian\",\n            \"sm\":  \"Samoan\",\n            \"sn\":  \"Shona\",\n            \"so\":  \"Somali\",\n            \"sq\":  \"Albanian\",\n            \"sr\":  \"Serbian\",\n            \"srn\": \"Sranan\",\n            \"ss\":  \"Swati\",\n            \"st\":  \"Sesotho\",\n            \"stq\": \"Saterland Frisian\",\n            \"su\":  \"Sundanese\",\n            \"sv\":  \"Swedish\",\n            \"sw\":  \"Swahili\",\n            \"szl\": \"Silesian\",\n            \"ta\":  \"Tamil\",\n            \"tcy\": \"Tulu\",\n            \"te\":  \"Telugu\",\n            \"tet\": \"Tetum\",\n            \"tg\":  \"Tajik\",\n            \"th\":  \"Thai\",\n            \"ti\":  \"Tigrinya\",\n            \"tk\":  \"Turkmen\",\n            \"tl\":  \"Tagalog\",\n            \"tn\":  \"Tswana\",\n            \"to\":  \"Tongan\",\n            \"tpi\": \"Tok Pisin\",\n            \"tr\":  \"Turkish\",\n            \"ts\":  \"Tsonga\",\n            \"tt\":  \"Tatar\",\n            \"tum\": \"Tumbuka\",\n            \"tw\":  \"Twi\",\n            \"ty\":  \"Tahitian\",\n            \"tyv\": \"Tuvan\",\n            \"udm\": \"Udmurt\",\n            \"ug\":  \"Uyghur\",\n            \"uk\":  \"Ukrainian\",\n            \"ur\":  \"Urdu\",\n            \"uz\":  \"Uzbek\",\n            \"ve\":  \"Venda\",\n            \"vec\": \"Venetian\",\n            \"vep\": \"Vepsian\",\n            \"vi\":  \"Vietnamese\",\n            \"vls\": \"West Flemish\",\n            \"vo\":  \"Volapük\",\n            \"wa\":  \"Walloon\",\n            \"war\": \"Waray-Waray\",\n            \"wo\":  \"Wolof\",\n            \"wuu\": \"Wu\",\n            \"xal\": \"Kalmyk\",\n            \"xh\":  \"Xhosa\",\n            \"xmf\": \"Mingrelian\",\n            \"yi\":  \"Yiddish\",\n            \"yo\":  \"Yoruba\",\n            \"za\":  \"Zhuang\",\n            \"zea\": \"Zeelandic\",\n            \"zh\":  \"Chinese\",\n            \"zh-classical\": \"Classical Chinese\",\n            \"zh-min-nan\": \"Min Nan\",\n            \"zh-yue\": \"Cantonese\",\n            \"zu\":  \"Zulu\",\n        }[code];\n    }\n\n}\n\n\n/**\n * Byte frequencies of various languages generated from Wikipedia dumps taken in late 2017 and early 2018.\n * The Chi-Squared test cannot accept expected values of 0, so 0.0001 has been used to account for bytes\n * that do not normally appear in the language.\n *\n * The common languages are chosen based on https://w3techs.com/technologies/overview/content_language/all\n * as of early 2018.\n */\nconst COMMON_LANG_FREQS = {\n    \"en\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.755, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.843, 0.004, 0.375, 0.002, 0.008, 0.019, 0.008, 0.134, 0.137, 0.137, 0.001, 0.001, 0.972, 0.19, 0.857, 0.017, 0.334, 0.421, 0.246, 0.108, 0.104, 0.112, 0.103, 0.1, 0.127, 0.237, 0.04, 0.027, 0.004, 0.003, 0.004, 0.002, 0.0001, 0.338, 0.218, 0.326, 0.163, 0.121, 0.149, 0.133, 0.192, 0.232, 0.107, 0.082, 0.148, 0.248, 0.134, 0.103, 0.195, 0.012, 0.162, 0.368, 0.366, 0.077, 0.061, 0.127, 0.009, 0.03, 0.015, 0.004, 0.0001, 0.004, 0.0001, 0.003, 0.0001, 6.614, 1.039, 2.327, 2.934, 9.162, 1.606, 1.415, 3.503, 5.718, 0.081, 0.461, 3.153, 1.793, 5.723, 5.565, 1.415, 0.066, 5.036, 4.79, 6.284, 1.992, 0.759, 1.176, 0.139, 1.162, 0.102, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.06, 0.004, 0.003, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.003, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.031, 0.006, 0.001, 0.001, 0.001, 0.002, 0.014, 0.001, 0.001, 0.005, 0.005, 0.001, 0.002, 0.017, 0.007, 0.002, 0.003, 0.004, 0.002, 0.001, 0.002, 0.002, 0.012, 0.001, 0.002, 0.001, 0.004, 0.001, 0.001, 0.003, 0.003, 0.002, 0.005, 0.001, 0.001, 0.003, 0.001, 0.003, 0.001, 0.002, 0.001, 0.004, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.02, 0.047, 0.009, 0.009, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.061, 0.001, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ru\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.512, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.274, 0.002, 0.063, 0.0001, 0.001, 0.009, 0.001, 0.001, 0.118, 0.118, 0.0001, 0.001, 0.595, 0.135, 0.534, 0.009, 0.18, 0.281, 0.15, 0.078, 0.076, 0.077, 0.068, 0.066, 0.083, 0.16, 0.036, 0.016, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.013, 0.009, 0.014, 0.009, 0.007, 0.006, 0.007, 0.006, 0.031, 0.002, 0.003, 0.007, 0.012, 0.007, 0.005, 0.01, 0.001, 0.008, 0.017, 0.011, 0.003, 0.009, 0.005, 0.012, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.003, 0.0001, 0.065, 0.009, 0.022, 0.021, 0.074, 0.01, 0.013, 0.019, 0.054, 0.001, 0.008, 0.036, 0.02, 0.047, 0.055, 0.013, 0.001, 0.052, 0.037, 0.041, 0.026, 0.007, 0.006, 0.003, 0.011, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.469, 2.363, 2.342, 0.986, 0.156, 0.422, 0.252, 0.495, 0.217, 0.136, 0.014, 0.778, 0.56, 0.097, 0.251, 0.811, 0.09, 0.184, 0.165, 0.06, 0.179, 0.021, 0.013, 0.029, 0.05, 0.005, 0.116, 0.045, 0.087, 0.073, 0.067, 0.124, 0.211, 0.16, 0.055, 0.033, 0.036, 0.024, 0.013, 0.02, 0.022, 0.002, 0.0001, 0.1, 0.0001, 0.025, 0.009, 0.011, 3.536, 0.619, 1.963, 0.833, 1.275, 3.452, 0.323, 0.635, 3.408, 0.642, 1.486, 1.967, 1.26, 2.857, 4.587, 1.082, 0.0001, 0.0001, 0.339, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.0001, 0.002, 0.001, 31.356, 12.318, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.131, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"de\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.726, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.303, 0.002, 0.278, 0.0001, 0.0001, 0.007, 0.003, 0.005, 0.149, 0.149, 0.015, 0.001, 0.636, 0.237, 0.922, 0.023, 0.305, 0.472, 0.225, 0.115, 0.11, 0.121, 0.108, 0.11, 0.145, 0.271, 0.049, 0.022, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.413, 0.383, 0.144, 0.412, 0.275, 0.258, 0.273, 0.218, 0.18, 0.167, 0.277, 0.201, 0.328, 0.179, 0.111, 0.254, 0.012, 0.219, 0.602, 0.209, 0.1, 0.185, 0.206, 0.005, 0.01, 0.112, 0.002, 0.0001, 0.002, 0.0001, 0.006, 0.0001, 4.417, 1.306, 1.99, 3.615, 12.382, 1.106, 2.0, 2.958, 6.179, 0.082, 0.866, 2.842, 1.869, 7.338, 2.27, 0.606, 0.016, 6.056, 4.424, 4.731, 3.002, 0.609, 0.918, 0.053, 0.169, 0.824, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.147, 0.002, 0.003, 0.001, 0.006, 0.001, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.03, 0.0001, 0.0001, 0.009, 0.001, 0.002, 0.009, 0.002, 0.001, 0.061, 0.0001, 0.048, 0.122, 0.057, 0.009, 0.001, 0.001, 0.4, 0.001, 0.002, 0.003, 0.003, 0.017, 0.001, 0.003, 0.001, 0.005, 0.0001, 0.001, 0.003, 0.002, 0.003, 0.005, 0.001, 0.001, 0.203, 0.0001, 0.002, 0.001, 0.002, 0.002, 0.438, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.056, 1.237, 0.01, 0.013, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.148, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ja\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.834, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.258, 0.007, 0.036, 0.001, 0.0001, 0.005, 0.002, 0.003, 0.033, 0.033, 0.0001, 0.002, 0.019, 0.052, 0.026, 0.009, 0.281, 0.407, 0.259, 0.126, 0.108, 0.109, 0.095, 0.092, 0.104, 0.184, 0.008, 0.001, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.048, 0.026, 0.039, 0.027, 0.028, 0.022, 0.018, 0.016, 0.03, 0.012, 0.014, 0.02, 0.03, 0.025, 0.025, 0.026, 0.002, 0.026, 0.045, 0.031, 0.013, 0.014, 0.014, 0.006, 0.006, 0.003, 0.001, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.077, 0.012, 0.03, 0.026, 0.088, 0.012, 0.017, 0.025, 0.067, 0.002, 0.016, 0.041, 0.039, 0.059, 0.066, 0.016, 0.001, 0.06, 0.043, 0.051, 0.028, 0.009, 0.007, 0.004, 0.015, 0.004, 0.0001, 0.011, 0.0001, 0.0001, 0.0001, 2.555, 10.322, 5.875, 4.462, 0.784, 0.468, 0.442, 0.409, 1.173, 0.96, 0.657, 1.448, 1.442, 0.636, 0.341, 0.685, 0.495, 0.342, 0.651, 0.536, 0.435, 0.657, 0.51, 0.978, 0.31, 0.563, 0.439, 0.514, 0.668, 0.438, 0.29, 1.039, 0.423, 0.532, 0.407, 0.691, 0.677, 0.555, 0.911, 0.887, 1.086, 0.531, 0.836, 1.345, 0.438, 0.666, 1.528, 0.959, 0.535, 0.379, 0.302, 0.822, 0.614, 0.308, 0.253, 0.467, 0.807, 0.807, 0.777, 0.809, 1.292, 0.546, 0.524, 0.425, 0.0001, 0.0001, 0.002, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.015, 19.387, 1.167, 4.022, 2.518, 1.734, 1.339, 1.229, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.409, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"es\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.757, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.771, 0.003, 0.315, 0.001, 0.004, 0.019, 0.003, 0.014, 0.132, 0.133, 0.001, 0.001, 0.976, 0.078, 0.703, 0.014, 0.268, 0.331, 0.197, 0.095, 0.086, 0.095, 0.085, 0.084, 0.105, 0.183, 0.053, 0.027, 0.001, 0.002, 0.002, 0.002, 0.0001, 0.242, 0.129, 0.28, 0.129, 0.322, 0.105, 0.099, 0.077, 0.116, 0.074, 0.034, 0.209, 0.196, 0.086, 0.059, 0.187, 0.009, 0.118, 0.247, 0.128, 0.061, 0.072, 0.033, 0.023, 0.018, 0.013, 0.005, 0.0001, 0.005, 0.0001, 0.003, 0.0001, 8.9, 0.939, 3.234, 4.015, 9.642, 0.603, 0.891, 0.531, 5.007, 0.262, 0.107, 4.355, 1.915, 5.487, 6.224, 1.805, 0.423, 4.992, 5.086, 3.402, 2.878, 0.667, 0.044, 0.125, 0.673, 0.299, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.033, 0.009, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.003, 0.0001, 0.001, 0.001, 0.003, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.006, 0.006, 0.001, 0.0001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.008, 0.008, 0.001, 0.001, 0.025, 0.274, 0.002, 0.002, 0.002, 0.001, 0.001, 0.002, 0.002, 0.221, 0.003, 0.019, 0.001, 0.373, 0.001, 0.001, 0.005, 0.144, 0.01, 0.631, 0.002, 0.001, 0.002, 0.001, 0.002, 0.001, 0.102, 0.018, 0.006, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.079, 1.766, 0.003, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.008, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.032, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"fr\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.894, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.162, 0.003, 0.276, 0.0001, 0.0001, 0.012, 0.002, 0.638, 0.153, 0.153, 0.001, 0.002, 0.96, 0.247, 0.715, 0.011, 0.225, 0.339, 0.18, 0.084, 0.081, 0.086, 0.081, 0.084, 0.106, 0.194, 0.063, 0.018, 0.003, 0.002, 0.003, 0.002, 0.0001, 0.208, 0.141, 0.255, 0.128, 0.144, 0.1, 0.095, 0.071, 0.154, 0.072, 0.042, 0.331, 0.173, 0.077, 0.056, 0.167, 0.013, 0.108, 0.214, 0.102, 0.049, 0.062, 0.035, 0.009, 0.014, 0.011, 0.003, 0.0001, 0.003, 0.0001, 0.004, 0.0001, 5.761, 0.627, 2.287, 3.136, 10.738, 0.723, 0.838, 0.669, 5.295, 0.172, 0.12, 4.204, 1.941, 5.522, 4.015, 2.005, 0.584, 5.043, 5.545, 5.13, 4.06, 0.906, 0.051, 0.295, 0.278, 0.085, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.136, 0.003, 0.004, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.034, 0.0001, 0.0001, 0.001, 0.004, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.019, 0.003, 0.0001, 0.0001, 0.001, 0.001, 0.112, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.367, 0.007, 0.034, 0.001, 0.003, 0.001, 0.003, 0.046, 0.303, 1.817, 0.082, 0.045, 0.001, 0.004, 0.029, 0.017, 0.004, 0.002, 0.002, 0.005, 0.038, 0.001, 0.003, 0.0001, 0.002, 0.02, 0.002, 0.054, 0.004, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.113, 2.813, 0.007, 0.026, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.003, 0.001, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.122, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pt\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.934, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.319, 0.004, 0.372, 0.001, 0.002, 0.012, 0.004, 0.016, 0.15, 0.15, 0.001, 0.002, 1.16, 0.21, 0.746, 0.022, 0.296, 0.361, 0.226, 0.106, 0.098, 0.105, 0.096, 0.094, 0.114, 0.207, 0.054, 0.022, 0.006, 0.004, 0.006, 0.002, 0.0001, 0.345, 0.166, 0.295, 0.143, 0.233, 0.136, 0.112, 0.077, 0.129, 0.093, 0.039, 0.119, 0.217, 0.135, 0.164, 0.222, 0.016, 0.14, 0.259, 0.142, 0.064, 0.078, 0.041, 0.021, 0.013, 0.012, 0.007, 0.0001, 0.007, 0.0001, 0.007, 0.0001, 9.026, 0.717, 2.572, 4.173, 8.551, 0.751, 0.906, 0.629, 5.107, 0.172, 0.12, 2.357, 3.189, 4.024, 7.683, 1.87, 0.445, 5.017, 5.188, 3.559, 2.852, 0.875, 0.055, 0.186, 0.122, 0.257, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.034, 0.01, 0.003, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.014, 0.001, 0.001, 0.001, 0.005, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.009, 0.006, 0.0001, 0.0001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.007, 0.007, 0.0001, 0.001, 0.079, 0.267, 0.045, 0.508, 0.002, 0.001, 0.001, 0.424, 0.003, 0.417, 0.113, 0.003, 0.001, 0.255, 0.001, 0.001, 0.005, 0.003, 0.015, 0.161, 0.032, 0.087, 0.003, 0.001, 0.002, 0.001, 0.095, 0.002, 0.005, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.067, 2.471, 0.004, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.007, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.033, 0.002, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"it\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.828, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.918, 0.002, 0.385, 0.0001, 0.001, 0.007, 0.003, 0.383, 0.13, 0.131, 0.0001, 0.001, 0.948, 0.103, 0.657, 0.014, 0.252, 0.332, 0.195, 0.093, 0.089, 0.095, 0.088, 0.084, 0.098, 0.183, 0.061, 0.035, 0.006, 0.002, 0.006, 0.001, 0.0001, 0.215, 0.131, 0.235, 0.125, 0.08, 0.104, 0.125, 0.057, 0.24, 0.04, 0.038, 0.208, 0.179, 0.133, 0.054, 0.164, 0.025, 0.114, 0.256, 0.12, 0.052, 0.079, 0.038, 0.021, 0.012, 0.012, 0.002, 0.0001, 0.002, 0.0001, 0.005, 0.0001, 8.583, 0.65, 3.106, 3.081, 8.81, 0.801, 1.321, 0.694, 8.492, 0.02, 0.115, 5.238, 1.88, 5.659, 6.812, 1.981, 0.236, 4.962, 3.674, 5.112, 2.35, 1.107, 0.055, 0.027, 0.118, 0.709, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.022, 0.004, 0.002, 0.002, 0.001, 0.001, 0.001, 0.002, 0.013, 0.001, 0.0001, 0.0001, 0.001, 0.004, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.006, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.005, 0.0001, 0.001, 0.005, 0.005, 0.0001, 0.001, 0.153, 0.007, 0.001, 0.001, 0.003, 0.001, 0.001, 0.002, 0.174, 0.033, 0.004, 0.009, 0.036, 0.004, 0.001, 0.001, 0.006, 0.003, 0.097, 0.004, 0.001, 0.001, 0.003, 0.001, 0.002, 0.056, 0.009, 0.007, 0.004, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.043, 0.574, 0.01, 0.009, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.007, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.021, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"zh\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.074, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.273, 0.003, 0.045, 0.0001, 0.001, 0.012, 0.001, 0.004, 0.032, 0.032, 0.001, 0.003, 0.032, 0.068, 0.063, 0.017, 0.386, 0.478, 0.308, 0.149, 0.134, 0.146, 0.127, 0.121, 0.136, 0.231, 0.018, 0.009, 0.007, 0.006, 0.007, 0.0001, 0.0001, 0.045, 0.029, 0.041, 0.028, 0.022, 0.017, 0.02, 0.019, 0.025, 0.01, 0.013, 0.02, 0.033, 0.021, 0.018, 0.028, 0.002, 0.022, 0.045, 0.031, 0.01, 0.013, 0.012, 0.007, 0.005, 0.003, 0.004, 0.0001, 0.004, 0.0001, 0.009, 0.0001, 0.159, 0.026, 0.051, 0.047, 0.17, 0.025, 0.032, 0.057, 0.124, 0.003, 0.021, 0.089, 0.049, 0.12, 0.129, 0.028, 0.002, 0.124, 0.083, 0.1, 0.058, 0.016, 0.016, 0.008, 0.03, 0.012, 0.006, 0.004, 0.006, 0.001, 0.0001, 2.707, 1.09, 1.398, 0.705, 1.23, 1.04, 0.715, 0.952, 1.455, 1.297, 0.845, 1.19, 2.403, 1.193, 0.813, 1.077, 0.889, 0.565, 0.387, 0.47, 0.931, 0.663, 1.035, 0.837, 0.77, 0.772, 1.434, 1.023, 1.668, 0.609, 0.437, 0.793, 0.535, 0.706, 0.48, 0.538, 0.785, 0.909, 0.7, 0.697, 1.017, 0.519, 0.441, 0.567, 0.626, 1.082, 0.814, 1.054, 1.074, 0.811, 0.556, 0.684, 0.903, 0.43, 0.642, 0.78, 2.083, 1.147, 2.006, 1.331, 2.547, 1.015, 0.911, 0.807, 0.0001, 0.0001, 0.069, 0.007, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.126, 1.369, 3.539, 8.968, 5.44, 4.358, 3.141, 2.48, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 1.821, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"fa\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.841, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.03, 0.001, 0.048, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.117, 0.117, 0.001, 0.001, 0.009, 0.038, 0.486, 0.012, 0.007, 0.009, 0.007, 0.005, 0.003, 0.004, 0.003, 0.003, 0.003, 0.004, 0.048, 0.001, 0.001, 0.003, 0.001, 0.001, 0.0001, 0.011, 0.006, 0.011, 0.006, 0.005, 0.005, 0.004, 0.005, 0.007, 0.002, 0.002, 0.005, 0.008, 0.005, 0.005, 0.008, 0.001, 0.005, 0.011, 0.008, 0.002, 0.003, 0.004, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.002, 0.0001, 0.007, 0.0001, 0.058, 0.008, 0.02, 0.02, 0.06, 0.011, 0.012, 0.017, 0.051, 0.001, 0.009, 0.031, 0.018, 0.042, 0.047, 0.015, 0.001, 0.043, 0.03, 0.037, 0.022, 0.005, 0.008, 0.003, 0.009, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.678, 0.557, 0.438, 0.001, 1.227, 2.118, 3.004, 2.445, 2.539, 0.0001, 0.003, 0.021, 5.067, 0.002, 0.007, 0.006, 0.015, 0.005, 0.002, 0.008, 0.07, 0.0001, 0.0001, 0.0001, 0.053, 0.001, 0.0001, 0.018, 0.0001, 0.001, 0.0001, 0.002, 0.002, 0.006, 0.337, 0.015, 0.006, 0.001, 0.059, 6.029, 1.704, 1.216, 2.096, 0.113, 0.433, 0.309, 0.439, 3.398, 0.192, 3.798, 0.977, 1.716, 1.137, 0.259, 0.129, 0.264, 0.12, 0.588, 0.085, 0.033, 0.001, 0.0001, 0.327, 0.0001, 0.0001, 0.0001, 0.068, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 23.012, 12.666, 1.946, 5.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.676, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pl\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.97, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.695, 0.002, 0.242, 0.0001, 0.0001, 0.007, 0.002, 0.011, 0.194, 0.194, 0.0001, 0.001, 0.805, 0.129, 1.016, 0.02, 0.347, 0.542, 0.289, 0.14, 0.138, 0.144, 0.123, 0.13, 0.153, 0.343, 0.068, 0.014, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.17, 0.165, 0.143, 0.124, 0.066, 0.081, 0.113, 0.075, 0.141, 0.107, 0.18, 0.108, 0.192, 0.142, 0.119, 0.322, 0.004, 0.139, 0.268, 0.117, 0.058, 0.041, 0.322, 0.032, 0.008, 0.109, 0.001, 0.0001, 0.001, 0.0001, 0.006, 0.0001, 6.697, 0.859, 2.856, 2.291, 5.604, 0.259, 1.117, 0.918, 6.017, 1.562, 2.537, 1.759, 1.903, 4.231, 5.86, 1.841, 0.006, 3.854, 3.145, 2.863, 1.965, 0.061, 3.408, 0.016, 2.669, 3.631, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.208, 0.018, 1.343, 0.004, 0.168, 0.653, 0.002, 0.145, 0.003, 0.001, 0.001, 0.001, 0.002, 0.004, 0.001, 0.002, 0.002, 0.001, 0.003, 0.126, 0.002, 0.001, 0.002, 0.002, 0.001, 0.65, 0.023, 0.378, 0.002, 0.035, 0.035, 0.002, 0.018, 0.011, 0.001, 0.002, 0.005, 0.001, 0.001, 0.002, 0.003, 0.012, 0.001, 0.002, 0.001, 0.005, 0.001, 0.001, 0.01, 0.004, 0.011, 0.641, 0.003, 0.006, 0.005, 0.001, 0.008, 0.004, 0.056, 0.014, 0.433, 0.007, 0.008, 0.002, 0.0001, 0.0001, 0.025, 0.694, 1.442, 2.413, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.006, 0.003, 0.06, 0.02, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.003, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.205, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tr\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.91, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.447, 0.013, 0.297, 0.0001, 0.001, 0.013, 0.003, 0.465, 0.123, 0.123, 0.001, 0.002, 0.653, 0.111, 0.957, 0.015, 0.312, 0.387, 0.238, 0.107, 0.101, 0.108, 0.097, 0.095, 0.109, 0.217, 0.04, 0.028, 0.007, 0.019, 0.007, 0.002, 0.0001, 0.336, 0.309, 0.117, 0.167, 0.132, 0.105, 0.13, 0.135, 0.063, 0.042, 0.261, 0.085, 0.236, 0.083, 0.095, 0.131, 0.004, 0.092, 0.247, 0.219, 0.038, 0.052, 0.037, 0.008, 0.095, 0.019, 0.007, 0.0001, 0.007, 0.0001, 0.005, 0.001, 8.533, 1.3, 0.65, 3.067, 6.656, 0.419, 0.804, 0.718, 6.178, 0.059, 2.986, 5.127, 2.286, 5.537, 2.04, 0.623, 0.006, 5.247, 2.411, 2.743, 2.225, 0.903, 0.049, 0.018, 2.076, 0.792, 0.0001, 0.018, 0.0001, 0.0001, 0.0001, 0.096, 0.004, 0.004, 0.004, 0.002, 0.002, 0.002, 0.041, 0.002, 0.001, 0.001, 0.001, 0.002, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.007, 0.002, 0.001, 0.031, 0.001, 0.003, 0.065, 0.001, 0.001, 0.033, 0.009, 0.047, 1.71, 0.04, 0.005, 0.027, 0.002, 0.003, 0.001, 0.001, 0.647, 0.002, 0.008, 0.002, 0.003, 0.001, 0.004, 0.019, 0.002, 0.132, 3.435, 0.005, 0.004, 0.003, 0.003, 0.525, 0.001, 0.004, 0.002, 0.003, 0.007, 1.206, 0.003, 0.003, 0.002, 0.0001, 0.0001, 0.046, 2.539, 4.197, 1.125, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.007, 0.003, 0.023, 0.009, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.01, 0.007, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.094, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nl\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.158, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.747, 0.002, 0.267, 0.0001, 0.001, 0.008, 0.01, 0.052, 0.196, 0.196, 0.0001, 0.001, 0.504, 0.205, 0.944, 0.013, 0.311, 0.428, 0.229, 0.104, 0.101, 0.109, 0.102, 0.102, 0.137, 0.252, 0.048, 0.012, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.205, 0.192, 0.181, 0.371, 0.131, 0.088, 0.11, 0.236, 0.167, 0.069, 0.091, 0.119, 0.172, 0.137, 0.117, 0.141, 0.005, 0.112, 0.229, 0.137, 0.034, 0.123, 0.084, 0.006, 0.011, 0.064, 0.001, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 6.042, 1.063, 1.294, 4.124, 13.689, 0.579, 2.105, 1.822, 5.542, 0.948, 1.42, 3.124, 1.72, 7.129, 4.759, 1.349, 0.015, 5.115, 3.623, 4.903, 1.642, 1.84, 1.06, 0.063, 0.226, 0.656, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.023, 0.003, 0.004, 0.003, 0.002, 0.001, 0.001, 0.002, 0.001, 0.002, 0.0001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.008, 0.001, 0.0001, 0.001, 0.001, 0.002, 0.007, 0.001, 0.001, 0.003, 0.003, 0.001, 0.002, 0.008, 0.009, 0.003, 0.002, 0.005, 0.002, 0.001, 0.003, 0.009, 0.038, 0.001, 0.051, 0.001, 0.005, 0.001, 0.011, 0.004, 0.003, 0.013, 0.008, 0.002, 0.002, 0.008, 0.001, 0.004, 0.001, 0.003, 0.002, 0.01, 0.003, 0.003, 0.001, 0.0001, 0.0001, 0.02, 0.166, 0.007, 0.01, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.016, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.022, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ko\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.893, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.919, 0.003, 0.069, 0.0001, 0.0001, 0.007, 0.002, 0.048, 0.269, 0.269, 0.0001, 0.002, 0.501, 0.04, 0.699, 0.01, 0.29, 0.417, 0.259, 0.125, 0.109, 0.112, 0.1, 0.094, 0.109, 0.192, 0.015, 0.002, 0.006, 0.002, 0.006, 0.003, 0.0001, 0.038, 0.026, 0.038, 0.022, 0.02, 0.024, 0.015, 0.013, 0.023, 0.008, 0.015, 0.017, 0.027, 0.016, 0.016, 0.023, 0.002, 0.017, 0.041, 0.027, 0.011, 0.013, 0.01, 0.005, 0.004, 0.002, 0.006, 0.0001, 0.006, 0.0001, 0.012, 0.0001, 0.108, 0.014, 0.037, 0.031, 0.116, 0.024, 0.022, 0.032, 0.084, 0.002, 0.021, 0.064, 0.06, 0.077, 0.092, 0.02, 0.001, 0.086, 0.056, 0.066, 0.046, 0.011, 0.008, 0.004, 0.019, 0.004, 0.0001, 0.002, 0.0001, 0.025, 0.0001, 2.21, 0.565, 0.766, 0.471, 3.043, 0.671, 0.334, 0.049, 1.404, 0.218, 1.17, 1.657, 1.23, 0.278, 0.091, 0.557, 1.645, 0.451, 0.058, 0.386, 1.38, 2.193, 0.506, 1.29, 2.708, 0.68, 0.385, 0.399, 2.758, 3.352, 0.954, 0.141, 1.848, 0.829, 0.071, 0.249, 1.741, 0.637, 0.43, 0.888, 0.537, 0.506, 0.243, 0.027, 1.4, 0.355, 0.026, 0.179, 2.38, 0.404, 0.739, 1.021, 2.205, 0.729, 0.454, 0.308, 1.635, 0.561, 0.035, 0.084, 1.612, 0.309, 0.024, 0.047, 0.0001, 0.0001, 0.034, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.039, 0.089, 0.025, 0.107, 0.071, 0.044, 0.037, 0.043, 3.199, 8.716, 12.558, 3.298, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"cs\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.804, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.066, 0.002, 0.232, 0.0001, 0.0001, 0.008, 0.002, 0.009, 0.188, 0.188, 0.007, 0.002, 0.814, 0.094, 1.008, 0.025, 0.299, 0.437, 0.233, 0.115, 0.111, 0.119, 0.106, 0.102, 0.129, 0.233, 0.051, 0.011, 0.002, 0.002, 0.002, 0.002, 0.0001, 0.143, 0.145, 0.103, 0.117, 0.06, 0.072, 0.055, 0.092, 0.08, 0.13, 0.142, 0.093, 0.169, 0.137, 0.088, 0.246, 0.003, 0.104, 0.236, 0.127, 0.039, 0.213, 0.033, 0.007, 0.007, 0.069, 0.002, 0.0001, 0.002, 0.0001, 0.005, 0.0001, 5.018, 1.137, 1.8, 2.299, 5.465, 0.243, 0.288, 1.623, 3.2, 1.177, 2.624, 3.218, 2.048, 4.447, 5.813, 1.952, 0.006, 3.062, 3.218, 3.502, 2.227, 3.008, 0.043, 0.058, 1.313, 1.405, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.104, 0.003, 0.004, 0.003, 0.001, 0.001, 0.001, 0.003, 0.041, 0.001, 0.001, 0.001, 0.049, 0.57, 0.001, 0.012, 0.001, 0.001, 0.002, 0.048, 0.002, 0.001, 0.001, 0.002, 0.011, 0.748, 0.01, 0.981, 0.025, 0.001, 0.025, 0.002, 0.191, 1.9, 0.003, 0.001, 0.005, 0.024, 0.002, 0.002, 0.002, 0.87, 0.001, 0.001, 0.001, 1.984, 0.001, 0.336, 0.006, 0.002, 0.004, 0.031, 0.002, 0.003, 0.006, 0.001, 0.003, 0.001, 0.094, 0.002, 0.007, 0.671, 0.58, 0.001, 0.0001, 0.0001, 0.173, 5.104, 1.615, 2.233, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.021, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.009, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.103, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ar\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.65, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.194, 0.002, 0.102, 0.0001, 0.0001, 0.007, 0.001, 0.002, 0.109, 0.108, 0.002, 0.001, 0.03, 0.046, 0.42, 0.018, 0.182, 0.202, 0.135, 0.063, 0.065, 0.061, 0.055, 0.053, 0.062, 0.113, 0.054, 0.001, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.01, 0.006, 0.009, 0.007, 0.005, 0.004, 0.004, 0.004, 0.005, 0.002, 0.002, 0.005, 0.007, 0.005, 0.004, 0.007, 0.001, 0.005, 0.009, 0.006, 0.002, 0.002, 0.002, 0.001, 0.001, 0.001, 0.007, 0.001, 0.007, 0.0001, 0.004, 0.0001, 0.052, 0.008, 0.019, 0.018, 0.055, 0.008, 0.011, 0.016, 0.045, 0.001, 0.006, 0.028, 0.016, 0.037, 0.04, 0.012, 0.001, 0.038, 0.03, 0.035, 0.02, 0.006, 0.006, 0.002, 0.009, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.055, 1.131, 0.874, 0.939, 4.804, 2.787, 2.235, 1.018, 2.407, 0.349, 3.542, 0.092, 0.4, 0.007, 0.051, 0.053, 0.022, 0.061, 0.01, 0.008, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.008, 0.001, 0.001, 0.0001, 0.002, 0.013, 0.133, 0.049, 0.782, 0.037, 0.335, 0.157, 6.208, 1.599, 1.486, 1.889, 0.276, 0.607, 0.762, 0.341, 1.38, 0.239, 2.041, 0.293, 1.149, 0.411, 0.383, 0.246, 0.406, 0.094, 1.401, 0.223, 0.006, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.027, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 23.298, 20.414, 0.003, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.019, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"vi\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.205, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.546, 0.002, 0.241, 0.0001, 0.001, 0.015, 0.013, 0.009, 0.13, 0.13, 0.0001, 0.002, 0.714, 0.089, 0.813, 0.02, 0.259, 0.361, 0.203, 0.104, 0.097, 0.104, 0.089, 0.089, 0.116, 0.194, 0.047, 0.017, 0.002, 0.002, 0.002, 0.002, 0.0001, 0.148, 0.175, 0.293, 0.111, 0.056, 0.04, 0.092, 0.206, 0.057, 0.03, 0.119, 0.232, 0.178, 0.247, 0.036, 0.156, 0.056, 0.062, 0.184, 0.397, 0.022, 0.114, 0.033, 0.033, 0.019, 0.009, 0.005, 0.0001, 0.005, 0.0001, 0.003, 0.0001, 2.683, 0.66, 3.149, 0.627, 1.148, 0.076, 2.542, 4.362, 3.528, 0.019, 0.59, 1.486, 1.611, 5.924, 2.001, 0.761, 0.201, 1.559, 1.014, 3.555, 1.77, 0.861, 0.05, 0.173, 0.826, 0.047, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.021, 0.214, 0.011, 0.478, 0.002, 0.039, 0.001, 0.324, 0.002, 0.072, 0.001, 0.198, 0.002, 0.32, 0.002, 0.048, 0.141, 1.485, 0.001, 0.116, 0.015, 0.106, 0.001, 0.025, 0.002, 0.579, 0.004, 0.289, 0.004, 0.257, 0.005, 0.174, 1.516, 1.221, 0.326, 0.818, 0.013, 0.337, 0.005, 0.51, 0.014, 0.324, 0.408, 0.115, 0.147, 0.492, 0.002, 0.218, 0.82, 0.26, 0.102, 0.383, 0.379, 0.016, 0.006, 0.094, 0.005, 0.132, 2.233, 4.628, 0.009, 0.062, 0.003, 0.385, 0.0001, 0.0001, 0.047, 4.542, 1.653, 0.065, 0.997, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.011, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 6.74, 0.019, 0.004, 0.002, 0.009, 0.006, 0.004, 0.003, 0.003, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"el\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.389, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.245, 0.003, 0.167, 0.001, 0.0001, 0.005, 0.002, 0.015, 0.1, 0.101, 0.0001, 0.001, 0.487, 0.058, 0.449, 0.01, 0.151, 0.215, 0.114, 0.058, 0.055, 0.058, 0.052, 0.051, 0.065, 0.119, 0.032, 0.001, 0.003, 0.003, 0.003, 0.0001, 0.0001, 0.021, 0.016, 0.024, 0.014, 0.012, 0.012, 0.011, 0.013, 0.012, 0.005, 0.006, 0.013, 0.018, 0.01, 0.009, 0.015, 0.001, 0.013, 0.025, 0.017, 0.005, 0.006, 0.008, 0.002, 0.002, 0.001, 0.005, 0.0001, 0.005, 0.0001, 0.002, 0.0001, 0.125, 0.018, 0.039, 0.039, 0.142, 0.017, 0.026, 0.036, 0.105, 0.002, 0.017, 0.072, 0.036, 0.093, 0.102, 0.022, 0.002, 0.099, 0.07, 0.077, 0.046, 0.014, 0.01, 0.005, 0.02, 0.005, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 1.502, 1.948, 1.522, 1.805, 3.613, 1.458, 0.354, 0.481, 0.073, 0.584, 0.024, 0.002, 0.912, 0.435, 0.305, 0.001, 0.006, 0.156, 0.057, 0.068, 0.049, 0.097, 0.01, 0.064, 0.017, 0.048, 0.112, 0.037, 0.115, 0.048, 0.003, 0.099, 0.122, 0.029, 0.001, 0.129, 0.119, 0.011, 0.03, 0.034, 0.002, 0.008, 0.0001, 0.022, 0.85, 0.749, 0.601, 1.063, 0.004, 3.95, 0.27, 0.716, 0.649, 2.656, 0.14, 1.63, 0.422, 2.831, 1.733, 1.214, 1.337, 2.636, 0.149, 3.615, 0.0001, 0.0001, 0.06, 0.007, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 28.675, 14.922, 0.013, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.013, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.282, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.667, 0.001, 0.345, 0.0001, 0.0001, 0.007, 0.002, 0.013, 0.083, 0.083, 0.0001, 0.0001, 0.902, 0.146, 1.182, 0.007, 0.152, 0.25, 0.108, 0.06, 0.06, 0.065, 0.065, 0.066, 0.089, 0.153, 0.044, 0.004, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.178, 0.164, 0.421, 0.354, 0.095, 0.078, 0.149, 0.127, 0.181, 0.06, 0.161, 0.209, 0.174, 0.099, 0.072, 0.149, 0.019, 0.12, 0.249, 0.206, 0.034, 0.058, 0.04, 0.006, 0.012, 0.014, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 6.63, 0.945, 0.963, 3.448, 8.696, 0.922, 2.03, 1.373, 4.448, 0.429, 1.949, 3.417, 3.024, 6.448, 3.193, 1.076, 0.019, 6.923, 3.891, 5.562, 1.877, 1.653, 0.074, 0.114, 0.424, 0.075, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.022, 0.039, 0.002, 0.003, 0.007, 0.074, 0.004, 0.007, 0.005, 0.002, 0.002, 0.0001, 0.003, 0.008, 0.002, 0.004, 0.001, 0.002, 0.0001, 0.011, 0.001, 0.001, 0.012, 0.001, 0.005, 0.002, 0.001, 0.001, 0.001, 0.004, 0.001, 0.003, 0.21, 0.017, 0.005, 0.004, 1.574, 0.853, 0.002, 0.007, 0.008, 0.038, 0.004, 0.047, 0.001, 0.014, 0.002, 0.009, 0.187, 0.01, 0.004, 0.012, 0.004, 0.002, 0.808, 0.001, 0.008, 0.002, 0.004, 0.002, 0.006, 0.002, 0.003, 0.001, 0.0001, 0.0001, 0.393, 3.436, 0.069, 0.044, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.014, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.021, 0.021, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.006, 0.019, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hu\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.827, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.714, 0.004, 0.265, 0.0001, 0.0001, 0.007, 0.001, 0.007, 0.159, 0.159, 0.001, 0.002, 1.016, 0.461, 0.937, 0.013, 0.261, 0.429, 0.206, 0.109, 0.106, 0.113, 0.103, 0.105, 0.137, 0.238, 0.073, 0.019, 0.004, 0.004, 0.004, 0.002, 0.0001, 0.469, 0.135, 0.097, 0.073, 0.142, 0.093, 0.075, 0.087, 0.095, 0.062, 0.133, 0.086, 0.175, 0.085, 0.042, 0.096, 0.003, 0.071, 0.186, 0.107, 0.027, 0.069, 0.028, 0.009, 0.008, 0.025, 0.002, 0.0001, 0.002, 0.0001, 0.004, 0.0001, 6.316, 1.591, 0.619, 1.364, 7.125, 0.648, 2.159, 0.946, 3.15, 0.796, 3.265, 4.526, 2.054, 3.978, 3.047, 0.846, 0.006, 3.327, 4.35, 5.787, 0.902, 1.395, 0.037, 0.035, 1.463, 2.94, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.129, 0.02, 0.003, 0.003, 0.001, 0.001, 0.001, 0.003, 0.002, 0.014, 0.001, 0.001, 0.001, 0.006, 0.0001, 0.001, 0.004, 0.667, 0.001, 0.068, 0.001, 0.0001, 0.005, 0.001, 0.001, 0.009, 0.007, 0.002, 0.003, 0.026, 0.026, 0.002, 0.024, 2.603, 0.002, 0.001, 0.003, 0.001, 0.002, 0.002, 0.003, 2.374, 0.001, 0.002, 0.001, 0.448, 0.001, 0.001, 0.005, 0.169, 0.003, 0.702, 0.002, 0.002, 0.76, 0.001, 0.004, 0.002, 0.223, 0.002, 0.382, 0.004, 0.004, 0.001, 0.0001, 0.0001, 0.028, 7.544, 0.01, 0.845, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.021, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.128, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ro\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.044, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.178, 0.003, 0.287, 0.001, 0.001, 0.038, 0.002, 0.011, 0.2, 0.201, 0.001, 0.002, 1.114, 0.333, 0.783, 0.015, 0.314, 0.397, 0.224, 0.108, 0.105, 0.107, 0.098, 0.099, 0.123, 0.221, 0.062, 0.021, 0.007, 0.006, 0.007, 0.002, 0.0001, 0.27, 0.164, 0.289, 0.16, 0.109, 0.099, 0.098, 0.077, 0.163, 0.044, 0.047, 0.132, 0.205, 0.095, 0.07, 0.207, 0.004, 0.158, 0.242, 0.12, 0.072, 0.085, 0.033, 0.021, 0.01, 0.019, 0.006, 0.0001, 0.006, 0.0001, 0.007, 0.0001, 7.568, 0.638, 3.253, 2.492, 8.352, 0.862, 0.693, 0.377, 7.77, 0.16, 0.142, 3.906, 1.919, 5.009, 3.799, 1.948, 0.008, 5.326, 2.857, 4.711, 4.259, 0.743, 0.045, 0.139, 0.103, 0.506, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.128, 0.004, 0.004, 1.675, 0.002, 0.001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.001, 0.003, 0.104, 0.001, 0.001, 0.002, 0.001, 0.018, 0.003, 0.001, 0.001, 0.001, 0.016, 0.733, 0.007, 0.695, 0.006, 0.05, 0.046, 0.002, 0.038, 0.012, 0.339, 0.002, 0.003, 0.001, 0.001, 0.002, 0.004, 0.016, 0.001, 0.003, 0.001, 0.004, 0.716, 0.001, 0.007, 0.003, 0.004, 0.005, 0.003, 0.002, 0.005, 0.001, 0.003, 0.001, 0.002, 0.003, 0.007, 0.003, 0.003, 0.001, 0.0001, 0.0001, 0.048, 1.213, 1.681, 0.01, 0.0001, 0.003, 1.446, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.003, 0.016, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.127, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"id\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.029, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.265, 0.003, 0.293, 0.001, 0.002, 0.008, 0.004, 0.02, 0.156, 0.156, 0.001, 0.002, 0.897, 0.232, 0.837, 0.025, 0.281, 0.301, 0.205, 0.089, 0.081, 0.088, 0.077, 0.074, 0.084, 0.156, 0.047, 0.017, 0.004, 0.004, 0.004, 0.002, 0.0001, 0.336, 0.259, 0.156, 0.221, 0.076, 0.084, 0.101, 0.111, 0.249, 0.128, 0.292, 0.143, 0.276, 0.131, 0.06, 0.365, 0.008, 0.137, 0.448, 0.233, 0.076, 0.043, 0.063, 0.011, 0.049, 0.014, 0.01, 0.0001, 0.01, 0.0001, 0.002, 0.0001, 14.771, 1.913, 0.506, 3.424, 6.588, 0.273, 2.854, 1.797, 6.389, 0.58, 3.078, 2.893, 3.104, 7.626, 2.047, 2.047, 0.011, 4.279, 3.371, 3.841, 3.795, 0.171, 0.34, 0.026, 1.249, 0.063, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.031, 0.005, 0.004, 0.003, 0.003, 0.002, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.004, 0.002, 0.001, 0.001, 0.001, 0.001, 0.012, 0.003, 0.001, 0.001, 0.001, 0.002, 0.005, 0.001, 0.001, 0.006, 0.006, 0.001, 0.002, 0.051, 0.005, 0.002, 0.002, 0.003, 0.001, 0.002, 0.003, 0.002, 0.009, 0.001, 0.002, 0.001, 0.003, 0.001, 0.001, 0.004, 0.003, 0.004, 0.003, 0.002, 0.002, 0.002, 0.001, 0.003, 0.002, 0.002, 0.002, 0.004, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.055, 0.03, 0.005, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.003, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.006, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.03, 0.003, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sk\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.159, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.105, 0.002, 0.192, 0.0001, 0.0001, 0.007, 0.002, 0.005, 0.209, 0.21, 0.013, 0.002, 0.819, 0.162, 1.046, 0.023, 0.302, 0.407, 0.233, 0.125, 0.121, 0.119, 0.111, 0.11, 0.127, 0.222, 0.055, 0.011, 0.002, 0.003, 0.002, 0.001, 0.0001, 0.172, 0.157, 0.128, 0.107, 0.068, 0.073, 0.08, 0.101, 0.088, 0.103, 0.136, 0.098, 0.191, 0.186, 0.106, 0.263, 0.004, 0.11, 0.26, 0.138, 0.041, 0.2, 0.032, 0.006, 0.008, 0.071, 0.001, 0.0001, 0.001, 0.0001, 0.004, 0.0001, 6.363, 1.243, 1.749, 2.177, 5.774, 0.29, 0.367, 1.611, 4.04, 1.457, 2.743, 2.816, 2.062, 4.279, 6.818, 1.868, 0.006, 3.912, 3.184, 3.285, 2.066, 3.292, 0.044, 0.067, 1.073, 1.331, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.113, 0.006, 0.004, 0.002, 0.002, 0.001, 0.001, 0.002, 0.077, 0.003, 0.0001, 0.001, 0.033, 0.618, 0.006, 0.066, 0.001, 0.001, 0.001, 0.046, 0.001, 0.006, 0.001, 0.001, 0.001, 0.013, 0.009, 0.007, 0.027, 0.001, 0.026, 0.001, 0.106, 1.828, 0.001, 0.001, 0.067, 0.259, 0.001, 0.002, 0.006, 0.586, 0.001, 0.001, 0.001, 0.717, 0.001, 0.002, 0.005, 0.002, 0.004, 0.16, 0.12, 0.002, 0.005, 0.038, 0.002, 0.001, 0.54, 0.002, 0.006, 0.806, 0.828, 0.001, 0.0001, 0.0001, 0.114, 4.297, 1.036, 1.463, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.003, 0.014, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.112, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"da\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.925, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.716, 0.002, 0.323, 0.001, 0.001, 0.007, 0.004, 0.044, 0.149, 0.15, 0.001, 0.001, 0.888, 0.199, 1.047, 0.017, 0.356, 0.494, 0.245, 0.119, 0.115, 0.124, 0.118, 0.127, 0.168, 0.257, 0.046, 0.018, 0.001, 0.002, 0.001, 0.002, 0.0001, 0.185, 0.17, 0.132, 0.265, 0.124, 0.155, 0.096, 0.211, 0.151, 0.076, 0.153, 0.12, 0.178, 0.102, 0.069, 0.125, 0.005, 0.111, 0.307, 0.131, 0.057, 0.087, 0.054, 0.005, 0.012, 0.01, 0.002, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 4.818, 1.29, 0.375, 4.241, 11.595, 1.856, 2.915, 1.153, 4.647, 0.373, 2.179, 3.858, 2.304, 5.903, 3.8, 1.073, 0.008, 6.456, 4.455, 5.128, 1.418, 1.705, 0.066, 0.033, 0.579, 0.056, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.052, 0.003, 0.002, 0.001, 0.001, 0.008, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.033, 0.003, 0.0001, 0.001, 0.001, 0.013, 0.005, 0.0001, 0.001, 0.002, 0.008, 0.001, 0.002, 0.01, 0.006, 0.001, 0.001, 0.01, 0.595, 0.559, 0.002, 0.002, 0.02, 0.001, 0.004, 0.001, 0.004, 0.001, 0.001, 0.005, 0.002, 0.003, 0.005, 0.001, 0.001, 0.011, 0.001, 0.585, 0.001, 0.002, 0.003, 0.011, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.02, 1.836, 0.004, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.052, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"fi\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.851, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.647, 0.002, 0.239, 0.0001, 0.0001, 0.006, 0.003, 0.009, 0.115, 0.115, 0.0001, 0.004, 0.594, 0.296, 1.014, 0.011, 0.404, 0.475, 0.268, 0.112, 0.107, 0.117, 0.106, 0.107, 0.133, 0.295, 0.069, 0.007, 0.003, 0.004, 0.003, 0.001, 0.0001, 0.183, 0.111, 0.1, 0.068, 0.113, 0.064, 0.065, 0.195, 0.087, 0.098, 0.225, 0.146, 0.211, 0.097, 0.06, 0.172, 0.005, 0.116, 0.314, 0.181, 0.037, 0.143, 0.044, 0.006, 0.048, 0.009, 0.001, 0.0001, 0.001, 0.0001, 0.004, 0.0001, 9.681, 0.162, 0.176, 0.832, 6.272, 0.12, 0.289, 1.322, 8.475, 1.576, 3.754, 4.597, 2.281, 6.958, 4.47, 1.345, 0.007, 2.326, 6.029, 6.589, 4.108, 1.653, 0.05, 0.021, 1.301, 0.041, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.101, 0.002, 0.002, 0.001, 0.004, 0.002, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.061, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.008, 0.0001, 0.001, 0.001, 0.032, 0.0001, 0.001, 0.032, 0.02, 0.001, 0.001, 2.624, 0.003, 0.001, 0.001, 0.002, 0.014, 0.0001, 0.002, 0.001, 0.01, 0.001, 0.001, 0.003, 0.002, 0.002, 0.005, 0.001, 0.001, 0.349, 0.001, 0.002, 0.001, 0.002, 0.001, 0.005, 0.002, 0.004, 0.001, 0.0001, 0.0001, 0.039, 3.028, 0.006, 0.023, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.007, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.101, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"th\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.353, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.736, 0.001, 0.084, 0.0001, 0.0001, 0.003, 0.001, 0.003, 0.081, 0.081, 0.0001, 0.001, 0.043, 0.029, 0.16, 0.005, 0.088, 0.106, 0.121, 0.047, 0.051, 0.082, 0.032, 0.03, 0.033, 0.045, 0.008, 0.004, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.013, 0.009, 0.013, 0.008, 0.008, 0.006, 0.006, 0.006, 0.008, 0.003, 0.003, 0.006, 0.01, 0.006, 0.005, 0.009, 0.001, 0.007, 0.015, 0.012, 0.003, 0.003, 0.006, 0.001, 0.002, 0.001, 0.003, 0.0001, 0.003, 0.0001, 0.001, 0.0001, 0.08, 0.011, 0.029, 0.025, 0.092, 0.012, 0.017, 0.027, 0.069, 0.001, 0.009, 0.042, 0.023, 0.063, 0.066, 0.017, 0.001, 0.062, 0.045, 0.056, 0.028, 0.008, 0.007, 0.003, 0.015, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 1.311, 1.859, 0.629, 0.364, 0.845, 0.001, 0.034, 1.547, 1.721, 0.971, 0.381, 0.156, 0.367, 0.089, 0.014, 0.016, 0.045, 0.009, 0.014, 0.115, 0.776, 0.653, 0.138, 0.742, 0.12, 1.918, 0.573, 0.602, 0.112, 0.028, 0.443, 0.069, 0.115, 1.089, 0.883, 1.745, 0.026, 0.859, 0.001, 0.829, 0.228, 0.108, 0.682, 0.53, 0.008, 1.369, 0.031, 0.006, 0.627, 1.083, 2.149, 0.218, 0.714, 0.916, 0.178, 0.322, 26.536, 5.927, 0.003, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.007, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 31.884, 0.001, 0.018, 0.002, 0.001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bg\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.55, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.448, 0.001, 0.106, 0.0001, 0.0001, 0.005, 0.001, 0.003, 0.12, 0.12, 0.002, 0.001, 0.557, 0.131, 0.613, 0.011, 0.182, 0.272, 0.137, 0.074, 0.072, 0.075, 0.066, 0.065, 0.083, 0.144, 0.028, 0.009, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.013, 0.009, 0.015, 0.008, 0.007, 0.006, 0.006, 0.006, 0.041, 0.002, 0.003, 0.007, 0.011, 0.006, 0.005, 0.01, 0.001, 0.006, 0.015, 0.011, 0.003, 0.01, 0.005, 0.007, 0.001, 0.001, 0.003, 0.0001, 0.003, 0.0001, 0.002, 0.0001, 0.088, 0.012, 0.031, 0.028, 0.092, 0.009, 0.016, 0.024, 0.077, 0.002, 0.014, 0.045, 0.037, 0.056, 0.066, 0.019, 0.001, 0.063, 0.052, 0.05, 0.037, 0.008, 0.006, 0.003, 0.013, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.651, 2.091, 3.127, 0.625, 0.166, 0.165, 0.297, 0.452, 0.133, 0.189, 0.677, 0.001, 0.018, 0.001, 0.079, 0.727, 0.091, 0.092, 0.108, 0.095, 0.081, 0.039, 0.009, 0.034, 0.052, 0.011, 0.114, 0.044, 0.167, 0.089, 0.136, 0.155, 0.116, 0.171, 0.083, 0.024, 0.037, 0.04, 0.014, 0.018, 0.016, 0.009, 0.001, 0.0001, 0.001, 0.002, 0.012, 0.008, 5.212, 0.516, 1.875, 0.701, 1.296, 3.589, 0.274, 0.882, 3.979, 0.288, 1.391, 1.465, 0.909, 3.169, 3.698, 1.109, 0.0001, 0.0001, 0.048, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.0001, 0.015, 0.006, 31.942, 11.185, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.201, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"he\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.485, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.289, 0.001, 0.262, 0.0001, 0.0001, 0.005, 0.001, 0.096, 0.104, 0.103, 0.0001, 0.001, 0.64, 0.203, 0.573, 0.005, 0.181, 0.234, 0.129, 0.06, 0.061, 0.062, 0.055, 0.054, 0.065, 0.138, 0.049, 0.013, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.016, 0.011, 0.014, 0.009, 0.007, 0.007, 0.006, 0.007, 0.009, 0.003, 0.003, 0.008, 0.012, 0.007, 0.005, 0.01, 0.001, 0.008, 0.016, 0.012, 0.003, 0.004, 0.005, 0.002, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.007, 0.0001, 0.073, 0.008, 0.021, 0.022, 0.081, 0.015, 0.013, 0.021, 0.056, 0.001, 0.007, 0.043, 0.024, 0.051, 0.061, 0.011, 0.001, 0.058, 0.038, 0.043, 0.032, 0.007, 0.005, 0.003, 0.012, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.003, 0.002, 0.003, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 2.008, 2.447, 0.696, 1.135, 3.773, 4.868, 0.394, 0.995, 0.678, 4.903, 0.173, 0.854, 2.776, 1.153, 2.22, 0.562, 1.585, 0.919, 1.159, 0.101, 0.969, 0.062, 0.568, 1.054, 2.634, 1.902, 2.428, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.009, 0.002, 0.002, 0.002, 0.006, 0.004, 0.005, 0.005, 0.008, 0.005, 0.001, 0.002, 0.01, 0.002, 0.005, 0.001, 0.0001, 0.0001, 0.008, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.015, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.044, 42.985, 0.006, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.013, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"uk\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.595, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.309, 0.001, 0.06, 0.0001, 0.001, 0.01, 0.001, 0.059, 0.134, 0.135, 0.002, 0.002, 0.619, 0.137, 0.568, 0.01, 0.199, 0.281, 0.159, 0.081, 0.077, 0.082, 0.071, 0.067, 0.079, 0.158, 0.041, 0.017, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.014, 0.009, 0.015, 0.009, 0.007, 0.006, 0.007, 0.006, 0.029, 0.002, 0.003, 0.007, 0.011, 0.006, 0.005, 0.01, 0.001, 0.008, 0.016, 0.01, 0.003, 0.01, 0.004, 0.011, 0.001, 0.001, 0.003, 0.0001, 0.003, 0.0001, 0.004, 0.0001, 0.067, 0.008, 0.022, 0.02, 0.069, 0.01, 0.012, 0.018, 0.056, 0.001, 0.008, 0.037, 0.02, 0.046, 0.054, 0.014, 0.001, 0.051, 0.037, 0.039, 0.027, 0.007, 0.006, 0.003, 0.012, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.481, 1.842, 2.043, 1.429, 0.162, 0.46, 0.448, 0.496, 0.265, 0.125, 0.001, 0.003, 0.806, 0.001, 0.316, 0.84, 0.08, 0.077, 0.114, 0.065, 0.394, 0.018, 2.734, 0.422, 0.001, 0.01, 0.11, 0.047, 0.088, 0.083, 0.052, 0.13, 0.228, 0.124, 0.058, 0.089, 0.032, 0.023, 0.02, 0.023, 0.023, 0.004, 0.0001, 0.09, 0.0001, 0.001, 0.008, 0.014, 3.574, 0.601, 2.221, 0.664, 1.335, 1.986, 0.299, 0.851, 2.427, 0.557, 1.658, 1.688, 1.249, 3.061, 4.029, 1.082, 0.0001, 0.0001, 0.335, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.018, 0.0001, 0.002, 0.001, 28.71, 14.784, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.144, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lt\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.086, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.626, 0.002, 0.167, 0.001, 0.0001, 0.009, 0.001, 0.01, 0.234, 0.234, 0.001, 0.002, 1.069, 0.088, 1.436, 0.009, 0.347, 0.549, 0.256, 0.135, 0.132, 0.151, 0.128, 0.13, 0.15, 0.368, 0.06, 0.018, 0.001, 0.002, 0.002, 0.001, 0.0001, 0.213, 0.143, 0.054, 0.128, 0.066, 0.049, 0.096, 0.041, 0.157, 0.121, 0.23, 0.188, 0.16, 0.109, 0.037, 0.238, 0.002, 0.129, 0.21, 0.163, 0.036, 0.209, 0.013, 0.047, 0.01, 0.016, 0.002, 0.0001, 0.002, 0.0001, 0.003, 0.0001, 8.107, 0.954, 0.391, 1.797, 4.13, 0.204, 1.223, 0.172, 9.411, 1.587, 2.883, 2.415, 2.501, 3.736, 4.946, 1.811, 0.003, 4.047, 5.62, 3.782, 3.399, 1.76, 0.016, 0.008, 1.047, 0.248, 0.0001, 0.015, 0.0001, 0.002, 0.0001, 0.475, 0.005, 0.003, 0.002, 0.002, 0.411, 0.001, 0.001, 0.006, 0.001, 0.001, 0.001, 0.019, 0.313, 0.0001, 0.001, 0.001, 0.001, 0.006, 0.247, 0.001, 0.0001, 0.001, 1.225, 0.001, 0.136, 0.001, 0.001, 0.108, 0.003, 0.111, 0.001, 0.364, 0.781, 0.001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.003, 0.002, 0.299, 0.001, 0.004, 0.013, 0.355, 0.007, 0.002, 0.007, 0.931, 0.001, 0.004, 0.001, 0.001, 0.004, 0.002, 0.003, 0.003, 0.003, 0.037, 0.575, 0.001, 0.0001, 0.0001, 0.29, 0.016, 2.467, 2.697, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.033, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.477, 0.001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.115, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.127, 0.002, 0.244, 0.0001, 0.0001, 0.007, 0.004, 0.029, 0.125, 0.125, 0.001, 0.001, 0.736, 0.236, 1.026, 0.016, 0.357, 0.45, 0.2, 0.113, 0.108, 0.13, 0.122, 0.121, 0.148, 0.271, 0.033, 0.009, 0.004, 0.002, 0.004, 0.001, 0.0001, 0.218, 0.193, 0.121, 0.247, 0.133, 0.148, 0.105, 0.221, 0.171, 0.071, 0.137, 0.127, 0.194, 0.145, 0.08, 0.133, 0.007, 0.124, 0.352, 0.152, 0.062, 0.099, 0.053, 0.006, 0.016, 0.016, 0.005, 0.0001, 0.005, 0.0001, 0.002, 0.001, 6.479, 0.879, 0.246, 3.008, 9.683, 1.285, 2.701, 0.948, 5.112, 0.784, 2.645, 3.726, 2.383, 5.836, 3.991, 1.273, 0.009, 6.373, 4.403, 5.512, 1.465, 1.904, 0.067, 0.025, 0.761, 0.055, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.031, 0.01, 0.005, 0.003, 0.003, 0.012, 0.002, 0.003, 0.002, 0.002, 0.001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.002, 0.002, 0.001, 0.02, 0.003, 0.002, 0.002, 0.001, 0.013, 0.005, 0.002, 0.001, 0.002, 0.001, 0.001, 0.003, 0.042, 0.013, 0.002, 0.002, 0.016, 0.934, 0.093, 0.004, 0.01, 0.021, 0.004, 0.076, 0.002, 0.01, 0.001, 0.002, 0.012, 0.007, 0.039, 0.01, 0.004, 0.006, 0.015, 0.002, 0.552, 0.004, 0.006, 0.078, 0.011, 0.006, 0.007, 0.003, 0.0001, 0.0001, 0.197, 1.726, 0.009, 0.008, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.017, 0.007, 0.044, 0.016, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.01, 0.009, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 0.002, 0.027, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hr\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.893, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.172, 0.002, 0.34, 0.0001, 0.001, 0.011, 0.002, 0.016, 0.182, 0.182, 0.001, 0.002, 0.943, 0.135, 1.23, 0.019, 0.3, 0.38, 0.204, 0.106, 0.1, 0.109, 0.096, 0.094, 0.112, 0.22, 0.065, 0.02, 0.009, 0.004, 0.009, 0.002, 0.0001, 0.156, 0.17, 0.109, 0.14, 0.063, 0.069, 0.111, 0.12, 0.137, 0.079, 0.163, 0.086, 0.175, 0.178, 0.118, 0.22, 0.004, 0.116, 0.267, 0.137, 0.108, 0.095, 0.03, 0.008, 0.009, 0.078, 0.011, 0.0001, 0.011, 0.0001, 0.002, 0.0001, 8.648, 1.028, 0.78, 2.344, 6.653, 0.218, 1.346, 0.572, 7.393, 3.932, 2.783, 2.724, 2.195, 4.91, 6.755, 1.994, 0.007, 4.039, 3.61, 3.329, 3.254, 2.478, 0.043, 0.016, 0.083, 1.288, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.039, 0.005, 0.004, 0.003, 0.002, 0.001, 0.003, 0.353, 0.002, 0.001, 0.001, 0.001, 0.016, 0.678, 0.001, 0.001, 0.004, 0.158, 0.001, 0.011, 0.002, 0.001, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.009, 0.005, 0.008, 0.001, 0.033, 0.524, 0.003, 0.002, 0.003, 0.001, 0.001, 0.002, 0.002, 0.01, 0.001, 0.005, 0.001, 0.004, 0.001, 0.001, 0.008, 0.004, 0.005, 0.005, 0.002, 0.004, 0.004, 0.001, 0.004, 0.002, 0.004, 0.006, 0.006, 0.016, 0.36, 0.002, 0.0001, 0.0001, 0.021, 0.044, 1.208, 0.914, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.011, 0.005, 0.028, 0.01, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.038, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"no\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.028, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.853, 0.002, 0.247, 0.0001, 0.001, 0.006, 0.004, 0.016, 0.159, 0.158, 0.001, 0.001, 0.698, 0.213, 1.037, 0.017, 0.377, 0.496, 0.255, 0.116, 0.113, 0.123, 0.117, 0.116, 0.152, 0.295, 0.042, 0.013, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.196, 0.176, 0.125, 0.246, 0.126, 0.148, 0.099, 0.211, 0.167, 0.071, 0.132, 0.135, 0.185, 0.133, 0.091, 0.127, 0.006, 0.11, 0.321, 0.146, 0.058, 0.092, 0.051, 0.007, 0.014, 0.011, 0.002, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 4.956, 1.168, 0.243, 2.996, 11.38, 1.384, 2.632, 1.02, 4.719, 0.546, 2.591, 3.946, 2.341, 6.218, 3.979, 1.354, 0.009, 6.417, 4.712, 5.821, 1.424, 1.732, 0.061, 0.029, 0.639, 0.049, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.041, 0.006, 0.003, 0.002, 0.002, 0.009, 0.002, 0.002, 0.001, 0.002, 0.001, 0.001, 0.002, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.034, 0.002, 0.001, 0.002, 0.001, 0.014, 0.003, 0.001, 0.001, 0.001, 0.002, 0.001, 0.002, 0.028, 0.009, 0.001, 0.002, 0.012, 0.765, 0.126, 0.003, 0.003, 0.021, 0.001, 0.062, 0.001, 0.006, 0.001, 0.001, 0.007, 0.003, 0.006, 0.006, 0.002, 0.003, 0.012, 0.001, 0.598, 0.002, 0.004, 0.062, 0.009, 0.004, 0.004, 0.002, 0.0001, 0.0001, 0.152, 1.588, 0.007, 0.007, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.004, 0.022, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.039, 0.001, 0.001, 0.004, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sr\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.872, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.68, 0.001, 0.1, 0.0001, 0.0001, 0.009, 0.0001, 0.005, 0.176, 0.176, 0.0001, 0.003, 0.5, 0.178, 0.762, 0.011, 0.275, 0.318, 0.214, 0.099, 0.096, 0.093, 0.078, 0.075, 0.084, 0.129, 0.031, 0.008, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.017, 0.01, 0.025, 0.013, 0.007, 0.006, 0.019, 0.007, 0.026, 0.003, 0.008, 0.007, 0.014, 0.016, 0.013, 0.016, 0.001, 0.009, 0.02, 0.011, 0.006, 0.008, 0.003, 0.004, 0.001, 0.003, 0.002, 0.0001, 0.002, 0.0001, 0.018, 0.0001, 0.453, 0.047, 0.05, 0.128, 0.37, 0.027, 0.066, 0.039, 0.393, 0.16, 0.152, 0.148, 0.154, 0.268, 0.352, 0.1, 0.001, 0.219, 0.193, 0.185, 0.165, 0.107, 0.003, 0.002, 0.007, 0.07, 0.053, 0.001, 0.053, 0.0001, 0.0001, 2.152, 2.07, 1.61, 1.756, 0.112, 0.204, 0.344, 0.339, 0.366, 0.003, 0.007, 0.001, 0.001, 0.031, 0.0001, 0.007, 0.082, 0.095, 0.143, 0.054, 0.071, 0.047, 0.006, 0.035, 1.459, 0.284, 0.347, 0.2, 0.143, 0.119, 0.086, 0.186, 0.072, 0.175, 0.071, 0.052, 0.034, 0.041, 0.014, 0.02, 0.016, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 4.933, 0.477, 1.401, 0.663, 1.33, 3.708, 0.225, 0.704, 3.913, 0.001, 1.472, 1.2, 1.198, 2.623, 3.682, 1.022, 0.0001, 0.0001, 0.018, 0.003, 0.054, 0.041, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 30.181, 10.982, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.062, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ca\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.816, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.948, 0.002, 0.294, 0.001, 0.011, 0.035, 0.001, 0.634, 0.154, 0.154, 0.001, 0.002, 1.001, 0.144, 0.747, 0.01, 0.301, 0.411, 0.25, 0.137, 0.131, 0.135, 0.12, 0.123, 0.144, 0.212, 0.051, 0.029, 0.002, 0.003, 0.003, 0.001, 0.0001, 0.252, 0.125, 0.23, 0.119, 0.296, 0.09, 0.091, 0.066, 0.12, 0.061, 0.034, 0.213, 0.174, 0.072, 0.049, 0.171, 0.012, 0.097, 0.192, 0.11, 0.053, 0.092, 0.024, 0.034, 0.01, 0.009, 0.002, 0.0001, 0.002, 0.0001, 0.004, 0.0001, 9.132, 1.004, 2.746, 3.236, 9.343, 0.681, 0.95, 0.465, 5.412, 0.169, 0.095, 4.932, 2.114, 4.848, 3.551, 1.884, 0.571, 5.202, 5.696, 4.416, 2.672, 1.094, 0.036, 0.312, 0.252, 0.123, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.044, 0.004, 0.004, 0.002, 0.002, 0.001, 0.001, 0.001, 0.002, 0.015, 0.001, 0.001, 0.001, 0.005, 0.001, 0.001, 0.001, 0.001, 0.002, 0.006, 0.003, 0.001, 0.001, 0.001, 0.001, 0.021, 0.001, 0.001, 0.003, 0.003, 0.001, 0.001, 0.327, 0.012, 0.002, 0.002, 0.002, 0.001, 0.001, 0.088, 0.218, 0.355, 0.001, 0.01, 0.003, 0.236, 0.001, 0.038, 0.005, 0.007, 0.161, 0.374, 0.002, 0.003, 0.003, 0.047, 0.003, 0.002, 0.063, 0.01, 0.034, 0.003, 0.002, 0.002, 0.0001, 0.0001, 0.099, 1.903, 0.005, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 0.004, 0.012, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.005, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.039, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sl\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.06, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.437, 0.024, 0.237, 0.001, 0.001, 0.007, 0.002, 0.011, 0.174, 0.174, 0.021, 0.002, 1.072, 0.17, 1.037, 0.022, 0.277, 0.429, 0.215, 0.122, 0.124, 0.121, 0.109, 0.108, 0.134, 0.239, 0.061, 0.025, 0.005, 0.006, 0.005, 0.002, 0.0001, 0.162, 0.141, 0.1, 0.122, 0.063, 0.075, 0.091, 0.086, 0.111, 0.082, 0.154, 0.138, 0.185, 0.145, 0.099, 0.224, 0.004, 0.106, 0.263, 0.133, 0.042, 0.163, 0.031, 0.007, 0.007, 0.087, 0.013, 0.0001, 0.014, 0.0001, 0.006, 0.0001, 7.7, 1.204, 0.709, 2.364, 7.782, 0.229, 1.139, 0.879, 6.985, 3.327, 2.701, 3.64, 2.037, 5.283, 6.653, 2.232, 0.006, 4.152, 3.513, 3.409, 1.654, 3.049, 0.039, 0.016, 0.079, 1.473, 0.0001, 0.01, 0.0001, 0.0001, 0.0001, 0.054, 0.004, 0.003, 0.002, 0.002, 0.001, 0.001, 0.011, 0.002, 0.002, 0.0001, 0.001, 0.021, 0.847, 0.001, 0.0001, 0.001, 0.002, 0.002, 0.027, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.056, 0.644, 0.007, 0.001, 0.003, 0.001, 0.001, 0.002, 0.003, 0.013, 0.001, 0.027, 0.001, 0.005, 0.001, 0.001, 0.007, 0.003, 0.004, 0.005, 0.002, 0.003, 0.006, 0.001, 0.004, 0.002, 0.004, 0.028, 0.008, 0.018, 0.391, 0.002, 0.0001, 0.0001, 0.071, 0.059, 0.881, 1.071, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.01, 0.005, 0.024, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.054, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.879, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.099, 0.004, 0.432, 0.0001, 0.0001, 0.013, 0.002, 0.007, 0.207, 0.208, 0.0001, 0.003, 0.965, 0.082, 1.276, 0.01, 0.332, 0.476, 0.254, 0.122, 0.117, 0.123, 0.105, 0.106, 0.127, 0.271, 0.045, 0.023, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.208, 0.134, 0.062, 0.128, 0.074, 0.067, 0.074, 0.058, 0.112, 0.068, 0.189, 0.194, 0.144, 0.089, 0.055, 0.234, 0.002, 0.136, 0.249, 0.163, 0.042, 0.182, 0.012, 0.007, 0.003, 0.051, 0.001, 0.0001, 0.001, 0.0001, 0.003, 0.0001, 8.58, 1.078, 0.806, 2.221, 4.451, 0.231, 1.228, 0.175, 6.667, 1.704, 2.603, 2.424, 2.389, 3.209, 2.883, 1.908, 0.003, 4.056, 5.825, 4.121, 3.633, 1.801, 0.012, 0.009, 0.029, 1.289, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.124, 2.988, 0.003, 0.002, 0.001, 0.006, 0.331, 0.001, 0.002, 0.001, 0.0001, 0.001, 0.015, 0.083, 0.0001, 0.001, 0.001, 0.001, 0.007, 1.174, 0.07, 0.0001, 0.001, 0.001, 0.002, 0.003, 0.001, 0.001, 0.005, 0.012, 0.009, 0.001, 0.06, 0.627, 0.004, 0.097, 0.002, 0.001, 0.001, 0.001, 0.001, 0.002, 0.006, 1.565, 0.0001, 0.002, 0.0001, 0.0001, 0.01, 0.002, 0.005, 0.002, 0.002, 0.005, 0.01, 0.106, 0.006, 0.002, 0.003, 0.01, 0.298, 0.012, 0.176, 0.002, 0.0001, 0.0001, 0.03, 0.013, 6.068, 1.452, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.051, 0.018, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.11, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"et\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.183, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.759, 0.003, 0.281, 0.0001, 0.0001, 0.013, 0.001, 0.037, 0.198, 0.199, 0.001, 0.003, 0.786, 0.203, 1.175, 0.017, 0.35, 0.548, 0.272, 0.142, 0.137, 0.143, 0.127, 0.129, 0.154, 0.323, 0.059, 0.022, 0.017, 0.003, 0.017, 0.003, 0.0001, 0.235, 0.096, 0.074, 0.061, 0.173, 0.056, 0.064, 0.105, 0.122, 0.088, 0.255, 0.166, 0.186, 0.114, 0.065, 0.208, 0.003, 0.138, 0.296, 0.251, 0.046, 0.167, 0.033, 0.011, 0.008, 0.01, 0.008, 0.0001, 0.008, 0.0001, 0.004, 0.0001, 9.665, 0.664, 0.152, 2.822, 7.678, 0.189, 1.393, 1.095, 7.816, 1.25, 3.234, 4.738, 2.585, 4.03, 3.549, 1.167, 0.005, 3.003, 6.68, 5.333, 4.153, 1.613, 0.043, 0.017, 0.074, 0.045, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.13, 0.015, 0.01, 0.006, 0.004, 0.003, 0.003, 0.004, 0.002, 0.002, 0.001, 0.002, 0.003, 0.005, 0.001, 0.003, 0.002, 0.002, 0.003, 0.102, 0.002, 0.008, 0.003, 0.003, 0.002, 0.004, 0.002, 0.001, 0.044, 0.005, 0.006, 0.003, 0.016, 0.035, 0.003, 0.002, 0.833, 0.002, 0.001, 0.002, 0.002, 0.01, 0.001, 0.006, 0.001, 0.005, 0.001, 0.001, 0.017, 0.004, 0.012, 0.007, 0.005, 0.763, 0.179, 0.003, 0.015, 0.005, 0.008, 0.007, 0.518, 0.012, 0.028, 0.003, 0.0001, 0.0001, 0.02, 2.358, 0.019, 0.061, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 0.004, 0.104, 0.037, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.123, 0.001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hi\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.374, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.123, 0.002, 0.071, 0.0001, 0.001, 0.004, 0.0001, 0.023, 0.08, 0.08, 0.0001, 0.001, 0.255, 0.072, 0.052, 0.006, 0.068, 0.07, 0.044, 0.02, 0.019, 0.023, 0.019, 0.019, 0.021, 0.04, 0.021, 0.006, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.008, 0.004, 0.007, 0.004, 0.005, 0.003, 0.004, 0.003, 0.006, 0.001, 0.002, 0.003, 0.005, 0.004, 0.003, 0.005, 0.0001, 0.003, 0.008, 0.005, 0.002, 0.002, 0.002, 0.001, 0.001, 0.001, 0.007, 0.0001, 0.008, 0.0001, 0.001, 0.0001, 0.049, 0.007, 0.017, 0.016, 0.052, 0.008, 0.01, 0.017, 0.038, 0.001, 0.004, 0.024, 0.015, 0.034, 0.035, 0.012, 0.001, 0.033, 0.03, 0.034, 0.015, 0.005, 0.005, 0.002, 0.008, 0.001, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 1.039, 0.443, 1.278, 0.061, 0.0001, 0.273, 0.146, 1.879, 0.535, 0.214, 0.013, 0.729, 0.054, 1.826, 0.0001, 0.253, 0.014, 0.012, 0.0001, 0.042, 0.14, 2.07, 0.133, 0.43, 0.035, 0.004, 0.215, 0.046, 0.503, 0.014, 0.016, 0.269, 0.037, 0.213, 0.023, 0.155, 24.777, 7.162, 0.554, 0.224, 1.23, 0.009, 0.8, 0.117, 0.393, 0.245, 0.995, 0.828, 2.018, 0.001, 0.771, 0.001, 0.001, 0.707, 0.299, 0.18, 1.226, 0.94, 0.0001, 0.0001, 0.133, 0.001, 2.558, 1.303, 0.0001, 0.0001, 0.008, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.261, 0.0001, 0.024, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n};\nconst EXTENSIVE_LANG_FREQS = Object.assign({}, COMMON_LANG_FREQS, {\n    \"aa\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.161, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.548, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.29, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.29, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.452, 0.645, 0.645, 2.581, 9.032, 0.0001, 5.161, 3.871, 5.806, 0.0001, 1.935, 2.581, 1.29, 5.161, 2.581, 1.29, 0.0001, 4.516, 0.645, 3.226, 0.645, 0.0001, 1.29, 0.0001, 0.645, 1.29, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.581, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.29, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.0001, 0.0001, 1.29, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.645, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.871, 0.645, 2.581, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ab\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.925, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.477, 0.003, 0.06, 0.0001, 0.0001, 0.005, 0.0001, 0.013, 0.269, 0.273, 0.001, 0.001, 0.518, 0.306, 0.76, 0.006, 0.291, 0.709, 0.42, 0.294, 0.242, 0.237, 0.242, 0.222, 0.25, 0.32, 0.04, 0.028, 0.008, 0.0001, 0.008, 0.002, 0.0001, 0.004, 0.004, 0.004, 0.006, 0.001, 0.002, 0.001, 0.001, 0.033, 0.012, 0.001, 0.001, 0.002, 0.001, 0.011, 0.003, 0.0001, 0.002, 0.009, 0.002, 0.002, 0.007, 0.006, 0.01, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.679, 0.013, 0.02, 0.028, 0.073, 0.002, 0.006, 0.01, 0.057, 0.001, 0.005, 0.035, 0.039, 0.025, 0.031, 0.027, 0.011, 0.045, 0.036, 0.027, 0.037, 0.009, 0.002, 0.01, 0.027, 0.004, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 3.029, 1.109, 1.569, 1.131, 0.085, 0.805, 0.262, 0.083, 0.992, 0.002, 0.003, 2.495, 0.791, 0.003, 0.006, 0.03, 0.654, 0.059, 0.04, 0.137, 0.332, 0.075, 0.041, 0.012, 0.142, 2.586, 0.087, 1.002, 0.086, 0.047, 0.045, 0.323, 0.073, 0.328, 0.016, 0.067, 0.011, 0.052, 0.054, 0.455, 0.024, 0.199, 0.0001, 0.026, 0.015, 0.539, 0.001, 0.001, 7.771, 0.517, 0.209, 1.034, 0.683, 1.368, 0.238, 0.686, 3.093, 0.042, 0.729, 1.305, 0.754, 1.868, 1.136, 0.676, 0.0001, 0.0001, 0.065, 0.019, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.001, 0.155, 0.0001, 0.005, 0.002, 22.83, 11.878, 3.39, 2.86, 0.019, 0.007, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.386, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ace\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.36, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.198, 0.0001, 0.137, 0.0001, 0.001, 0.007, 0.0001, 0.256, 0.125, 0.125, 0.0001, 0.0001, 1.042, 0.179, 1.302, 0.01, 0.401, 0.568, 0.284, 0.118, 0.113, 0.112, 0.099, 0.093, 0.097, 0.13, 0.041, 0.007, 0.003, 0.001, 0.003, 0.001, 0.0001, 0.777, 0.587, 0.153, 0.133, 0.028, 0.036, 0.256, 0.095, 0.205, 0.159, 0.483, 0.331, 0.444, 0.183, 0.028, 0.481, 0.019, 0.179, 0.473, 0.324, 0.101, 0.026, 0.042, 0.006, 0.021, 0.009, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 11.224, 1.773, 0.851, 1.583, 5.304, 0.086, 3.693, 3.458, 3.728, 0.528, 2.425, 2.037, 2.4, 8.165, 2.618, 1.607, 0.015, 2.485, 1.74, 2.806, 6.018, 0.112, 0.344, 0.01, 1.486, 0.04, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.039, 0.008, 0.005, 0.009, 0.016, 0.007, 0.006, 0.006, 0.01, 0.004, 0.008, 0.003, 0.002, 0.004, 0.012, 0.004, 0.007, 0.003, 0.004, 0.014, 0.002, 0.001, 0.001, 0.002, 0.004, 0.016, 0.001, 0.001, 0.002, 0.002, 0.001, 0.007, 0.007, 0.006, 0.003, 0.008, 0.005, 0.002, 0.001, 0.019, 1.193, 0.401, 0.007, 0.574, 0.003, 0.006, 0.002, 0.006, 0.025, 0.011, 0.006, 0.008, 0.873, 0.004, 0.151, 0.002, 0.005, 0.005, 0.008, 0.007, 0.004, 0.001, 0.002, 0.002, 0.0001, 0.0001, 0.025, 3.205, 0.014, 0.012, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.004, 0.001, 0.012, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.061, 0.078, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 0.011, 0.039, 0.001, 0.001, 0.005, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ady\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.142, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.359, 0.001, 0.089, 0.0001, 0.0001, 0.003, 0.0001, 0.006, 0.111, 0.11, 0.001, 0.001, 0.645, 0.309, 0.862, 0.007, 0.118, 0.279, 0.082, 0.059, 0.052, 0.055, 0.051, 0.045, 0.057, 0.071, 0.053, 0.011, 0.003, 0.003, 0.003, 0.001, 0.0001, 0.008, 0.007, 0.003, 0.003, 0.002, 0.003, 0.0001, 0.002, 1.228, 0.004, 0.001, 0.003, 0.004, 0.002, 0.004, 0.005, 0.0001, 0.001, 0.006, 0.003, 0.002, 0.004, 0.002, 0.008, 0.0001, 0.0001, 0.005, 0.0001, 0.005, 0.0001, 0.001, 0.0001, 0.05, 0.009, 0.016, 0.02, 0.067, 0.009, 0.011, 0.022, 0.046, 0.001, 0.006, 0.031, 0.02, 0.036, 0.037, 0.013, 0.0001, 0.038, 0.031, 0.043, 0.016, 0.004, 0.008, 0.003, 0.011, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.778, 0.778, 1.192, 2.098, 0.406, 1.886, 0.203, 0.188, 0.585, 0.672, 2.887, 2.927, 0.717, 6.004, 0.019, 0.21, 0.317, 0.122, 0.019, 0.226, 0.145, 0.045, 0.02, 0.041, 0.09, 0.005, 0.262, 0.059, 0.092, 0.079, 0.053, 0.07, 0.076, 0.092, 0.086, 0.055, 0.029, 0.124, 0.039, 0.031, 0.045, 0.011, 0.0001, 0.031, 0.0001, 0.018, 0.005, 0.018, 2.762, 0.645, 0.171, 1.681, 0.855, 1.134, 0.39, 0.89, 1.618, 0.147, 1.755, 1.169, 1.845, 1.192, 0.989, 0.792, 0.0001, 0.0001, 0.094, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.006, 0.0001, 0.003, 0.004, 0.0001, 0.0001, 20.033, 23.044, 0.001, 0.227, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.229, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"af\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.732, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.494, 0.002, 0.361, 0.0001, 0.001, 0.008, 0.001, 0.297, 0.136, 0.136, 0.002, 0.001, 0.651, 0.269, 0.893, 0.01, 0.25, 0.371, 0.17, 0.095, 0.09, 0.104, 0.09, 0.086, 0.122, 0.213, 0.049, 0.019, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.241, 0.154, 0.103, 0.382, 0.093, 0.072, 0.119, 0.168, 0.14, 0.087, 0.137, 0.088, 0.157, 0.131, 0.103, 0.129, 0.004, 0.104, 0.29, 0.11, 0.03, 0.115, 0.083, 0.006, 0.008, 0.015, 0.002, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 6.202, 1.128, 0.17, 4.12, 13.284, 0.609, 2.484, 1.201, 6.602, 0.17, 2.299, 2.976, 1.671, 6.221, 4.571, 1.147, 0.006, 5.197, 4.908, 4.263, 1.7, 1.691, 1.336, 0.018, 0.832, 0.043, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.182, 0.005, 0.004, 0.003, 0.002, 0.001, 0.001, 0.001, 0.003, 0.002, 0.001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.002, 0.001, 0.001, 0.024, 0.002, 0.001, 0.001, 0.001, 0.003, 0.118, 0.001, 0.001, 0.017, 0.016, 0.001, 0.001, 0.076, 0.018, 0.001, 0.005, 0.004, 0.002, 0.002, 0.003, 0.003, 0.032, 0.053, 0.119, 0.001, 0.004, 0.001, 0.014, 0.007, 0.003, 0.004, 0.007, 0.002, 0.003, 0.005, 0.001, 0.005, 0.002, 0.003, 0.003, 0.007, 0.003, 0.003, 0.002, 0.0001, 0.0001, 0.084, 0.264, 0.004, 0.005, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.003, 0.001, 0.0001, 0.009, 0.004, 0.022, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.003, 0.181, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ak\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.856, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 17.14, 0.001, 0.181, 0.0001, 0.002, 0.004, 0.001, 0.124, 0.134, 0.137, 0.001, 0.0001, 0.719, 0.119, 1.218, 0.014, 0.179, 0.257, 0.137, 0.052, 0.061, 0.075, 0.065, 0.054, 0.059, 0.197, 0.031, 0.029, 0.002, 0.015, 0.002, 0.018, 0.0001, 0.566, 0.167, 0.173, 0.118, 0.172, 0.085, 0.258, 0.093, 0.098, 0.056, 0.193, 0.111, 0.204, 0.399, 0.102, 0.121, 0.012, 0.083, 0.271, 0.145, 0.084, 0.04, 0.206, 0.011, 0.078, 0.02, 0.025, 0.0001, 0.025, 0.0001, 0.0001, 0.0001, 10.911, 1.752, 0.404, 1.704, 5.791, 1.18, 0.501, 1.542, 4.479, 0.04, 1.975, 0.667, 3.211, 7.434, 5.302, 0.888, 0.03, 2.693, 2.695, 1.838, 2.674, 0.126, 2.695, 0.023, 2.4, 0.077, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.046, 0.01, 0.002, 0.005, 0.002, 0.006, 0.095, 0.003, 0.01, 0.003, 0.006, 0.011, 0.002, 0.017, 0.002, 0.004, 0.052, 0.011, 0.001, 0.002, 1.774, 0.002, 0.002, 0.001, 0.0001, 0.02, 0.0001, 1.749, 0.01, 0.01, 0.0001, 0.0001, 0.151, 0.004, 0.001, 0.002, 0.006, 0.022, 0.001, 0.003, 0.005, 0.01, 0.002, 0.003, 0.002, 0.005, 0.001, 0.003, 0.01, 0.006, 0.005, 0.012, 0.015, 0.552, 0.007, 0.003, 0.008, 0.006, 0.006, 0.392, 0.013, 0.005, 0.007, 0.004, 0.0001, 0.0001, 0.146, 0.054, 0.004, 0.004, 0.139, 0.0001, 0.0001, 3.532, 0.002, 0.008, 0.003, 0.34, 0.547, 0.0001, 0.045, 0.018, 0.0001, 0.0001, 0.018, 0.055, 0.008, 0.002, 0.016, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.048, 0.037, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"als\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.981, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.805, 0.003, 0.368, 0.0001, 0.0001, 0.09, 0.001, 0.06, 0.177, 0.177, 0.009, 0.001, 0.909, 0.215, 1.001, 0.022, 0.318, 0.46, 0.232, 0.128, 0.122, 0.132, 0.116, 0.119, 0.142, 0.206, 0.063, 0.024, 0.004, 0.003, 0.004, 0.001, 0.0001, 0.315, 0.452, 0.163, 0.512, 0.205, 0.236, 0.319, 0.219, 0.188, 0.156, 0.222, 0.212, 0.32, 0.172, 0.112, 0.199, 0.01, 0.225, 0.653, 0.132, 0.131, 0.173, 0.23, 0.004, 0.019, 0.129, 0.009, 0.0001, 0.009, 0.0001, 0.003, 0.001, 3.964, 1.276, 2.626, 3.453, 8.363, 1.057, 2.308, 3.744, 6.377, 0.069, 0.66, 2.78, 2.213, 4.452, 3.12, 0.516, 0.012, 5.572, 4.629, 4.341, 2.669, 0.935, 0.979, 0.046, 0.315, 0.925, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.124, 0.003, 0.002, 0.002, 0.034, 0.001, 0.001, 0.001, 0.005, 0.003, 0.0001, 0.0001, 0.004, 0.002, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.023, 0.002, 0.001, 0.01, 0.001, 0.003, 0.02, 0.003, 0.002, 0.048, 0.001, 0.034, 0.042, 0.156, 0.005, 0.005, 0.003, 1.018, 0.003, 0.001, 0.003, 0.354, 0.039, 0.002, 0.022, 0.079, 0.004, 0.001, 0.002, 0.004, 0.003, 0.015, 0.003, 0.029, 0.017, 0.333, 0.001, 0.002, 0.045, 0.004, 0.015, 0.5, 0.004, 0.001, 0.002, 0.0001, 0.0001, 0.108, 2.635, 0.006, 0.005, 0.0001, 0.005, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.011, 0.005, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.005, 0.12, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"am\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.067, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.441, 0.005, 0.08, 0.001, 0.0001, 0.003, 0.0001, 0.013, 0.12, 0.121, 0.002, 0.001, 0.021, 0.111, 0.25, 0.041, 0.102, 0.167, 0.089, 0.049, 0.044, 0.048, 0.044, 0.043, 0.057, 0.081, 0.018, 0.001, 0.048, 0.019, 0.048, 0.008, 0.0001, 0.009, 0.005, 0.007, 0.005, 0.005, 0.004, 0.003, 0.003, 0.004, 0.004, 0.002, 0.003, 0.006, 0.003, 0.002, 0.004, 0.001, 0.003, 0.007, 0.005, 0.002, 0.002, 0.002, 0.001, 0.001, 0.001, 0.017, 0.0001, 0.02, 0.0001, 0.007, 0.0001, 0.059, 0.06, 0.021, 0.018, 0.066, 0.009, 0.014, 0.02, 0.05, 0.001, 0.005, 0.029, 0.021, 0.042, 0.045, 0.014, 0.001, 0.09, 0.032, 0.04, 0.026, 0.005, 0.007, 0.003, 0.012, 0.002, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.402, 0.178, 0.052, 0.194, 0.053, 0.478, 0.259, 0.003, 10.51, 5.557, 5.996, 6.414, 2.305, 3.741, 0.258, 0.015, 0.706, 0.091, 0.071, 0.613, 0.064, 1.598, 0.107, 0.008, 0.907, 0.126, 0.312, 0.688, 0.12, 0.989, 0.129, 0.009, 2.006, 0.213, 0.679, 0.599, 0.206, 1.204, 0.134, 0.012, 1.72, 0.213, 0.231, 1.059, 0.087, 1.793, 0.284, 0.013, 1.151, 0.255, 0.312, 0.726, 0.115, 2.127, 0.177, 0.025, 0.19, 0.059, 0.032, 0.208, 0.015, 0.466, 0.016, 0.003, 0.0001, 0.0001, 0.096, 0.004, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.005, 0.009, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.017, 0.046, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 29.467, 0.047, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.001, 0.0001, 0.017, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"an\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.253, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.49, 0.005, 0.725, 0.0001, 0.0001, 0.005, 0.001, 0.998, 0.246, 0.246, 0.002, 0.002, 1.083, 0.164, 0.685, 0.057, 0.291, 0.382, 0.213, 0.125, 0.12, 0.124, 0.115, 0.119, 0.131, 0.221, 0.057, 0.029, 0.007, 0.01, 0.006, 0.001, 0.0001, 0.411, 0.169, 0.298, 0.091, 0.216, 0.095, 0.1, 0.059, 0.154, 0.037, 0.024, 0.177, 0.199, 0.072, 0.146, 0.19, 0.011, 0.122, 0.227, 0.128, 0.065, 0.101, 0.021, 0.037, 0.032, 0.028, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 9.483, 1.074, 3.3, 3.436, 7.765, 0.618, 0.822, 0.72, 5.365, 0.027, 0.17, 3.124, 1.916, 5.869, 6.23, 1.654, 0.435, 4.741, 4.813, 3.981, 2.96, 0.573, 0.028, 0.256, 1.248, 0.309, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.014, 0.007, 0.003, 0.003, 0.002, 0.001, 0.001, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.007, 0.002, 0.001, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.002, 0.028, 0.174, 0.002, 0.002, 0.003, 0.001, 0.001, 0.008, 0.012, 0.227, 0.002, 0.014, 0.002, 0.209, 0.001, 0.002, 0.004, 0.013, 0.086, 0.54, 0.002, 0.002, 0.003, 0.002, 0.004, 0.002, 0.027, 0.014, 0.019, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.127, 1.249, 0.007, 0.007, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.009, 0.005, 0.014, 0.005, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.002, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.013, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ang\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.542, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.629, 0.001, 0.406, 0.001, 0.001, 0.005, 0.001, 0.041, 0.166, 0.166, 0.001, 0.001, 0.772, 0.085, 0.973, 0.007, 0.229, 0.292, 0.152, 0.081, 0.082, 0.095, 0.083, 0.089, 0.101, 0.139, 0.058, 0.032, 0.011, 0.001, 0.011, 0.001, 0.0001, 0.204, 0.193, 0.317, 0.089, 0.179, 0.148, 0.229, 0.279, 0.189, 0.034, 0.031, 0.128, 0.195, 0.168, 0.087, 0.103, 0.007, 0.125, 0.419, 0.122, 0.043, 0.034, 0.145, 0.006, 0.012, 0.007, 0.02, 0.0001, 0.02, 0.0001, 0.0001, 0.0001, 5.666, 0.997, 2.318, 3.22, 8.139, 1.491, 2.061, 1.574, 3.89, 0.022, 0.109, 2.731, 2.332, 6.4, 3.389, 0.62, 0.014, 4.435, 4.532, 3.015, 1.701, 0.127, 1.341, 0.09, 0.658, 0.04, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 0.032, 0.62, 0.006, 0.006, 0.004, 0.003, 0.052, 0.002, 0.001, 0.001, 0.002, 0.033, 0.008, 0.478, 0.002, 0.002, 0.01, 0.003, 0.05, 1.069, 0.004, 0.001, 0.004, 0.002, 0.003, 0.003, 0.011, 0.012, 0.009, 0.068, 0.141, 0.003, 0.009, 0.037, 0.013, 0.751, 0.006, 0.002, 1.085, 0.003, 0.002, 0.01, 0.039, 0.996, 0.002, 0.008, 0.002, 0.002, 0.371, 0.007, 0.005, 0.069, 0.002, 0.003, 0.002, 0.008, 0.006, 0.003, 0.005, 0.004, 0.005, 0.004, 2.003, 0.078, 0.0001, 0.0001, 0.009, 3.7, 2.566, 0.742, 0.075, 0.766, 0.127, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.012, 0.006, 0.017, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.006, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.024, 0.022, 0.003, 0.001, 0.003, 0.002, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"arc\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.038, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.39, 0.001, 0.055, 0.0001, 0.0001, 0.007, 0.0001, 0.005, 0.294, 0.294, 0.0001, 0.0001, 0.039, 0.041, 0.295, 0.017, 0.207, 0.161, 0.078, 0.046, 0.044, 0.053, 0.042, 0.044, 0.043, 0.091, 0.189, 0.006, 0.003, 0.004, 0.003, 0.0001, 0.0001, 0.01, 0.01, 0.013, 0.007, 0.004, 0.004, 0.006, 0.005, 0.007, 0.003, 0.005, 0.008, 0.011, 0.008, 0.004, 0.008, 0.001, 0.007, 0.013, 0.004, 0.003, 0.005, 0.004, 0.001, 0.001, 0.002, 0.005, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.107, 0.013, 0.023, 0.039, 0.088, 0.011, 0.022, 0.025, 0.081, 0.003, 0.021, 0.05, 0.023, 0.07, 0.066, 0.018, 0.002, 0.062, 0.042, 0.051, 0.032, 0.013, 0.011, 0.006, 0.012, 0.006, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.359, 0.027, 0.139, 0.022, 0.095, 0.021, 0.095, 0.051, 0.776, 0.005, 0.029, 0.002, 0.032, 0.003, 0.011, 0.005, 6.959, 0.008, 1.918, 0.561, 0.013, 2.47, 0.003, 1.261, 3.75, 0.282, 0.787, 0.504, 0.018, 4.683, 0.009, 0.786, 1.796, 2.249, 2.761, 0.874, 0.009, 1.007, 0.747, 0.053, 0.199, 0.858, 2.538, 1.15, 2.879, 0.016, 0.009, 0.021, 0.023, 0.056, 0.023, 0.019, 0.01, 0.046, 0.007, 0.011, 0.024, 0.035, 0.015, 0.012, 0.048, 0.023, 0.008, 0.047, 0.0001, 0.0001, 0.004, 0.019, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.832, 0.001, 0.126, 0.053, 0.042, 0.017, 0.001, 0.0001, 0.0001, 0.009, 0.024, 0.108, 0.212, 0.141, 0.001, 0.004, 41.501, 0.031, 0.0001, 0.0001, 0.002, 0.019, 0.018, 0.0001, 0.001, 0.004, 0.004, 0.0001, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"arz\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.02, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.739, 0.003, 0.126, 0.0001, 0.0001, 0.004, 0.001, 0.003, 0.118, 0.124, 0.002, 0.001, 0.064, 0.045, 0.405, 0.01, 0.141, 0.269, 0.129, 0.067, 0.063, 0.072, 0.064, 0.065, 0.08, 0.165, 0.039, 0.002, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.012, 0.009, 0.011, 0.008, 0.005, 0.005, 0.005, 0.006, 0.006, 0.005, 0.004, 0.009, 0.011, 0.005, 0.003, 0.007, 0.0001, 0.006, 0.013, 0.009, 0.001, 0.004, 0.004, 0.001, 0.001, 0.001, 0.006, 0.001, 0.006, 0.0001, 0.002, 0.0001, 0.091, 0.01, 0.025, 0.026, 0.093, 0.01, 0.015, 0.024, 0.072, 0.002, 0.01, 0.045, 0.023, 0.064, 0.06, 0.013, 0.001, 0.06, 0.046, 0.047, 0.027, 0.009, 0.007, 0.004, 0.017, 0.005, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.111, 1.136, 0.763, 1.043, 4.458, 2.752, 2.413, 1.721, 2.708, 1.077, 3.156, 0.021, 0.238, 0.002, 0.017, 0.028, 0.008, 0.018, 0.006, 0.004, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.003, 0.003, 0.004, 0.0001, 0.003, 0.019, 0.06, 0.018, 0.274, 0.041, 0.116, 0.08, 6.51, 1.771, 0.79, 1.749, 0.151, 0.593, 0.743, 0.294, 1.313, 0.079, 2.202, 0.292, 1.274, 0.493, 0.453, 0.187, 0.361, 0.078, 1.267, 0.19, 0.005, 0.002, 0.002, 0.011, 0.002, 0.0001, 0.0001, 0.025, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 0.004, 0.01, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.004, 21.565, 21.383, 0.022, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.029, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"as\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.296, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.811, 0.001, 0.086, 0.0001, 0.0001, 0.005, 0.0001, 0.083, 0.075, 0.077, 0.0001, 0.001, 0.203, 0.086, 0.044, 0.006, 0.008, 0.009, 0.006, 0.004, 0.003, 0.003, 0.002, 0.002, 0.003, 0.004, 0.022, 0.007, 0.002, 0.003, 0.002, 0.001, 0.0001, 0.015, 0.009, 0.013, 0.007, 0.006, 0.005, 0.005, 0.006, 0.011, 0.003, 0.003, 0.005, 0.01, 0.007, 0.004, 0.011, 0.001, 0.008, 0.013, 0.013, 0.003, 0.002, 0.004, 0.0001, 0.001, 0.001, 0.01, 0.0001, 0.01, 0.0001, 0.001, 0.0001, 0.213, 0.031, 0.074, 0.083, 0.255, 0.044, 0.045, 0.095, 0.18, 0.004, 0.017, 0.099, 0.058, 0.166, 0.164, 0.046, 0.002, 0.151, 0.14, 0.179, 0.063, 0.023, 0.027, 0.005, 0.036, 0.003, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.537, 0.769, 0.261, 0.102, 0.001, 0.242, 0.382, 1.586, 0.215, 0.133, 0.002, 0.429, 0.033, 1.928, 0.026, 0.213, 0.004, 0.0001, 0.0001, 0.14, 0.003, 1.299, 0.21, 0.401, 0.056, 0.073, 0.394, 0.328, 0.382, 0.006, 0.051, 0.353, 0.081, 0.128, 0.02, 0.231, 1.75, 0.525, 21.552, 9.182, 1.32, 0.031, 0.846, 0.112, 0.982, 0.29, 0.858, 1.027, 2.855, 0.297, 0.931, 0.0001, 0.0001, 0.0001, 0.293, 0.318, 0.674, 0.559, 0.001, 0.0001, 0.584, 0.0001, 2.717, 1.766, 0.0001, 0.0001, 0.009, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.161, 0.0001, 0.072, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ast\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.724, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.007, 0.002, 0.424, 0.002, 0.001, 0.01, 0.003, 0.548, 0.156, 0.156, 0.002, 0.003, 1.046, 0.096, 0.743, 0.015, 0.245, 0.288, 0.158, 0.086, 0.078, 0.093, 0.076, 0.077, 0.093, 0.166, 0.056, 0.032, 0.002, 0.005, 0.002, 0.002, 0.0001, 0.218, 0.121, 0.236, 0.117, 0.257, 0.089, 0.088, 0.078, 0.115, 0.051, 0.038, 0.23, 0.167, 0.117, 0.051, 0.161, 0.007, 0.094, 0.198, 0.134, 0.043, 0.06, 0.041, 0.061, 0.037, 0.011, 0.014, 0.0001, 0.014, 0.0001, 0.001, 0.0001, 8.074, 0.835, 3.151, 3.345, 9.578, 0.701, 0.803, 0.452, 5.046, 0.025, 0.11, 4.637, 2.087, 5.542, 5.253, 1.877, 0.488, 4.828, 5.384, 3.477, 3.909, 0.672, 0.055, 0.4, 0.967, 0.259, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.04, 0.01, 0.002, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.003, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.01, 0.01, 0.001, 0.0001, 0.001, 0.002, 0.009, 0.001, 0.001, 0.005, 0.006, 0.0001, 0.001, 0.026, 0.531, 0.001, 0.001, 0.002, 0.001, 0.002, 0.002, 0.002, 0.291, 0.001, 0.019, 0.001, 0.46, 0.001, 0.001, 0.005, 0.157, 0.004, 0.608, 0.002, 0.002, 0.003, 0.002, 0.004, 0.002, 0.119, 0.021, 0.027, 0.002, 0.001, 0.003, 0.0001, 0.0001, 0.073, 2.207, 0.003, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.012, 0.005, 0.007, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.039, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"atj\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.34, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.835, 0.0001, 0.034, 0.0001, 0.0001, 0.001, 0.0001, 0.005, 0.045, 0.047, 0.0001, 0.0001, 0.548, 0.045, 1.11, 0.006, 0.039, 0.075, 0.033, 0.013, 0.017, 0.015, 0.02, 0.018, 0.017, 0.061, 0.024, 0.003, 0.015, 0.0001, 0.015, 0.002, 0.0001, 0.175, 0.012, 0.062, 0.025, 0.193, 0.022, 0.01, 0.006, 0.035, 0.021, 0.212, 0.019, 0.332, 0.208, 0.141, 0.099, 0.007, 0.017, 0.034, 0.12, 0.001, 0.003, 0.089, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 11.805, 0.044, 6.264, 0.083, 5.028, 0.008, 0.026, 0.952, 15.443, 0.004, 9.886, 0.134, 2.846, 5.167, 5.337, 2.131, 0.022, 2.079, 2.27, 7.277, 0.131, 0.025, 4.581, 0.005, 0.015, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.009, 0.046, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.015, 0.069, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"av\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.031, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.23, 0.001, 0.083, 0.0001, 0.0001, 0.007, 0.001, 0.001, 0.166, 0.166, 0.001, 0.001, 0.458, 0.25, 0.562, 0.01, 0.133, 0.234, 0.149, 0.084, 0.058, 0.065, 0.053, 0.053, 0.06, 0.094, 0.055, 0.017, 0.001, 0.003, 0.001, 0.003, 0.0001, 0.011, 0.006, 0.01, 0.003, 0.003, 0.003, 0.003, 0.002, 0.777, 0.001, 0.002, 0.002, 0.006, 0.003, 0.003, 0.002, 0.0001, 0.002, 0.007, 0.008, 0.003, 0.006, 0.001, 0.011, 0.001, 0.0001, 0.007, 0.0001, 0.007, 0.0001, 0.009, 0.0001, 0.075, 0.008, 0.02, 0.025, 0.067, 0.007, 0.015, 0.018, 0.067, 0.001, 0.008, 0.038, 0.014, 0.043, 0.038, 0.019, 0.001, 0.041, 0.043, 0.036, 0.031, 0.01, 0.006, 0.003, 0.01, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.671, 1.227, 0.995, 2.675, 0.059, 0.905, 0.851, 0.335, 0.128, 0.084, 1.771, 0.03, 0.884, 0.039, 0.044, 0.818, 0.134, 0.075, 0.027, 0.273, 0.227, 0.015, 0.029, 0.016, 0.039, 0.006, 0.125, 0.043, 0.127, 0.032, 0.014, 0.032, 0.185, 0.089, 0.062, 0.016, 0.021, 0.082, 0.047, 0.033, 0.042, 0.006, 0.002, 0.039, 0.002, 0.019, 0.005, 0.013, 7.089, 1.927, 0.825, 1.964, 1.317, 1.929, 0.263, 0.636, 2.852, 0.187, 1.471, 3.734, 0.878, 1.983, 1.647, 0.208, 0.0001, 0.0001, 0.195, 0.006, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.022, 0.0001, 0.001, 0.0001, 30.778, 12.343, 0.0001, 0.534, 0.0001, 0.002, 0.0001, 0.001, 0.025, 0.022, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.177, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ay\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.037, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.588, 0.005, 0.247, 0.0001, 0.0001, 0.0001, 0.027, 1.72, 0.603, 0.602, 0.046, 0.001, 1.21, 0.158, 1.031, 0.021, 0.387, 0.817, 0.515, 0.316, 0.306, 0.36, 0.273, 0.279, 0.341, 0.428, 0.504, 0.129, 0.064, 0.005, 0.064, 0.147, 0.0001, 0.442, 0.126, 0.339, 0.185, 0.072, 0.071, 0.077, 0.1, 0.109, 0.302, 0.254, 0.268, 0.282, 0.145, 0.064, 0.43, 0.127, 0.121, 0.288, 0.2, 0.25, 0.05, 0.191, 0.012, 0.11, 0.013, 0.007, 0.0001, 0.008, 0.0001, 0.002, 0.004, 14.491, 0.243, 1.49, 0.745, 1.57, 0.085, 0.27, 2.104, 6.268, 1.613, 3.058, 2.342, 2.397, 3.14, 1.316, 1.65, 1.821, 3.874, 4.07, 2.906, 5.224, 0.153, 1.248, 0.859, 2.145, 0.119, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.211, 0.009, 0.003, 0.004, 0.002, 0.001, 0.002, 0.002, 0.003, 0.002, 0.001, 0.002, 0.002, 0.003, 0.002, 0.002, 0.004, 0.008, 0.001, 0.016, 0.006, 0.002, 0.001, 0.001, 0.005, 0.126, 0.002, 0.002, 0.008, 0.019, 0.001, 0.001, 0.061, 0.068, 0.001, 0.003, 0.22, 0.002, 0.002, 0.004, 0.004, 0.062, 0.002, 0.003, 0.001, 0.11, 0.003, 0.049, 0.044, 0.259, 0.029, 0.076, 0.026, 0.004, 0.004, 0.007, 0.009, 0.003, 0.038, 0.01, 0.012, 0.003, 0.005, 0.006, 0.0001, 0.0001, 0.133, 0.88, 0.003, 0.004, 0.0001, 0.001, 0.0001, 0.002, 0.001, 0.003, 0.002, 0.0001, 0.006, 0.002, 0.031, 0.01, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.004, 0.004, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.01, 0.003, 0.207, 0.001, 0.004, 0.008, 0.005, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"az\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.803, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.785, 0.003, 0.222, 0.0001, 0.001, 0.009, 0.001, 0.007, 0.139, 0.141, 0.001, 0.002, 0.64, 0.404, 0.91, 0.014, 0.244, 0.339, 0.188, 0.096, 0.09, 0.102, 0.087, 0.087, 0.102, 0.202, 0.038, 0.019, 0.004, 0.002, 0.004, 0.004, 0.0001, 0.276, 0.242, 0.068, 0.094, 0.057, 0.061, 0.057, 0.095, 0.062, 0.008, 0.127, 0.055, 0.202, 0.081, 0.086, 0.077, 0.107, 0.098, 0.172, 0.115, 0.037, 0.055, 0.005, 0.062, 0.066, 0.023, 0.006, 0.0001, 0.006, 0.0001, 0.004, 0.001, 7.007, 1.378, 0.673, 3.497, 1.722, 0.535, 0.389, 0.748, 6.853, 0.041, 1.544, 4.525, 2.336, 5.203, 1.602, 0.396, 1.07, 4.974, 2.444, 2.338, 1.812, 1.06, 0.008, 0.478, 1.947, 0.87, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.147, 0.01, 0.009, 0.005, 0.005, 0.009, 0.003, 0.033, 0.002, 0.001, 0.001, 0.003, 0.002, 0.001, 0.002, 0.082, 0.004, 0.001, 0.002, 0.028, 0.04, 0.001, 0.012, 0.001, 0.002, 6.259, 0.001, 0.001, 0.046, 0.034, 0.075, 1.454, 0.026, 0.003, 0.003, 0.001, 0.001, 0.001, 0.001, 0.485, 0.001, 0.001, 0.001, 0.011, 0.002, 0.016, 0.001, 0.001, 0.187, 2.533, 0.009, 0.004, 0.005, 0.028, 0.457, 0.003, 0.014, 0.003, 0.01, 0.017, 1.158, 0.011, 0.03, 0.004, 0.0001, 0.0001, 0.067, 2.145, 2.985, 1.196, 0.079, 0.0001, 0.0001, 6.24, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.207, 0.052, 0.0001, 0.018, 0.0001, 0.0001, 0.0001, 0.001, 0.008, 0.009, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.14, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"azb\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.225, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.112, 0.002, 0.032, 0.0001, 0.0001, 0.003, 0.0001, 0.002, 0.275, 0.275, 0.002, 0.001, 0.028, 0.165, 0.744, 0.053, 0.037, 0.078, 0.041, 0.038, 0.027, 0.033, 0.024, 0.023, 0.03, 0.03, 0.059, 0.003, 0.004, 0.001, 0.003, 0.0001, 0.0001, 0.005, 0.004, 0.007, 0.004, 0.002, 0.002, 0.002, 0.003, 0.008, 0.002, 0.002, 0.004, 0.004, 0.003, 0.001, 0.007, 0.001, 0.004, 0.011, 0.002, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.005, 0.0001, 0.005, 0.0001, 0.022, 0.0001, 0.096, 0.009, 0.017, 0.038, 0.09, 0.012, 0.02, 0.043, 0.1, 0.0001, 0.026, 0.053, 0.017, 0.052, 0.064, 0.04, 0.001, 0.055, 0.055, 0.106, 0.015, 0.003, 0.052, 0.004, 0.018, 0.009, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.77, 0.455, 0.528, 0.028, 2.648, 1.417, 3.922, 1.536, 3.205, 0.004, 0.23, 0.004, 7.975, 0.001, 0.011, 0.01, 0.002, 0.06, 0.27, 0.013, 0.004, 0.001, 0.0001, 0.0001, 0.033, 0.002, 0.0001, 0.023, 0.001, 0.001, 0.0001, 0.002, 0.02, 0.007, 0.378, 0.004, 0.281, 0.002, 0.413, 5.027, 1.244, 0.85, 1.199, 0.132, 0.444, 0.158, 0.386, 2.668, 0.253, 3.47, 0.613, 1.73, 0.767, 0.17, 0.092, 0.269, 0.09, 0.326, 0.153, 0.08, 0.001, 0.001, 0.271, 0.002, 0.0001, 0.0001, 0.181, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 18.661, 14.13, 1.511, 8.604, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.0001, 0.763, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ba\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.692, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.933, 0.002, 0.044, 0.0001, 0.0001, 0.005, 0.0001, 0.001, 0.147, 0.147, 0.0001, 0.004, 0.482, 0.143, 0.604, 0.015, 0.158, 0.244, 0.135, 0.077, 0.08, 0.076, 0.061, 0.06, 0.081, 0.125, 0.052, 0.011, 0.008, 0.003, 0.008, 0.001, 0.0001, 0.003, 0.003, 0.006, 0.002, 0.002, 0.001, 0.002, 0.002, 0.025, 0.001, 0.002, 0.002, 0.003, 0.002, 0.001, 0.002, 0.0001, 0.001, 0.004, 0.005, 0.004, 0.007, 0.001, 0.012, 0.0001, 0.001, 0.006, 0.0001, 0.006, 0.0001, 0.002, 0.0001, 0.021, 0.003, 0.012, 0.011, 0.026, 0.004, 0.004, 0.006, 0.021, 0.001, 0.003, 0.02, 0.007, 0.023, 0.02, 0.005, 0.0001, 0.016, 0.01, 0.014, 0.014, 0.002, 0.003, 0.001, 0.009, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.739, 1.424, 2.096, 1.348, 0.183, 0.244, 0.115, 0.088, 0.621, 0.006, 0.016, 3.259, 0.202, 0.093, 0.068, 0.404, 0.112, 0.175, 0.076, 1.0, 0.273, 0.018, 0.005, 0.012, 0.081, 3.093, 0.13, 0.026, 0.084, 0.041, 0.082, 0.063, 0.299, 0.879, 0.098, 0.434, 0.038, 0.036, 0.005, 0.017, 0.043, 0.504, 0.0001, 0.196, 0.001, 0.016, 0.036, 0.445, 4.844, 0.952, 0.303, 0.533, 0.952, 2.488, 0.102, 0.15, 1.49, 1.18, 1.231, 3.558, 1.237, 2.847, 1.277, 0.365, 0.0001, 0.0001, 0.244, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.004, 0.0001, 0.002, 0.001, 24.156, 12.667, 4.154, 3.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.235, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bar\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.604, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.871, 0.004, 0.418, 0.0001, 0.0001, 0.008, 0.002, 0.216, 0.21, 0.21, 0.009, 0.001, 0.803, 0.202, 1.146, 0.023, 0.266, 0.394, 0.199, 0.121, 0.109, 0.119, 0.109, 0.117, 0.138, 0.187, 0.117, 0.02, 0.004, 0.005, 0.004, 0.003, 0.0001, 0.352, 0.447, 0.201, 0.532, 0.247, 0.245, 0.332, 0.228, 0.204, 0.156, 0.293, 0.235, 0.338, 0.204, 0.224, 0.214, 0.034, 0.205, 0.697, 0.181, 0.119, 0.18, 0.276, 0.005, 0.01, 0.114, 0.021, 0.0001, 0.021, 0.0001, 0.003, 0.003, 8.177, 1.169, 1.993, 4.065, 6.625, 1.095, 2.102, 3.003, 6.12, 0.162, 0.941, 2.0, 2.327, 6.606, 4.578, 0.55, 0.014, 3.249, 4.677, 4.042, 3.018, 0.854, 1.171, 0.071, 0.239, 0.864, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.102, 0.003, 0.003, 0.002, 0.004, 0.004, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.014, 0.001, 0.001, 0.016, 0.001, 0.002, 0.009, 0.001, 0.001, 0.039, 0.001, 0.036, 0.116, 0.061, 0.007, 0.003, 0.001, 0.274, 0.073, 0.002, 0.002, 0.004, 0.027, 0.002, 0.002, 0.002, 0.004, 0.001, 0.001, 0.004, 0.002, 0.01, 0.016, 0.006, 0.001, 0.154, 0.002, 0.005, 0.001, 0.002, 0.002, 0.176, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.07, 0.891, 0.007, 0.006, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.007, 0.004, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.103, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bcl\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.379, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.071, 0.002, 0.217, 0.001, 0.003, 0.005, 0.002, 0.116, 0.161, 0.16, 0.0001, 0.001, 0.914, 0.25, 0.911, 0.022, 0.337, 0.439, 0.274, 0.132, 0.116, 0.128, 0.121, 0.133, 0.144, 0.229, 0.055, 0.02, 0.017, 0.001, 0.017, 0.022, 0.0001, 0.585, 0.233, 0.246, 0.128, 0.11, 0.148, 0.111, 0.118, 0.238, 0.077, 0.175, 0.149, 0.27, 0.198, 0.07, 0.296, 0.013, 0.12, 0.508, 0.14, 0.057, 0.048, 0.04, 0.004, 0.02, 0.015, 0.025, 0.0001, 0.025, 0.0001, 0.0001, 0.0001, 15.454, 1.486, 0.494, 1.897, 2.968, 0.126, 4.169, 0.861, 6.432, 0.033, 2.688, 2.392, 2.068, 10.392, 5.039, 1.872, 0.022, 3.21, 4.66, 2.796, 1.875, 0.174, 0.643, 0.021, 1.752, 0.121, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.039, 0.006, 0.003, 0.003, 0.005, 0.004, 0.002, 0.003, 0.009, 0.002, 0.004, 0.002, 0.003, 0.004, 0.003, 0.002, 0.007, 0.003, 0.002, 0.009, 0.004, 0.002, 0.001, 0.002, 0.002, 0.008, 0.004, 0.003, 0.013, 0.011, 0.003, 0.001, 0.027, 0.035, 0.013, 0.004, 0.005, 0.003, 0.003, 0.006, 0.004, 0.006, 0.004, 0.003, 0.007, 0.019, 0.005, 0.003, 0.005, 0.018, 0.01, 0.022, 0.014, 0.003, 0.004, 0.003, 0.01, 0.004, 0.006, 0.004, 0.005, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.019, 0.136, 0.005, 0.006, 0.0001, 0.0001, 0.0001, 0.011, 0.004, 0.01, 0.002, 0.0001, 0.006, 0.003, 0.016, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.017, 0.012, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.007, 0.034, 0.001, 0.008, 0.01, 0.006, 0.004, 0.002, 0.003, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"be\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.607, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.35, 0.001, 0.055, 0.0001, 0.0001, 0.006, 0.0001, 0.05, 0.155, 0.156, 0.001, 0.002, 0.628, 0.121, 0.612, 0.009, 0.188, 0.295, 0.148, 0.088, 0.085, 0.087, 0.076, 0.074, 0.089, 0.156, 0.032, 0.017, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.009, 0.006, 0.026, 0.004, 0.005, 0.003, 0.019, 0.003, 0.047, 0.001, 0.002, 0.004, 0.009, 0.01, 0.004, 0.01, 0.0001, 0.005, 0.013, 0.005, 0.003, 0.013, 0.004, 0.018, 0.001, 0.002, 0.002, 0.0001, 0.002, 0.0001, 0.003, 0.0001, 0.046, 0.006, 0.014, 0.013, 0.042, 0.007, 0.007, 0.01, 0.04, 0.001, 0.006, 0.023, 0.014, 0.029, 0.035, 0.009, 0.001, 0.032, 0.024, 0.024, 0.019, 0.004, 0.003, 0.002, 0.006, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.314, 1.922, 1.481, 1.13, 0.14, 0.481, 1.007, 0.569, 0.351, 0.001, 0.001, 1.93, 0.479, 0.541, 0.221, 1.357, 0.128, 0.261, 0.085, 0.08, 0.203, 0.012, 2.438, 0.059, 0.001, 0.01, 0.103, 0.048, 0.097, 0.076, 0.995, 0.141, 0.181, 0.137, 0.046, 0.12, 0.029, 0.02, 0.016, 0.019, 0.023, 0.001, 0.0001, 0.081, 0.0001, 0.017, 0.007, 0.023, 7.12, 0.583, 1.325, 0.884, 1.382, 1.613, 0.241, 1.022, 0.011, 0.528, 1.726, 1.757, 1.251, 2.924, 1.397, 1.062, 0.0001, 0.0001, 0.283, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.021, 0.0001, 0.002, 0.001, 26.294, 17.28, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.156, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bh\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.941, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.272, 0.0001, 0.067, 0.0001, 0.001, 0.014, 0.0001, 0.006, 0.074, 0.074, 0.0001, 0.001, 0.205, 0.047, 0.036, 0.005, 0.139, 0.215, 0.134, 0.072, 0.07, 0.074, 0.065, 0.069, 0.075, 0.087, 0.017, 0.007, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.006, 0.004, 0.005, 0.002, 0.003, 0.002, 0.004, 0.002, 0.007, 0.001, 0.002, 0.003, 0.003, 0.003, 0.002, 0.004, 0.0001, 0.002, 0.006, 0.006, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.009, 0.0001, 0.1, 0.014, 0.029, 0.038, 0.115, 0.019, 0.024, 0.049, 0.081, 0.001, 0.007, 0.043, 0.023, 0.079, 0.071, 0.019, 0.001, 0.072, 0.065, 0.081, 0.029, 0.011, 0.014, 0.002, 0.014, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.902, 0.534, 1.035, 0.031, 0.0001, 0.22, 0.29, 2.243, 0.258, 0.137, 0.021, 0.553, 0.066, 1.318, 0.0001, 0.336, 0.009, 0.009, 0.0001, 0.03, 0.023, 1.891, 0.248, 0.639, 0.037, 0.011, 0.202, 0.05, 0.683, 0.024, 0.014, 0.375, 0.074, 0.252, 0.031, 0.13, 24.792, 6.19, 0.487, 0.175, 1.097, 0.001, 0.677, 0.098, 0.808, 0.311, 0.975, 0.521, 2.028, 0.0001, 1.424, 0.0001, 0.0001, 0.605, 0.237, 0.107, 1.177, 0.742, 0.0001, 0.0001, 0.117, 0.003, 3.031, 1.138, 0.0001, 0.0001, 0.016, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 29.692, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bi\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.859, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.276, 0.003, 0.256, 0.0001, 0.0001, 0.003, 0.003, 0.016, 0.486, 0.484, 0.001, 0.0001, 0.638, 0.156, 1.372, 0.022, 0.455, 0.969, 0.456, 0.237, 0.231, 0.247, 0.248, 0.25, 0.297, 0.612, 0.044, 0.019, 0.005, 0.0001, 0.004, 0.004, 0.0001, 0.449, 0.264, 0.227, 0.165, 0.234, 0.192, 0.164, 0.234, 0.179, 0.456, 0.316, 0.231, 0.458, 0.197, 0.135, 0.315, 0.005, 0.168, 0.606, 0.235, 0.049, 0.123, 0.109, 0.008, 0.231, 0.017, 0.005, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 8.019, 2.445, 0.575, 1.178, 6.318, 0.449, 2.782, 1.275, 5.992, 0.203, 1.688, 4.658, 3.419, 6.494, 6.015, 1.447, 0.023, 2.565, 2.973, 3.583, 1.992, 0.459, 0.92, 0.044, 0.557, 0.136, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.108, 0.019, 0.014, 0.005, 0.005, 0.004, 0.006, 0.01, 0.005, 0.008, 0.002, 0.002, 0.012, 0.031, 0.002, 0.001, 0.002, 0.004, 0.003, 0.089, 0.007, 0.003, 0.003, 0.004, 0.004, 0.002, 0.001, 0.001, 0.007, 0.004, 0.002, 0.004, 0.052, 0.019, 0.003, 0.005, 0.023, 0.009, 0.014, 0.014, 0.008, 0.023, 0.003, 0.01, 0.005, 0.015, 0.003, 0.004, 0.019, 0.013, 0.011, 0.022, 0.006, 0.01, 0.007, 0.004, 0.018, 0.01, 0.009, 0.009, 0.011, 0.009, 0.011, 0.009, 0.0001, 0.0001, 0.048, 0.113, 0.02, 0.046, 0.0001, 0.002, 0.0001, 0.005, 0.001, 0.002, 0.0001, 0.001, 0.032, 0.011, 0.078, 0.027, 0.001, 0.0001, 0.001, 0.018, 0.002, 0.0001, 0.017, 0.009, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.037, 0.005, 0.097, 0.0001, 0.0001, 0.007, 0.003, 0.001, 0.003, 0.001, 0.002, 0.001, 0.006, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bjn\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.274, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.352, 0.002, 0.406, 0.0001, 0.001, 0.013, 0.001, 0.109, 0.199, 0.198, 0.002, 0.001, 0.988, 0.406, 0.819, 0.036, 0.185, 0.196, 0.136, 0.076, 0.062, 0.071, 0.054, 0.058, 0.057, 0.091, 0.102, 0.025, 0.002, 0.003, 0.002, 0.005, 0.0001, 0.244, 0.391, 0.098, 0.173, 0.034, 0.031, 0.106, 0.136, 0.207, 0.121, 0.411, 0.116, 0.312, 0.12, 0.035, 0.341, 0.003, 0.133, 0.409, 0.258, 0.061, 0.026, 0.09, 0.002, 0.038, 0.007, 0.012, 0.0001, 0.012, 0.0001, 0.0001, 0.0001, 19.717, 2.113, 0.418, 2.814, 2.089, 0.126, 3.097, 2.135, 6.446, 0.654, 2.733, 2.879, 2.871, 8.542, 1.048, 1.844, 0.007, 3.384, 2.985, 3.613, 4.514, 0.083, 0.972, 0.009, 1.107, 0.035, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.03, 0.008, 0.005, 0.007, 0.006, 0.006, 0.006, 0.004, 0.006, 0.004, 0.008, 0.003, 0.003, 0.007, 0.001, 0.001, 0.002, 0.002, 0.002, 0.008, 0.003, 0.002, 0.002, 0.004, 0.002, 0.014, 0.001, 0.002, 0.005, 0.005, 0.002, 0.002, 0.012, 0.002, 0.002, 0.004, 0.012, 0.005, 0.004, 0.011, 0.007, 0.182, 0.006, 0.005, 0.004, 0.004, 0.003, 0.005, 0.009, 0.008, 0.005, 0.005, 0.003, 0.002, 0.001, 0.003, 0.006, 0.004, 0.004, 0.003, 0.003, 0.003, 0.002, 0.002, 0.0001, 0.0001, 0.019, 0.193, 0.007, 0.009, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.005, 0.002, 0.005, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.035, 0.03, 0.004, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.019, 0.008, 0.026, 0.006, 0.003, 0.008, 0.005, 0.003, 0.002, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bm\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.129, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.167, 0.007, 0.144, 0.0001, 0.001, 0.013, 0.002, 0.256, 0.237, 0.237, 0.007, 0.003, 0.973, 0.158, 0.97, 0.007, 0.243, 0.224, 0.128, 0.052, 0.064, 0.06, 0.072, 0.055, 0.07, 0.12, 0.287, 0.015, 0.0001, 0.01, 0.0001, 0.005, 0.0001, 0.444, 0.348, 0.111, 0.212, 0.105, 0.277, 0.105, 0.044, 0.094, 0.171, 0.429, 0.132, 0.368, 0.21, 0.091, 0.065, 0.003, 0.072, 0.446, 0.184, 0.079, 0.027, 0.078, 0.004, 0.046, 0.018, 0.018, 0.0001, 0.014, 0.0001, 0.017, 0.0001, 12.037, 2.27, 0.406, 1.816, 3.589, 1.305, 1.615, 0.299, 5.301, 0.672, 3.384, 3.18, 2.268, 7.22, 3.282, 0.194, 0.029, 2.428, 2.045, 1.645, 2.796, 0.059, 0.96, 0.016, 1.69, 0.107, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.237, 0.003, 0.001, 0.017, 0.017, 0.007, 0.015, 0.003, 0.008, 0.011, 0.026, 0.017, 0.001, 0.0001, 0.018, 0.005, 0.013, 0.002, 0.004, 0.018, 1.999, 0.0001, 0.0001, 0.0001, 0.002, 0.172, 0.0001, 1.879, 0.012, 0.017, 0.004, 0.0001, 0.054, 0.002, 0.001, 0.001, 0.002, 0.003, 0.005, 0.027, 0.322, 0.21, 0.005, 0.017, 0.007, 0.002, 0.001, 0.011, 0.002, 0.012, 0.238, 0.014, 0.415, 0.435, 0.001, 0.007, 0.005, 0.009, 0.01, 0.017, 0.003, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.064, 1.039, 0.002, 0.033, 0.027, 0.0001, 0.0001, 4.089, 0.016, 0.002, 0.003, 0.0001, 0.433, 0.0001, 0.024, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.065, 0.05, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.015, 0.0001, 0.003, 0.233, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.319, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.406, 0.001, 0.076, 0.0001, 0.0001, 0.012, 0.0001, 0.015, 0.057, 0.058, 0.0001, 0.001, 0.196, 0.086, 0.029, 0.005, 0.005, 0.006, 0.004, 0.002, 0.002, 0.002, 0.002, 0.001, 0.002, 0.002, 0.016, 0.009, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.005, 0.003, 0.004, 0.002, 0.002, 0.002, 0.002, 0.002, 0.003, 0.002, 0.001, 0.002, 0.003, 0.002, 0.002, 0.003, 0.0001, 0.002, 0.004, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.004, 0.001, 0.001, 0.0001, 0.043, 0.007, 0.016, 0.016, 0.05, 0.009, 0.009, 0.017, 0.038, 0.001, 0.004, 0.022, 0.013, 0.034, 0.034, 0.01, 0.001, 0.031, 0.027, 0.033, 0.016, 0.005, 0.005, 0.002, 0.008, 0.001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.359, 0.551, 0.299, 0.082, 0.002, 0.229, 0.186, 2.436, 0.034, 0.152, 0.002, 0.333, 0.036, 2.245, 0.026, 0.384, 0.008, 0.001, 0.001, 0.181, 0.002, 1.31, 0.16, 0.34, 0.043, 0.053, 0.26, 0.209, 0.4, 0.015, 0.042, 0.46, 0.067, 0.212, 0.008, 0.16, 1.542, 0.621, 24.834, 6.808, 1.602, 0.04, 0.792, 0.149, 1.148, 0.261, 0.867, 1.261, 2.631, 0.001, 0.874, 0.001, 0.001, 0.001, 0.381, 0.232, 0.963, 0.451, 0.001, 0.001, 0.701, 0.0001, 2.837, 1.811, 0.0001, 0.0001, 0.013, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.017, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.991, 0.0001, 0.03, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.169, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.69, 0.0001, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.01, 0.01, 0.0001, 0.0001, 0.002, 0.003, 0.005, 0.001, 0.003, 0.004, 0.003, 0.002, 0.001, 0.002, 0.001, 0.001, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.012, 0.002, 0.004, 0.004, 0.015, 0.003, 0.003, 0.006, 0.011, 0.0001, 0.001, 0.005, 0.003, 0.01, 0.01, 0.003, 0.0001, 0.008, 0.008, 0.01, 0.004, 0.001, 0.002, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.3, 0.21, 1.61, 0.004, 1.096, 0.171, 0.232, 0.056, 0.006, 0.125, 0.009, 7.85, 0.044, 0.821, 0.01, 0.147, 0.305, 1.571, 0.233, 1.086, 0.826, 0.17, 1.379, 0.052, 0.974, 0.101, 0.175, 0.065, 0.005, 0.008, 0.253, 0.318, 0.893, 0.39, 1.207, 0.915, 0.217, 0.014, 2.41, 0.028, 0.071, 0.06, 0.002, 0.023, 0.001, 0.018, 0.001, 0.001, 0.003, 0.913, 2.028, 0.112, 1.086, 0.005, 0.001, 0.055, 0.005, 0.003, 0.951, 0.005, 10.217, 21.49, 2.602, 0.016, 0.0001, 0.0001, 0.014, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 32.905, 0.0001, 0.024, 0.009, 0.002, 0.006, 0.004, 0.005, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bpy\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.902, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.282, 0.0001, 0.009, 0.0001, 0.0001, 0.224, 0.0001, 0.002, 0.281, 0.281, 0.0001, 0.0001, 0.306, 0.253, 0.183, 0.08, 0.005, 0.009, 0.002, 0.004, 0.002, 0.003, 0.003, 0.003, 0.003, 0.003, 0.197, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.016, 0.008, 0.017, 0.005, 0.005, 0.002, 0.004, 0.002, 0.003, 0.003, 0.005, 0.003, 0.007, 0.007, 0.001, 0.007, 0.0001, 0.004, 0.019, 0.004, 0.016, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.014, 0.0001, 0.118, 0.01, 0.016, 0.026, 0.05, 0.006, 0.015, 0.031, 0.057, 0.004, 0.009, 0.031, 0.017, 0.064, 0.06, 0.015, 0.001, 0.059, 0.03, 0.047, 0.04, 0.005, 0.005, 0.001, 0.018, 0.002, 0.0001, 0.016, 0.0001, 0.0001, 0.0001, 0.094, 0.582, 0.295, 0.004, 0.001, 0.199, 0.278, 1.651, 0.006, 0.325, 0.001, 0.49, 0.119, 1.057, 0.003, 0.285, 0.0001, 0.0001, 0.0001, 0.034, 0.032, 0.592, 0.143, 0.798, 0.084, 0.129, 0.075, 0.036, 0.484, 0.004, 0.03, 0.329, 0.051, 0.128, 0.007, 0.019, 1.405, 0.659, 24.309, 6.387, 2.166, 0.231, 0.814, 0.355, 0.961, 0.379, 1.131, 0.99, 2.941, 0.034, 0.919, 0.004, 0.001, 0.001, 0.243, 0.193, 0.791, 1.05, 0.0001, 0.0001, 0.626, 0.0001, 4.392, 1.335, 0.0001, 0.0001, 0.04, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.31, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"br\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.678, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.255, 0.004, 0.515, 0.0001, 0.0001, 0.007, 0.002, 0.663, 0.246, 0.246, 0.001, 0.002, 0.881, 0.746, 0.901, 0.014, 0.258, 0.444, 0.187, 0.109, 0.115, 0.122, 0.109, 0.12, 0.152, 0.228, 0.115, 0.024, 0.015, 0.004, 0.016, 0.003, 0.0001, 0.347, 0.279, 0.201, 0.205, 0.261, 0.098, 0.212, 0.134, 0.164, 0.075, 0.201, 0.168, 0.253, 0.109, 0.059, 0.199, 0.006, 0.146, 0.289, 0.136, 0.097, 0.091, 0.051, 0.019, 0.032, 0.015, 0.024, 0.0001, 0.024, 0.0001, 0.001, 0.0001, 9.146, 1.127, 0.833, 2.777, 10.42, 0.294, 1.799, 2.456, 3.655, 0.167, 1.352, 2.97, 1.505, 5.492, 4.696, 0.867, 0.019, 5.665, 2.33, 3.448, 2.744, 1.784, 0.434, 0.03, 0.247, 2.302, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 0.1, 0.012, 0.008, 0.007, 0.005, 0.004, 0.003, 0.003, 0.004, 0.005, 0.002, 0.002, 0.004, 0.005, 0.003, 0.002, 0.003, 0.002, 0.002, 0.011, 0.005, 0.002, 0.002, 0.002, 0.002, 0.074, 0.002, 0.003, 0.005, 0.005, 0.001, 0.004, 0.021, 0.015, 0.009, 0.005, 0.007, 0.003, 0.004, 0.009, 0.013, 0.045, 0.076, 0.018, 0.003, 0.013, 0.003, 0.005, 0.011, 0.591, 0.009, 0.012, 0.018, 0.007, 0.006, 0.004, 0.009, 0.467, 0.008, 0.021, 0.017, 0.008, 0.005, 0.006, 0.0001, 0.0001, 0.048, 1.28, 0.01, 0.011, 0.0001, 0.001, 0.0001, 0.004, 0.002, 0.002, 0.002, 0.0001, 0.032, 0.015, 0.039, 0.015, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.006, 0.009, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.009, 0.096, 0.003, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bs\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.108, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.139, 0.002, 0.313, 0.001, 0.001, 0.017, 0.002, 0.011, 0.204, 0.204, 0.001, 0.006, 0.915, 0.157, 1.176, 0.034, 0.332, 0.467, 0.264, 0.159, 0.151, 0.151, 0.132, 0.126, 0.142, 0.226, 0.068, 0.015, 0.006, 0.007, 0.006, 0.001, 0.0001, 0.156, 0.174, 0.174, 0.143, 0.072, 0.074, 0.155, 0.136, 0.152, 0.073, 0.147, 0.082, 0.163, 0.218, 0.118, 0.225, 0.003, 0.11, 0.283, 0.122, 0.105, 0.088, 0.031, 0.007, 0.007, 0.073, 0.025, 0.0001, 0.025, 0.0001, 0.008, 0.0001, 8.723, 0.95, 0.762, 2.331, 6.777, 0.26, 1.369, 0.582, 7.412, 3.867, 2.673, 2.682, 2.205, 4.994, 6.632, 1.941, 0.005, 3.955, 3.612, 3.234, 3.103, 2.415, 0.036, 0.017, 0.061, 1.207, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.038, 0.004, 0.003, 0.002, 0.002, 0.001, 0.003, 0.388, 0.002, 0.001, 0.001, 0.001, 0.016, 0.618, 0.001, 0.0001, 0.003, 0.172, 0.002, 0.018, 0.001, 0.0001, 0.001, 0.001, 0.002, 0.003, 0.001, 0.001, 0.006, 0.003, 0.004, 0.002, 0.035, 0.482, 0.001, 0.001, 0.003, 0.001, 0.001, 0.002, 0.001, 0.008, 0.001, 0.002, 0.001, 0.003, 0.001, 0.001, 0.007, 0.004, 0.003, 0.004, 0.002, 0.003, 0.004, 0.002, 0.004, 0.002, 0.002, 0.003, 0.006, 0.012, 0.366, 0.002, 0.0001, 0.0001, 0.02, 0.032, 1.199, 0.874, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.014, 0.006, 0.021, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.037, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bug\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.068, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.164, 0.0001, 0.016, 0.0001, 0.0001, 0.003, 0.001, 0.137, 0.016, 0.016, 0.0001, 0.001, 0.196, 1.935, 1.044, 0.004, 0.035, 0.02, 0.023, 0.01, 0.009, 0.007, 0.007, 0.006, 0.007, 0.013, 0.007, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.516, 0.311, 0.434, 0.185, 0.139, 0.134, 0.304, 0.324, 0.039, 0.055, 0.029, 0.369, 0.412, 0.063, 0.111, 1.316, 0.017, 0.157, 0.558, 0.13, 0.016, 0.233, 0.012, 0.002, 0.073, 0.002, 0.007, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 9.887, 0.241, 1.633, 1.832, 7.179, 0.088, 0.757, 0.513, 7.161, 0.111, 1.126, 1.683, 2.724, 6.291, 2.861, 1.308, 0.04, 7.537, 3.873, 3.7, 4.723, 0.375, 1.036, 0.149, 1.531, 0.172, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.047, 0.009, 0.005, 0.004, 0.009, 0.007, 0.006, 0.004, 0.009, 0.039, 0.01, 0.038, 0.003, 0.005, 0.002, 0.001, 0.004, 0.012, 0.007, 0.011, 0.011, 0.02, 0.001, 0.02, 0.012, 0.019, 0.011, 0.012, 0.002, 0.001, 0.006, 0.003, 0.004, 0.003, 0.047, 0.002, 0.016, 0.005, 0.004, 0.01, 0.405, 2.36, 0.01, 0.013, 0.003, 0.001, 0.008, 0.004, 0.008, 0.004, 0.008, 0.005, 0.176, 0.005, 0.002, 0.003, 0.012, 0.005, 0.008, 0.007, 0.003, 0.003, 0.004, 0.003, 0.0001, 0.0001, 0.007, 2.887, 0.002, 0.04, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.003, 0.023, 0.014, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.008, 0.007, 0.001, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.048, 0.15, 0.04, 0.0001, 0.0001, 0.002, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"bxr\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.49, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.129, 0.001, 0.08, 0.0001, 0.0001, 0.012, 0.0001, 0.001, 0.147, 0.147, 0.0001, 0.002, 0.553, 0.131, 0.523, 0.004, 0.151, 0.243, 0.109, 0.074, 0.068, 0.074, 0.065, 0.062, 0.079, 0.12, 0.022, 0.018, 0.003, 0.001, 0.002, 0.001, 0.0001, 0.004, 0.002, 0.007, 0.001, 0.002, 0.002, 0.002, 0.004, 0.037, 0.001, 0.001, 0.002, 0.003, 0.003, 0.003, 0.003, 0.0001, 0.002, 0.004, 0.003, 0.001, 0.011, 0.001, 0.019, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.037, 0.005, 0.011, 0.009, 0.029, 0.005, 0.007, 0.031, 0.027, 0.001, 0.005, 0.019, 0.012, 0.022, 0.025, 0.008, 0.001, 0.023, 0.018, 0.017, 0.016, 0.003, 0.002, 0.001, 0.005, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.392, 0.859, 1.489, 1.628, 0.046, 1.574, 0.057, 0.037, 0.549, 0.002, 0.003, 0.546, 0.265, 4.264, 0.148, 0.174, 0.118, 0.207, 0.029, 0.069, 0.123, 0.028, 0.013, 0.033, 0.034, 0.005, 0.055, 0.03, 0.09, 0.073, 0.049, 0.037, 0.094, 0.079, 0.088, 0.076, 0.026, 0.12, 0.011, 0.016, 0.032, 0.306, 0.001, 0.058, 0.001, 0.071, 0.033, 1.461, 5.842, 1.346, 0.152, 2.003, 2.072, 0.704, 0.52, 0.475, 1.576, 1.562, 0.254, 3.078, 0.893, 3.534, 3.045, 0.105, 0.0001, 0.0001, 0.188, 0.005, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.006, 0.002, 27.741, 14.028, 2.178, 0.307, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.003, 0.075, 0.002, 0.001, 0.004, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"cdo\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.899, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.597, 0.001, 0.273, 0.0001, 0.0001, 0.004, 0.0001, 0.004, 0.549, 0.551, 0.0001, 0.001, 0.624, 3.929, 0.732, 0.03, 0.251, 0.611, 0.29, 0.189, 0.163, 0.163, 0.16, 0.156, 0.166, 0.215, 0.133, 0.012, 0.001, 0.0001, 0.001, 0.002, 0.0001, 0.053, 0.117, 0.299, 0.251, 0.017, 0.027, 0.504, 0.23, 0.082, 0.03, 0.071, 0.135, 0.356, 0.159, 0.039, 0.068, 0.004, 0.027, 0.229, 0.101, 0.044, 0.025, 0.062, 0.001, 0.013, 0.003, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.822, 0.392, 1.504, 1.05, 0.748, 0.033, 6.691, 1.959, 3.832, 0.006, 1.877, 0.724, 0.396, 5.597, 0.623, 0.123, 0.005, 0.411, 2.143, 0.557, 2.118, 0.037, 0.065, 0.039, 0.184, 0.014, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.562, 0.653, 0.229, 0.604, 0.418, 0.298, 0.318, 0.129, 0.175, 0.171, 0.118, 0.212, 0.31, 0.409, 0.113, 0.98, 0.125, 0.066, 0.036, 0.255, 0.106, 0.397, 0.142, 0.124, 0.138, 0.172, 0.096, 0.139, 0.338, 0.116, 0.144, 0.186, 0.41, 1.078, 0.77, 0.114, 1.515, 0.081, 0.097, 0.077, 0.628, 0.714, 1.044, 0.603, 1.183, 1.024, 0.119, 0.129, 0.135, 0.183, 0.537, 1.615, 1.19, 0.067, 0.211, 0.1, 0.216, 1.217, 0.179, 0.199, 0.306, 0.119, 0.135, 0.091, 0.0001, 0.0001, 0.041, 7.531, 2.472, 1.618, 0.001, 0.001, 0.0001, 0.002, 0.002, 0.001, 2.018, 0.0001, 0.014, 0.006, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.757, 0.108, 0.212, 0.359, 1.361, 0.793, 0.503, 0.549, 0.397, 0.002, 0.003, 0.004, 0.001, 0.0001, 0.218, 0.03, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ce\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.477, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.593, 0.0001, 0.003, 0.0001, 0.0001, 0.014, 0.0001, 0.0001, 0.462, 0.462, 0.0001, 0.166, 0.461, 0.186, 0.813, 0.002, 0.175, 0.094, 0.109, 0.14, 0.045, 0.029, 0.022, 0.02, 0.031, 0.028, 0.033, 0.001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.145, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.004, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.145, 0.144, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.704, 1.438, 1.762, 1.875, 0.015, 2.329, 0.449, 0.169, 0.835, 0.009, 0.342, 0.05, 1.751, 0.164, 0.611, 0.21, 0.068, 0.113, 0.056, 0.04, 0.434, 0.02, 0.006, 0.019, 0.028, 0.002, 0.404, 0.034, 0.196, 0.056, 0.049, 0.075, 0.184, 0.229, 0.057, 0.026, 0.146, 0.02, 0.017, 0.02, 0.129, 0.002, 0.0001, 0.004, 0.0001, 0.008, 0.009, 0.018, 7.603, 0.877, 1.017, 0.93, 0.629, 1.84, 0.05, 0.386, 1.788, 1.009, 1.778, 2.253, 0.873, 3.199, 2.291, 0.075, 0.0001, 0.0001, 0.018, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 28.632, 13.675, 0.0001, 0.638, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.405, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ceb\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.228, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.341, 0.0001, 0.15, 0.0001, 0.0001, 0.002, 0.0001, 0.016, 0.068, 0.068, 0.0001, 0.0001, 1.15, 0.441, 1.259, 0.001, 0.028, 0.059, 0.035, 0.022, 0.021, 0.022, 0.021, 0.021, 0.026, 0.036, 0.037, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.098, 0.168, 0.578, 0.161, 0.203, 0.063, 0.093, 0.198, 0.052, 0.044, 0.126, 0.151, 0.236, 0.118, 0.082, 0.261, 0.02, 0.131, 0.295, 0.118, 0.081, 0.041, 0.087, 0.005, 0.015, 0.017, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 15.378, 2.318, 0.367, 1.953, 2.974, 0.093, 5.126, 1.479, 4.851, 0.069, 2.449, 3.4, 2.839, 8.407, 4.701, 1.442, 0.019, 2.43, 4.783, 3.214, 2.941, 0.169, 0.623, 0.03, 1.539, 0.068, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.059, 0.004, 0.005, 0.004, 0.004, 0.003, 0.008, 0.005, 0.003, 0.002, 0.001, 0.004, 0.009, 0.002, 0.004, 0.002, 0.003, 0.0001, 0.003, 0.001, 0.001, 0.001, 0.001, 0.01, 0.005, 0.001, 0.002, 0.001, 0.001, 0.002, 0.006, 0.184, 0.019, 0.007, 0.005, 0.008, 0.019, 0.003, 0.009, 0.007, 0.025, 0.004, 0.049, 0.001, 0.018, 0.002, 0.008, 0.279, 0.015, 0.004, 0.013, 0.004, 0.003, 0.007, 0.001, 0.046, 0.006, 0.007, 0.005, 0.006, 0.004, 0.006, 0.001, 0.0001, 0.0001, 0.452, 0.166, 0.097, 0.047, 0.001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.031, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.019, 0.018, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.017, 0.012, 0.008, 0.002, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ch\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.587, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.467, 0.008, 0.286, 0.0001, 0.0001, 0.018, 0.0001, 1.077, 0.189, 0.189, 0.0001, 0.0001, 1.14, 0.532, 1.257, 0.007, 0.648, 0.639, 0.504, 0.182, 0.3, 0.173, 0.195, 0.169, 0.204, 0.218, 0.042, 0.013, 0.0001, 0.001, 0.0001, 0.005, 0.0001, 0.26, 0.146, 0.257, 0.104, 0.401, 0.111, 0.564, 0.173, 0.223, 0.038, 0.106, 0.097, 0.317, 0.12, 0.025, 0.199, 0.01, 0.074, 0.256, 0.153, 0.279, 0.066, 0.06, 0.002, 0.047, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 10.968, 0.472, 0.524, 1.575, 4.239, 0.44, 3.776, 1.808, 6.943, 0.028, 1.21, 2.019, 1.749, 8.291, 5.798, 1.592, 0.018, 1.795, 5.81, 3.872, 3.565, 0.141, 0.106, 0.012, 0.845, 0.055, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.016, 0.008, 0.003, 0.001, 0.002, 0.01, 0.001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.0001, 0.0001, 0.006, 0.001, 0.0001, 0.005, 0.003, 0.0001, 0.0001, 0.001, 0.0001, 0.011, 0.0001, 0.003, 0.001, 0.0001, 0.002, 0.001, 0.0001, 0.02, 0.002, 0.003, 0.001, 0.974, 0.0001, 0.004, 0.003, 0.012, 0.0001, 0.0001, 0.001, 0.009, 0.0001, 0.0001, 0.002, 0.432, 0.0001, 0.044, 0.0001, 0.001, 0.003, 0.001, 0.002, 0.002, 0.006, 0.007, 0.002, 0.002, 0.001, 0.003, 0.0001, 0.0001, 0.001, 1.51, 0.004, 0.013, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.002, 0.0001, 0.0001, 0.005, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.002, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.012, 0.0001, 0.0001, 0.003, 0.002, 0.001, 0.0001, 0.001, 0.001, 0.002, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"cho\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.477, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.446, 0.089, 1.242, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.621, 0.621, 0.0001, 0.0001, 0.799, 0.0001, 0.532, 0.0001, 0.0001, 0.177, 0.089, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.355, 0.266, 0.0001, 0.0001, 0.0001, 0.089, 0.0001, 0.444, 0.0001, 1.154, 0.0001, 0.0001, 0.0001, 0.089, 0.799, 0.177, 0.0001, 0.177, 0.0001, 0.355, 0.177, 0.177, 0.444, 0.0001, 0.0001, 0.355, 0.0001, 0.0001, 0.089, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.955, 1.154, 0.799, 0.0001, 2.839, 0.177, 0.621, 7.365, 8.252, 0.0001, 5.146, 2.662, 3.549, 3.727, 5.413, 1.597, 0.0001, 0.799, 3.638, 5.146, 1.597, 1.065, 0.089, 0.0001, 1.331, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.154, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.266, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.177, 0.0001, 0.0001, 0.0001, 1.154, 0.0001, 0.089, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"chr\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.394, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.115, 0.002, 0.174, 0.0001, 0.001, 0.005, 0.001, 0.018, 0.095, 0.095, 0.0001, 0.001, 0.499, 0.081, 0.439, 0.009, 0.086, 0.076, 0.045, 0.025, 0.02, 0.027, 0.02, 0.018, 0.025, 0.029, 0.03, 0.019, 0.002, 0.001, 0.003, 0.002, 0.0001, 0.037, 0.02, 0.038, 0.014, 0.023, 0.017, 0.012, 0.014, 0.019, 0.011, 0.01, 0.013, 0.028, 0.014, 0.01, 0.02, 0.002, 0.016, 0.034, 0.027, 0.013, 0.008, 0.015, 0.002, 0.005, 0.003, 0.065, 0.0001, 0.065, 0.0001, 0.004, 0.0001, 0.692, 0.092, 0.264, 0.31, 0.823, 0.092, 0.184, 0.209, 0.663, 0.01, 0.064, 0.374, 0.188, 0.502, 0.498, 0.163, 0.016, 0.479, 0.482, 0.523, 0.235, 0.107, 0.076, 0.023, 0.123, 0.021, 0.0001, 0.028, 0.0001, 0.0001, 0.0001, 0.027, 0.355, 0.722, 0.213, 0.313, 0.628, 0.115, 0.06, 0.021, 0.056, 0.084, 0.04, 0.154, 1.876, 13.554, 13.952, 0.082, 0.032, 0.441, 0.837, 0.268, 0.161, 0.041, 1.986, 0.138, 0.561, 0.191, 0.664, 0.014, 0.045, 0.005, 0.13, 2.057, 0.126, 1.445, 0.138, 1.031, 0.39, 0.904, 0.381, 0.457, 1.048, 0.569, 0.458, 0.748, 0.433, 0.062, 1.427, 0.213, 0.207, 0.29, 0.574, 0.831, 0.687, 0.218, 0.077, 0.387, 0.051, 0.016, 0.01, 0.004, 0.004, 1.405, 0.134, 0.0001, 0.0001, 0.009, 0.006, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.003, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 27.238, 0.017, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"chy\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.992, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.662, 0.002, 0.655, 0.0001, 0.0001, 0.0001, 0.0001, 4.281, 0.488, 0.49, 0.012, 0.039, 1.209, 0.935, 1.193, 0.009, 0.099, 0.186, 0.07, 0.039, 0.048, 0.046, 0.051, 0.032, 0.087, 0.113, 0.294, 0.06, 0.044, 0.012, 0.043, 0.009, 0.0001, 0.28, 0.143, 0.271, 0.068, 0.058, 0.046, 0.056, 0.705, 0.041, 0.084, 0.094, 0.075, 0.71, 0.203, 0.133, 0.21, 0.01, 0.123, 0.333, 0.369, 0.109, 0.326, 0.043, 0.02, 0.015, 0.015, 0.017, 0.0001, 0.017, 0.0001, 0.0001, 0.005, 5.694, 0.454, 0.435, 0.594, 8.431, 0.195, 0.654, 4.544, 1.753, 0.053, 1.313, 1.118, 1.931, 4.523, 6.14, 0.553, 0.043, 1.203, 5.097, 4.735, 0.637, 1.842, 0.224, 0.461, 0.27, 0.08, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.024, 0.014, 0.009, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.002, 0.113, 0.0001, 0.002, 0.007, 0.005, 0.0001, 0.0001, 0.005, 0.002, 0.0001, 0.058, 0.012, 0.0001, 0.003, 0.029, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.002, 0.002, 0.044, 1.384, 0.696, 0.009, 0.027, 0.002, 0.002, 0.039, 0.005, 3.484, 0.98, 0.162, 0.003, 0.009, 0.002, 0.017, 0.009, 0.003, 0.005, 1.282, 0.993, 0.003, 0.142, 0.0001, 0.017, 0.0001, 0.002, 0.009, 0.007, 0.0001, 0.007, 0.005, 0.0001, 0.0001, 0.014, 8.846, 0.043, 0.545, 0.0001, 0.005, 0.046, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.031, 0.009, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.019, 0.003, 0.017, 0.0001, 0.0001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ckb\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.676, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.035, 0.002, 0.062, 0.0001, 0.0001, 0.003, 0.0001, 0.002, 0.131, 0.13, 0.001, 0.001, 0.011, 0.034, 0.374, 0.013, 0.01, 0.014, 0.008, 0.005, 0.004, 0.004, 0.004, 0.004, 0.005, 0.007, 0.05, 0.0001, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.009, 0.006, 0.007, 0.006, 0.004, 0.004, 0.004, 0.004, 0.005, 0.002, 0.003, 0.004, 0.007, 0.005, 0.003, 0.007, 0.001, 0.005, 0.01, 0.007, 0.002, 0.002, 0.003, 0.001, 0.001, 0.001, 0.004, 0.0001, 0.004, 0.0001, 0.003, 0.0001, 0.058, 0.008, 0.018, 0.017, 0.063, 0.009, 0.012, 0.017, 0.048, 0.001, 0.008, 0.031, 0.019, 0.043, 0.045, 0.012, 0.001, 0.045, 0.029, 0.036, 0.019, 0.006, 0.008, 0.003, 0.011, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.386, 0.193, 0.124, 0.067, 1.187, 1.207, 3.947, 0.41, 3.556, 0.028, 0.015, 0.002, 5.576, 0.003, 1.191, 0.005, 0.006, 0.005, 0.002, 0.004, 0.001, 6.665, 0.001, 0.002, 0.236, 0.001, 0.002, 0.008, 0.002, 0.002, 0.001, 0.006, 0.161, 0.192, 0.114, 0.062, 0.112, 0.064, 0.707, 4.366, 1.564, 2.13, 1.551, 0.015, 0.253, 0.092, 0.303, 2.261, 0.008, 2.411, 0.524, 1.151, 0.651, 0.531, 0.001, 0.004, 0.003, 0.092, 0.048, 0.036, 0.003, 0.003, 0.823, 0.003, 0.0001, 0.0001, 0.028, 0.007, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 15.514, 10.978, 4.45, 13.188, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.375, 0.002, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.063, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"co\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.449, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.862, 0.008, 0.387, 0.0001, 0.0001, 0.006, 0.001, 0.763, 0.212, 0.212, 0.003, 0.001, 0.925, 0.075, 0.859, 0.019, 0.189, 0.28, 0.146, 0.097, 0.087, 0.101, 0.081, 0.085, 0.107, 0.132, 0.097, 0.026, 0.009, 0.003, 0.01, 0.004, 0.0001, 0.325, 0.102, 0.335, 0.094, 0.091, 0.089, 0.126, 0.077, 0.208, 0.025, 0.02, 0.156, 0.189, 0.082, 0.052, 0.201, 0.016, 0.093, 0.268, 0.121, 0.17, 0.078, 0.019, 0.022, 0.005, 0.013, 0.032, 0.0001, 0.032, 0.0001, 0.016, 0.0001, 8.602, 0.557, 3.322, 3.101, 4.329, 0.784, 1.174, 1.381, 10.092, 0.419, 0.069, 2.83, 1.864, 5.457, 2.618, 1.888, 0.179, 4.342, 3.458, 4.676, 6.626, 0.877, 0.033, 0.017, 0.063, 0.595, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.058, 0.006, 0.004, 0.002, 0.003, 0.001, 0.001, 0.001, 0.004, 0.001, 0.001, 0.0001, 0.002, 0.002, 0.001, 0.0001, 0.002, 0.001, 0.001, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.039, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.789, 0.005, 0.002, 0.002, 0.004, 0.001, 0.002, 0.004, 0.94, 0.016, 0.001, 0.007, 0.251, 0.004, 0.001, 0.002, 0.005, 0.006, 0.189, 0.011, 0.005, 0.003, 0.002, 0.024, 0.003, 0.252, 0.004, 0.007, 0.006, 0.005, 0.002, 0.004, 0.0001, 0.0001, 0.05, 2.469, 0.006, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.032, 0.015, 0.008, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.004, 0.04, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"cr\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.443, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.088, 0.004, 0.073, 0.0001, 0.0001, 0.02, 0.0001, 0.023, 0.121, 0.12, 0.0001, 0.002, 0.629, 0.081, 0.971, 0.012, 0.119, 0.193, 0.101, 0.064, 0.076, 0.066, 0.061, 0.066, 0.062, 0.105, 0.063, 0.027, 0.0001, 0.0001, 0.0001, 0.015, 0.0001, 0.161, 0.04, 0.143, 0.045, 0.195, 0.034, 0.029, 0.053, 0.081, 0.084, 0.151, 0.056, 0.235, 0.167, 0.103, 0.138, 0.009, 0.033, 0.115, 0.119, 0.03, 0.034, 0.067, 0.012, 0.01, 0.004, 0.05, 0.0001, 0.047, 0.0001, 0.014, 0.0001, 9.914, 0.233, 4.69, 1.145, 5.906, 0.235, 0.326, 1.052, 10.924, 0.134, 6.149, 1.256, 2.551, 4.689, 5.033, 1.928, 0.073, 2.706, 3.099, 5.744, 0.924, 0.192, 2.967, 0.038, 0.312, 0.067, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.088, 0.031, 0.077, 0.099, 0.046, 0.115, 0.007, 0.048, 0.054, 0.011, 0.091, 0.103, 0.074, 0.037, 0.073, 0.005, 0.766, 0.405, 0.312, 0.295, 0.175, 0.052, 0.036, 0.009, 0.01, 0.038, 0.001, 0.005, 0.002, 0.0001, 0.001, 0.021, 0.037, 0.111, 0.205, 0.026, 0.084, 0.087, 0.065, 0.093, 0.076, 0.063, 0.057, 0.032, 0.002, 0.144, 0.111, 0.096, 0.017, 0.078, 0.065, 0.232, 0.037, 0.005, 0.0001, 0.0001, 0.021, 0.005, 0.022, 0.02, 0.014, 0.002, 0.005, 0.005, 0.0001, 0.0001, 0.046, 0.821, 0.023, 0.077, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 1.861, 0.08, 0.005, 0.002, 0.009, 0.005, 0.0001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"crh\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.666, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.545, 0.003, 0.2, 0.0001, 0.003, 0.006, 0.0001, 0.011, 0.498, 0.498, 0.001, 0.003, 0.581, 0.375, 1.265, 0.029, 0.54, 0.844, 0.447, 0.25, 0.254, 0.244, 0.225, 0.224, 0.237, 0.353, 0.036, 0.017, 0.017, 0.002, 0.017, 0.003, 0.0001, 0.292, 0.227, 0.115, 0.122, 0.258, 0.045, 0.081, 0.079, 0.299, 0.014, 0.172, 0.079, 0.19, 0.068, 0.102, 0.074, 0.317, 0.092, 0.196, 0.162, 0.157, 0.161, 0.003, 0.13, 0.089, 0.035, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 7.42, 1.383, 0.39, 2.173, 6.493, 0.253, 0.439, 0.324, 6.527, 0.039, 1.974, 3.301, 1.629, 5.164, 1.476, 0.486, 0.955, 4.625, 3.637, 2.416, 1.149, 1.071, 0.013, 0.004, 1.959, 0.598, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.415, 0.022, 0.015, 0.022, 0.008, 0.007, 0.005, 0.065, 0.008, 0.005, 0.004, 0.008, 0.007, 0.007, 0.003, 0.008, 0.005, 0.004, 0.004, 0.069, 0.234, 0.004, 0.026, 0.004, 0.006, 0.008, 0.008, 0.005, 0.067, 0.049, 0.094, 1.497, 0.026, 0.01, 0.278, 0.006, 0.008, 0.006, 0.005, 0.416, 0.004, 0.006, 0.005, 0.014, 0.004, 0.007, 0.006, 0.006, 0.149, 5.025, 0.014, 0.011, 0.012, 0.067, 0.295, 0.006, 0.022, 0.01, 0.019, 0.017, 0.605, 0.022, 0.039, 0.006, 0.0001, 0.0001, 0.035, 2.796, 4.495, 1.1, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.003, 0.002, 0.256, 0.079, 0.004, 0.002, 0.0001, 0.004, 0.008, 0.013, 0.021, 0.017, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.015, 0.009, 0.398, 0.007, 0.004, 0.019, 0.009, 0.005, 0.004, 0.004, 0.003, 0.002, 0.004, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"csb\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.825, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.296, 0.002, 0.584, 0.0001, 0.0001, 0.003, 0.001, 0.009, 0.331, 0.334, 0.002, 0.0001, 0.877, 0.236, 1.256, 0.065, 0.271, 0.637, 0.291, 0.193, 0.181, 0.174, 0.153, 0.187, 0.256, 0.339, 0.093, 0.04, 0.024, 0.004, 0.024, 0.003, 0.0001, 0.093, 0.136, 0.203, 0.135, 0.053, 0.045, 0.141, 0.038, 0.163, 0.132, 0.28, 0.122, 0.184, 0.116, 0.024, 0.275, 0.002, 0.1, 0.23, 0.118, 0.014, 0.056, 0.218, 0.119, 0.003, 0.085, 0.006, 0.0001, 0.007, 0.0001, 0.002, 0.0001, 4.612, 0.986, 3.096, 2.007, 3.546, 0.161, 1.136, 0.946, 4.255, 1.343, 2.142, 1.634, 1.571, 3.378, 2.668, 1.384, 0.004, 3.469, 3.152, 2.405, 0.834, 0.037, 2.89, 0.011, 0.614, 4.079, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.169, 0.025, 0.879, 0.003, 0.332, 0.515, 0.031, 0.005, 0.001, 0.002, 0.001, 0.001, 0.002, 0.002, 0.001, 0.005, 0.001, 0.013, 0.102, 0.134, 0.005, 0.002, 0.001, 0.001, 0.003, 0.049, 0.005, 0.012, 0.006, 0.026, 0.025, 0.003, 0.016, 0.006, 0.006, 0.677, 0.002, 0.001, 0.001, 0.001, 0.003, 1.17, 0.001, 2.19, 0.001, 0.003, 0.0001, 0.002, 0.009, 0.003, 2.322, 0.76, 1.31, 0.003, 0.004, 0.001, 0.007, 0.615, 0.005, 0.077, 0.465, 0.007, 0.003, 0.002, 0.0001, 0.0001, 0.14, 9.122, 0.543, 1.724, 0.0001, 0.001, 0.002, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.006, 0.002, 0.024, 0.023, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.005, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.197, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"cu\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.095, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.137, 0.0001, 0.05, 0.0001, 0.001, 0.001, 0.0001, 0.002, 0.026, 0.026, 0.001, 0.0001, 0.049, 0.014, 0.024, 0.015, 0.131, 0.259, 0.12, 0.082, 0.083, 0.082, 0.076, 0.078, 0.096, 0.129, 0.009, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.003, 0.001, 0.006, 0.001, 0.001, 0.001, 0.0001, 0.006, 0.004, 0.0001, 0.001, 0.001, 0.002, 0.002, 0.004, 0.001, 0.0001, 0.002, 0.004, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.003, 0.0001, 0.023, 0.002, 0.008, 0.007, 0.018, 0.001, 0.005, 0.004, 0.017, 0.005, 0.009, 0.01, 0.003, 0.016, 0.015, 0.003, 0.001, 0.01, 0.011, 0.009, 0.011, 0.004, 0.0001, 0.002, 0.002, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.938, 4.019, 2.29, 0.582, 0.265, 0.184, 0.28, 0.33, 0.175, 0.126, 2.698, 0.002, 1.962, 0.002, 0.135, 0.0001, 0.124, 0.906, 0.12, 0.072, 1.561, 0.0001, 0.139, 0.857, 0.034, 2.179, 0.103, 0.119, 0.097, 0.099, 0.095, 0.124, 0.126, 0.438, 0.049, 1.297, 0.06, 0.96, 0.01, 0.295, 0.011, 0.359, 0.005, 0.236, 0.002, 0.101, 0.019, 0.025, 3.114, 0.623, 1.373, 0.62, 1.221, 0.086, 0.518, 0.573, 2.627, 0.002, 1.325, 1.567, 0.924, 2.121, 2.823, 0.585, 0.0001, 0.0001, 0.514, 0.003, 0.006, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.001, 0.408, 0.0001, 0.016, 0.012, 21.25, 18.718, 0.249, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.51, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 1.747, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"cv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.247, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.093, 0.001, 0.059, 0.0001, 0.0001, 0.007, 0.0001, 0.003, 0.152, 0.151, 0.0001, 0.002, 0.478, 0.273, 0.79, 0.011, 0.204, 0.309, 0.183, 0.104, 0.101, 0.1, 0.081, 0.081, 0.096, 0.17, 0.076, 0.008, 0.002, 0.002, 0.002, 0.003, 0.0001, 0.004, 0.003, 0.005, 0.002, 0.002, 0.002, 0.002, 0.002, 0.019, 0.001, 0.001, 0.002, 0.003, 0.002, 0.002, 0.003, 0.0001, 0.003, 0.005, 0.003, 0.002, 0.006, 0.001, 0.01, 0.001, 0.0001, 0.013, 0.0001, 0.013, 0.0001, 0.001, 0.0001, 0.027, 0.004, 0.007, 0.008, 0.027, 0.004, 0.006, 0.007, 0.02, 0.001, 0.004, 0.016, 0.009, 0.019, 0.018, 0.006, 0.0001, 0.019, 0.014, 0.015, 0.011, 0.003, 0.002, 0.002, 0.004, 0.001, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 3.257, 1.78, 2.381, 2.851, 0.156, 1.36, 0.178, 0.773, 1.001, 0.006, 0.006, 0.869, 0.319, 0.035, 0.373, 0.165, 0.161, 0.088, 0.098, 0.049, 0.312, 2.25, 0.007, 0.017, 0.069, 0.007, 0.174, 0.039, 0.101, 0.06, 0.095, 0.155, 0.212, 0.157, 0.129, 0.054, 0.061, 0.066, 0.005, 1.16, 0.101, 0.002, 0.0001, 0.045, 0.001, 0.021, 0.156, 0.041, 4.16, 0.372, 1.295, 0.368, 0.304, 3.139, 0.041, 0.13, 2.185, 0.64, 1.311, 1.785, 0.994, 3.619, 1.18, 1.135, 0.0001, 0.0001, 0.101, 1.175, 3.79, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.0001, 0.002, 0.001, 24.733, 13.586, 0.004, 0.088, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.282, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"cy\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.628, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.48, 0.003, 0.545, 0.0001, 0.001, 0.007, 0.002, 0.872, 0.259, 0.258, 0.001, 0.001, 0.777, 0.194, 0.96, 0.016, 0.363, 0.487, 0.244, 0.138, 0.133, 0.135, 0.125, 0.126, 0.164, 0.239, 0.149, 0.081, 0.022, 0.001, 0.022, 0.003, 0.0001, 0.36, 0.242, 0.56, 0.267, 0.155, 0.163, 0.331, 0.126, 0.112, 0.06, 0.033, 0.279, 0.433, 0.133, 0.073, 0.238, 0.004, 0.18, 0.303, 0.196, 0.061, 0.026, 0.092, 0.003, 0.167, 0.006, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 7.082, 0.905, 1.506, 6.475, 6.263, 2.165, 2.494, 2.4, 4.773, 0.015, 0.114, 3.901, 1.419, 6.217, 4.277, 0.556, 0.008, 5.57, 2.092, 2.13, 1.941, 0.086, 2.82, 0.025, 5.712, 0.034, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.074, 0.005, 0.003, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.021, 0.002, 0.001, 0.0001, 0.001, 0.002, 0.033, 0.001, 0.001, 0.007, 0.009, 0.001, 0.001, 0.033, 0.007, 0.059, 0.003, 0.003, 0.001, 0.001, 0.003, 0.004, 0.015, 0.016, 0.004, 0.001, 0.004, 0.01, 0.012, 0.004, 0.003, 0.003, 0.004, 0.074, 0.043, 0.005, 0.016, 0.003, 0.006, 0.003, 0.002, 0.004, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.036, 0.221, 0.003, 0.06, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.007, 0.004, 0.014, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.072, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"din\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.698, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.927, 0.0001, 0.06, 0.0001, 0.003, 0.013, 0.0001, 0.015, 0.171, 0.17, 0.0001, 0.0001, 0.878, 0.077, 0.901, 0.027, 0.297, 0.229, 0.151, 0.055, 0.064, 0.078, 0.053, 0.048, 0.049, 0.126, 0.018, 0.013, 0.002, 0.0001, 0.002, 0.005, 0.0001, 0.424, 0.153, 0.093, 0.101, 0.075, 0.019, 0.074, 0.021, 0.051, 0.069, 0.324, 0.085, 0.16, 0.163, 0.021, 0.306, 0.002, 0.087, 0.062, 0.288, 0.034, 0.007, 0.069, 0.0001, 0.136, 0.003, 0.027, 0.0001, 0.027, 0.0001, 0.0001, 0.0001, 5.438, 0.999, 2.9, 1.603, 4.394, 0.024, 0.521, 1.912, 3.749, 0.362, 4.818, 2.02, 1.512, 4.26, 1.668, 1.035, 0.003, 2.29, 0.155, 3.595, 3.428, 0.022, 0.527, 0.011, 2.005, 0.013, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.036, 0.001, 0.001, 0.0001, 0.027, 0.0001, 0.026, 0.0001, 1.487, 0.0001, 0.005, 1.04, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 2.319, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.0001, 1.678, 0.006, 0.006, 0.0001, 0.0001, 0.01, 0.0001, 0.0001, 0.222, 1.181, 0.0001, 0.0001, 0.001, 0.004, 0.001, 0.0001, 3.25, 0.0001, 0.001, 0.006, 1.508, 0.003, 0.002, 0.006, 0.002, 0.0001, 0.011, 1.021, 0.001, 0.004, 0.002, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.016, 6.971, 0.0001, 1.041, 0.02, 0.0001, 0.0001, 4.193, 0.0001, 0.0001, 1.487, 0.0001, 0.027, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.062, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"diq\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.719, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.354, 0.008, 0.4, 0.0001, 0.0001, 0.009, 0.0001, 0.031, 0.299, 0.3, 0.001, 0.003, 0.98, 0.165, 1.27, 0.045, 0.227, 0.302, 0.162, 0.087, 0.08, 0.089, 0.076, 0.082, 0.096, 0.17, 0.156, 0.035, 0.026, 0.008, 0.027, 0.01, 0.0001, 0.309, 0.187, 0.135, 0.206, 0.243, 0.108, 0.12, 0.188, 0.05, 0.033, 0.209, 0.106, 0.271, 0.167, 0.06, 0.167, 0.062, 0.13, 0.271, 0.259, 0.059, 0.085, 0.06, 0.052, 0.088, 0.128, 0.014, 0.0001, 0.014, 0.0001, 0.002, 0.001, 7.586, 1.293, 0.911, 2.514, 8.148, 0.439, 0.62, 0.759, 4.61, 0.11, 2.125, 1.599, 2.095, 4.93, 3.468, 0.588, 0.377, 4.808, 2.018, 2.359, 1.695, 0.626, 1.106, 0.479, 3.36, 1.081, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.078, 0.018, 0.011, 0.012, 0.02, 0.015, 0.019, 0.078, 0.016, 0.004, 0.018, 0.002, 0.014, 0.004, 0.014, 0.003, 0.006, 0.005, 0.003, 0.011, 0.005, 0.006, 0.005, 0.002, 0.009, 0.023, 0.002, 0.004, 0.022, 0.016, 0.065, 0.865, 0.032, 0.01, 0.013, 0.005, 0.007, 0.004, 0.006, 0.242, 0.014, 0.032, 2.716, 0.012, 0.007, 0.008, 0.29, 0.015, 0.191, 2.379, 0.013, 0.015, 0.01, 0.006, 0.021, 0.004, 0.009, 0.01, 0.007, 0.128, 0.093, 0.009, 0.008, 0.006, 0.0001, 0.0001, 0.039, 3.563, 2.668, 0.816, 0.0001, 0.001, 0.0001, 0.005, 0.003, 0.002, 0.002, 0.0001, 0.03, 0.013, 0.034, 0.014, 0.001, 0.0001, 0.001, 0.012, 0.005, 0.037, 0.126, 0.091, 0.007, 0.013, 0.003, 0.0001, 0.0001, 0.0001, 0.019, 0.012, 0.072, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"dsb\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.783, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.853, 0.003, 0.608, 0.0001, 0.0001, 0.007, 0.002, 0.016, 0.311, 0.311, 0.022, 0.002, 0.839, 0.138, 1.194, 0.023, 0.287, 0.411, 0.214, 0.128, 0.124, 0.131, 0.109, 0.104, 0.125, 0.201, 0.084, 0.035, 0.006, 0.007, 0.007, 0.003, 0.0001, 0.155, 0.168, 0.123, 0.122, 0.077, 0.058, 0.102, 0.068, 0.054, 0.115, 0.164, 0.108, 0.197, 0.144, 0.038, 0.256, 0.004, 0.113, 0.246, 0.119, 0.042, 0.025, 0.244, 0.005, 0.007, 0.075, 0.008, 0.0001, 0.008, 0.0001, 0.002, 0.0001, 6.833, 1.047, 1.719, 1.818, 5.619, 0.234, 0.977, 0.835, 3.647, 3.795, 2.962, 1.965, 2.079, 4.006, 5.923, 1.615, 0.008, 3.224, 3.399, 2.803, 2.458, 0.071, 3.327, 0.021, 1.623, 1.195, 0.0001, 0.003, 0.0001, 0.001, 0.0001, 0.148, 0.049, 0.931, 0.01, 0.22, 0.006, 0.005, 0.266, 0.005, 0.002, 0.002, 0.002, 0.017, 0.029, 0.002, 0.002, 0.007, 0.003, 0.004, 0.026, 0.004, 0.064, 0.004, 0.004, 0.009, 0.024, 0.008, 1.886, 0.043, 0.009, 0.04, 0.009, 0.064, 0.625, 0.008, 0.004, 0.017, 0.003, 0.003, 0.004, 0.006, 0.017, 0.003, 0.004, 0.001, 0.008, 0.001, 0.002, 0.019, 0.008, 0.014, 1.225, 0.005, 0.009, 0.011, 0.005, 0.012, 0.012, 0.395, 0.009, 0.027, 0.02, 0.616, 0.016, 0.0001, 0.0001, 0.039, 1.311, 1.431, 3.692, 0.0001, 0.0001, 0.001, 0.004, 0.001, 0.001, 0.001, 0.0001, 0.017, 0.009, 0.074, 0.029, 0.001, 0.0001, 0.0001, 0.002, 0.007, 0.043, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.012, 0.141, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"dty\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.724, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.716, 0.001, 0.019, 0.0001, 0.0001, 0.003, 0.0001, 0.008, 0.063, 0.066, 0.001, 0.0001, 0.189, 0.033, 0.052, 0.008, 0.003, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.027, 0.004, 0.012, 0.001, 0.012, 0.001, 0.0001, 0.002, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.017, 0.012, 0.004, 0.005, 0.014, 0.002, 0.003, 0.006, 0.016, 0.001, 0.004, 0.007, 0.008, 0.013, 0.011, 0.003, 0.0001, 0.019, 0.008, 0.009, 0.004, 0.001, 0.003, 0.0001, 0.003, 0.001, 0.0001, 0.015, 0.0001, 0.0001, 0.0001, 0.87, 0.744, 0.354, 0.069, 0.0001, 0.295, 0.114, 1.106, 0.404, 0.216, 0.006, 1.008, 0.08, 2.434, 0.0001, 0.171, 0.009, 0.001, 0.001, 0.025, 0.014, 1.53, 0.174, 0.539, 0.045, 0.068, 0.25, 0.269, 0.443, 0.023, 0.04, 0.304, 0.083, 0.214, 0.028, 0.182, 24.937, 7.5, 0.641, 0.298, 1.687, 0.033, 0.816, 0.129, 0.459, 0.371, 1.179, 1.062, 2.109, 0.002, 1.084, 0.0001, 0.0001, 0.578, 0.275, 0.191, 1.004, 0.659, 0.001, 0.0001, 0.01, 0.01, 3.197, 1.534, 0.0001, 0.0001, 0.004, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.897, 0.0001, 0.034, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"dv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.449, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.782, 0.003, 0.057, 0.0001, 0.0001, 0.005, 0.0001, 0.005, 0.068, 0.068, 0.0001, 0.001, 0.01, 0.02, 0.58, 0.003, 0.08, 0.111, 0.068, 0.041, 0.031, 0.037, 0.03, 0.031, 0.035, 0.052, 0.01, 0.001, 0.003, 0.002, 0.003, 0.0001, 0.0001, 0.003, 0.001, 0.005, 0.002, 0.002, 0.002, 0.001, 0.003, 0.003, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.003, 0.0001, 0.002, 0.003, 0.005, 0.001, 0.001, 0.003, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.004, 0.0001, 0.069, 0.013, 0.026, 0.027, 0.096, 0.015, 0.017, 0.033, 0.065, 0.001, 0.006, 0.037, 0.021, 0.063, 0.061, 0.016, 0.001, 0.05, 0.05, 0.064, 0.025, 0.009, 0.011, 0.002, 0.014, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.961, 0.592, 2.65, 1.657, 0.723, 0.269, 1.597, 3.461, 1.72, 1.651, 0.757, 0.977, 1.223, 0.768, 1.538, 0.011, 0.778, 0.359, 0.094, 0.266, 0.255, 0.126, 0.187, 0.051, 0.006, 0.076, 0.047, 0.004, 0.004, 0.086, 0.041, 0.008, 0.02, 0.003, 0.091, 0.008, 0.069, 0.003, 5.331, 1.558, 2.986, 0.988, 3.164, 0.17, 3.662, 0.439, 0.51, 0.17, 3.636, 0.006, 0.014, 0.003, 0.002, 0.002, 0.001, 0.014, 0.001, 0.004, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.005, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.201, 0.101, 0.0001, 0.002, 0.0001, 0.0001, 45.417, 0.0001, 0.002, 0.0001, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.02, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"dz\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.39, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.815, 0.0001, 0.004, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.023, 0.023, 0.0001, 0.002, 0.003, 0.013, 0.008, 0.001, 0.017, 0.015, 0.012, 0.006, 0.005, 0.004, 0.005, 0.004, 0.004, 0.004, 0.001, 0.0001, 0.007, 0.0001, 0.007, 0.001, 0.0001, 0.002, 0.004, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.003, 0.001, 0.0001, 0.004, 0.0001, 0.002, 0.003, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.03, 0.011, 0.006, 0.008, 0.024, 0.002, 0.006, 0.009, 0.021, 0.002, 0.004, 0.014, 0.011, 0.019, 0.021, 0.004, 0.0001, 0.02, 0.011, 0.013, 0.01, 0.002, 0.002, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.269, 0.247, 1.794, 0.002, 1.18, 0.189, 0.19, 0.052, 0.002, 0.102, 0.016, 7.859, 0.051, 0.549, 0.008, 0.12, 0.301, 1.592, 0.28, 1.053, 0.694, 0.157, 1.278, 0.061, 0.824, 0.093, 0.2, 0.068, 0.006, 0.019, 0.267, 0.283, 0.898, 0.517, 1.238, 0.954, 0.214, 0.015, 2.251, 0.029, 0.117, 0.081, 0.001, 0.058, 0.0001, 0.012, 0.002, 0.0001, 0.002, 0.89, 2.149, 0.094, 1.08, 0.001, 0.0001, 0.053, 0.001, 0.0001, 0.926, 0.001, 10.076, 21.494, 2.583, 0.002, 0.0001, 0.0001, 0.002, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 32.733, 0.0001, 0.016, 0.005, 0.001, 0.002, 0.002, 0.001, 0.001, 0.003, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ee\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.047, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.659, 0.001, 0.347, 0.0001, 0.001, 0.004, 0.004, 0.044, 0.199, 0.199, 0.001, 0.0001, 0.713, 0.054, 1.348, 0.005, 0.312, 0.38, 0.219, 0.115, 0.09, 0.132, 0.118, 0.118, 0.109, 0.211, 0.064, 0.006, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.552, 0.172, 0.134, 0.182, 0.397, 0.085, 0.215, 0.112, 0.083, 0.04, 0.209, 0.217, 0.202, 0.168, 0.043, 0.117, 0.006, 0.112, 0.229, 0.176, 0.053, 0.059, 0.177, 0.021, 0.139, 0.02, 0.003, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 7.214, 1.62, 0.258, 2.122, 10.212, 0.557, 1.427, 0.62, 4.11, 0.028, 2.137, 3.419, 2.267, 3.348, 4.663, 0.886, 0.007, 1.264, 2.303, 2.327, 2.541, 0.557, 2.031, 0.389, 1.697, 0.84, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.058, 0.011, 0.016, 0.109, 0.004, 0.002, 0.009, 0.001, 0.003, 0.01, 0.044, 0.61, 0.005, 0.002, 0.0001, 0.003, 0.018, 0.018, 1.229, 0.009, 2.883, 0.003, 1.23, 0.001, 0.002, 0.008, 0.003, 0.085, 0.02, 0.018, 0.001, 0.001, 0.052, 0.01, 0.004, 0.485, 0.002, 0.0001, 0.002, 0.004, 0.005, 0.042, 0.003, 0.002, 0.003, 0.025, 0.002, 0.002, 0.007, 0.009, 0.047, 0.01, 0.005, 0.003, 0.005, 0.003, 0.006, 0.005, 0.14, 0.007, 0.005, 0.138, 0.008, 0.004, 0.0001, 0.0001, 0.039, 0.487, 0.018, 0.548, 1.276, 0.0001, 0.004, 4.335, 0.128, 0.004, 0.106, 0.013, 0.028, 0.013, 0.041, 0.016, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.018, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.138, 0.051, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"eml\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.684, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.039, 0.004, 0.415, 0.0001, 0.0001, 0.004, 0.001, 1.632, 0.216, 0.216, 0.001, 0.001, 0.746, 0.069, 0.997, 0.011, 0.415, 0.659, 0.408, 0.216, 0.231, 0.235, 0.226, 0.213, 0.215, 0.256, 0.061, 0.026, 0.05, 0.006, 0.05, 0.003, 0.0001, 0.44, 0.139, 0.4, 0.112, 0.078, 0.095, 0.114, 0.018, 0.424, 0.019, 0.012, 0.251, 0.226, 0.059, 0.026, 0.233, 0.016, 0.153, 0.231, 0.099, 0.036, 0.164, 0.011, 0.127, 0.003, 0.015, 0.004, 0.0001, 0.004, 0.0001, 0.002, 0.0001, 7.63, 0.549, 2.301, 3.601, 3.529, 0.617, 1.263, 0.808, 5.22, 0.113, 0.052, 4.92, 1.657, 5.406, 1.72, 1.353, 0.118, 3.957, 2.689, 3.146, 2.026, 0.904, 0.024, 0.02, 0.047, 0.34, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.239, 0.003, 0.006, 0.003, 0.004, 0.052, 0.002, 0.003, 0.008, 0.003, 0.006, 0.001, 0.002, 0.193, 0.002, 0.001, 0.002, 0.002, 0.003, 0.098, 0.002, 0.001, 0.001, 0.001, 0.033, 0.188, 0.003, 0.047, 0.006, 0.006, 0.001, 0.078, 0.562, 0.025, 0.617, 0.129, 0.182, 0.072, 0.003, 0.005, 1.444, 0.829, 0.895, 0.057, 0.235, 0.011, 0.346, 0.001, 0.004, 0.003, 0.664, 0.345, 0.314, 0.007, 0.019, 0.001, 0.003, 0.275, 0.004, 0.186, 0.062, 0.002, 0.002, 0.006, 0.0001, 0.0001, 0.011, 6.936, 0.1, 0.325, 0.0001, 0.004, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.003, 0.002, 0.007, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.192, 0.237, 0.003, 0.002, 0.005, 0.003, 0.003, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"eo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.154, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.737, 0.006, 0.429, 0.0001, 0.0001, 0.01, 0.001, 0.015, 0.235, 0.235, 0.001, 0.003, 0.936, 0.306, 0.916, 0.015, 0.284, 0.481, 0.226, 0.14, 0.134, 0.143, 0.121, 0.123, 0.155, 0.273, 0.072, 0.027, 0.012, 0.007, 0.013, 0.002, 0.0001, 0.209, 0.154, 0.114, 0.106, 0.232, 0.094, 0.127, 0.102, 0.106, 0.077, 0.183, 0.354, 0.184, 0.118, 0.083, 0.187, 0.004, 0.116, 0.241, 0.149, 0.061, 0.074, 0.035, 0.004, 0.009, 0.024, 0.021, 0.0001, 0.021, 0.0001, 0.004, 0.0001, 9.544, 0.784, 0.841, 2.534, 6.934, 0.706, 0.989, 0.423, 6.212, 2.407, 2.868, 4.302, 1.963, 5.456, 7.143, 1.699, 0.009, 4.617, 4.113, 4.222, 2.31, 1.083, 0.045, 0.017, 0.108, 0.46, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.058, 0.01, 0.009, 0.008, 0.004, 0.004, 0.002, 0.003, 0.037, 0.239, 0.001, 0.001, 0.004, 0.007, 0.001, 0.002, 0.002, 0.007, 0.002, 0.018, 0.008, 0.001, 0.002, 0.002, 0.002, 0.012, 0.003, 0.005, 0.093, 0.609, 0.008, 0.005, 0.021, 0.066, 0.005, 0.003, 0.01, 0.029, 0.002, 0.005, 0.005, 0.035, 0.002, 0.007, 0.002, 0.34, 0.001, 0.002, 0.012, 0.007, 0.011, 0.025, 0.006, 0.093, 0.016, 0.003, 0.007, 0.003, 0.008, 0.009, 0.016, 0.009, 0.009, 0.003, 0.0001, 0.0001, 0.038, 0.2, 0.946, 0.502, 0.0001, 0.001, 0.005, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.012, 0.006, 0.045, 0.015, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.003, 0.006, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.003, 0.056, 0.002, 0.001, 0.003, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"eu\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.418, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.177, 0.001, 0.297, 0.0001, 0.0001, 0.006, 0.001, 0.01, 0.167, 0.167, 0.0001, 0.001, 1.097, 0.307, 1.039, 0.006, 0.582, 0.665, 0.539, 0.263, 0.232, 0.207, 0.196, 0.233, 0.193, 0.297, 0.077, 0.037, 0.019, 0.004, 0.019, 0.001, 0.0001, 0.228, 0.197, 0.105, 0.074, 0.177, 0.09, 0.111, 0.131, 0.123, 0.048, 0.077, 0.106, 0.134, 0.065, 0.059, 0.121, 0.005, 0.05, 0.134, 0.08, 0.034, 0.046, 0.019, 0.022, 0.008, 0.029, 0.005, 0.0001, 0.005, 0.0001, 0.002, 0.0001, 11.924, 1.97, 0.229, 2.409, 9.817, 0.3, 1.545, 0.915, 6.874, 0.162, 4.015, 2.508, 1.08, 6.457, 4.385, 0.883, 0.011, 6.261, 2.025, 5.706, 3.55, 0.077, 0.032, 0.337, 0.117, 3.463, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.014, 0.003, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.003, 0.003, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.003, 0.003, 0.0001, 0.001, 0.008, 0.009, 0.002, 0.002, 0.004, 0.001, 0.001, 0.003, 0.009, 0.023, 0.001, 0.012, 0.001, 0.01, 0.001, 0.001, 0.003, 0.012, 0.007, 0.008, 0.006, 0.001, 0.003, 0.001, 0.002, 0.001, 0.004, 0.012, 0.004, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.039, 0.094, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.003, 0.007, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.013, 0.001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ext\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.183, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.144, 0.002, 0.474, 0.0001, 0.0001, 0.008, 0.001, 0.271, 0.191, 0.19, 0.004, 0.002, 1.06, 0.101, 0.854, 0.021, 0.249, 0.293, 0.188, 0.105, 0.088, 0.096, 0.085, 0.084, 0.099, 0.161, 0.072, 0.026, 0.008, 0.003, 0.006, 0.002, 0.0001, 0.241, 0.103, 0.248, 0.095, 0.369, 0.065, 0.091, 0.071, 0.128, 0.047, 0.018, 0.238, 0.161, 0.09, 0.062, 0.171, 0.026, 0.094, 0.188, 0.116, 0.077, 0.083, 0.02, 0.035, 0.009, 0.011, 0.028, 0.0001, 0.029, 0.0001, 0.001, 0.002, 8.822, 0.896, 2.974, 2.338, 7.586, 0.409, 0.951, 0.639, 6.63, 0.18, 0.079, 4.794, 1.966, 5.508, 3.713, 1.742, 0.451, 4.358, 5.625, 3.427, 5.456, 0.664, 0.026, 0.047, 0.265, 0.205, 0.0001, 0.012, 0.0001, 0.001, 0.0001, 0.09, 0.042, 0.016, 0.012, 0.021, 0.01, 0.009, 0.007, 0.019, 0.01, 0.009, 0.002, 0.007, 0.012, 0.003, 0.002, 0.01, 0.006, 0.003, 0.011, 0.012, 0.003, 0.002, 0.002, 0.004, 0.049, 0.003, 0.005, 0.01, 0.009, 0.003, 0.003, 0.02, 0.663, 0.003, 0.009, 0.008, 0.004, 0.004, 0.092, 0.006, 0.332, 0.01, 0.012, 0.009, 0.354, 0.005, 0.01, 0.016, 0.295, 0.019, 0.537, 0.024, 0.015, 0.008, 0.008, 0.011, 0.017, 0.174, 0.02, 0.041, 0.023, 0.01, 0.023, 0.0001, 0.0001, 0.078, 2.453, 0.012, 0.009, 0.0001, 0.0001, 0.0001, 0.019, 0.008, 0.025, 0.005, 0.0001, 0.151, 0.073, 0.021, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.005, 0.034, 0.026, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.021, 0.082, 0.001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ff\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.229, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.756, 0.003, 0.154, 0.0001, 0.0001, 0.002, 0.0001, 0.07, 0.321, 0.324, 0.004, 0.001, 1.19, 0.201, 1.011, 0.039, 0.221, 0.281, 0.197, 0.076, 0.082, 0.099, 0.098, 0.084, 0.101, 0.154, 0.153, 0.028, 0.04, 0.008, 0.04, 0.01, 0.0001, 0.371, 0.159, 0.12, 0.097, 0.095, 0.198, 0.111, 0.12, 0.065, 0.102, 0.267, 0.138, 0.299, 0.201, 0.095, 0.085, 0.014, 0.046, 0.262, 0.159, 0.061, 0.013, 0.059, 0.003, 0.066, 0.008, 0.007, 0.0001, 0.007, 0.0001, 0.006, 0.0001, 10.449, 0.928, 0.343, 3.119, 8.063, 0.727, 1.432, 1.127, 6.432, 0.966, 2.387, 3.274, 2.586, 6.222, 6.89, 0.484, 0.058, 2.623, 1.086, 2.251, 3.239, 0.048, 1.581, 0.013, 1.385, 0.042, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.25, 0.043, 0.0001, 0.008, 0.007, 0.001, 0.007, 0.002, 0.023, 0.003, 0.031, 0.086, 0.001, 0.0001, 0.009, 0.005, 0.007, 0.017, 0.003, 1.378, 0.001, 0.001, 0.0001, 1.485, 0.01, 0.123, 0.001, 0.002, 0.036, 0.035, 0.003, 0.0001, 0.06, 0.009, 0.0001, 0.003, 0.0001, 0.002, 0.02, 0.011, 0.007, 0.04, 0.001, 0.024, 0.001, 0.003, 0.0001, 0.0001, 0.006, 0.154, 0.006, 0.01, 0.135, 0.002, 0.002, 0.009, 0.004, 0.002, 0.004, 0.025, 0.02, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.111, 0.229, 0.005, 0.088, 0.202, 0.0001, 0.0001, 2.86, 0.02, 0.001, 0.001, 0.0001, 0.003, 0.0001, 0.017, 0.011, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.023, 0.044, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.248, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"fj\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.647, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.11, 0.005, 0.222, 0.0001, 0.0001, 0.0001, 0.002, 0.182, 0.39, 0.39, 0.002, 0.003, 0.665, 0.202, 1.418, 0.055, 0.382, 0.504, 0.342, 0.179, 0.168, 0.196, 0.159, 0.133, 0.129, 0.164, 0.07, 0.04, 0.02, 0.002, 0.02, 0.013, 0.002, 0.352, 0.212, 0.246, 0.146, 0.319, 0.066, 0.096, 0.061, 0.166, 0.217, 0.277, 0.179, 0.262, 0.29, 0.095, 0.254, 0.022, 0.118, 0.377, 0.355, 0.066, 0.534, 0.043, 0.003, 0.05, 0.01, 0.0001, 0.0001, 0.005, 0.0001, 0.008, 0.0001, 13.891, 0.708, 1.055, 1.505, 4.909, 0.352, 0.936, 0.685, 8.96, 0.075, 2.998, 2.827, 2.182, 5.506, 3.831, 0.546, 0.257, 2.747, 2.722, 3.592, 4.532, 2.046, 0.526, 0.045, 0.71, 0.111, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.267, 0.01, 0.007, 0.005, 0.007, 0.002, 0.0001, 0.002, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.008, 0.0001, 0.0001, 0.013, 0.192, 0.0001, 0.003, 0.002, 0.0001, 0.008, 0.002, 0.0001, 0.023, 0.022, 0.002, 0.0001, 0.007, 0.005, 0.0001, 0.01, 0.003, 0.0001, 0.002, 0.005, 0.002, 0.003, 0.0001, 0.0001, 0.0001, 0.01, 0.0001, 0.0001, 0.008, 0.0001, 0.003, 0.022, 0.003, 0.002, 0.005, 0.0001, 0.008, 0.0001, 0.01, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.0001, 0.0001, 0.013, 0.065, 0.0001, 0.023, 0.002, 0.0001, 0.0001, 0.013, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.002, 0.01, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.26, 0.0001, 0.003, 0.002, 0.0001, 0.003, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"fo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.171, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.257, 0.003, 0.223, 0.0001, 0.0001, 0.01, 0.002, 0.015, 0.14, 0.141, 0.001, 0.001, 0.983, 0.292, 1.473, 0.021, 0.562, 0.624, 0.361, 0.197, 0.187, 0.183, 0.177, 0.172, 0.176, 0.285, 0.092, 0.018, 0.015, 0.005, 0.015, 0.002, 0.0001, 0.162, 0.14, 0.076, 0.088, 0.116, 0.204, 0.083, 0.244, 0.058, 0.087, 0.291, 0.108, 0.161, 0.114, 0.065, 0.092, 0.004, 0.082, 0.312, 0.204, 0.061, 0.078, 0.034, 0.003, 0.012, 0.006, 0.004, 0.0001, 0.004, 0.0001, 0.002, 0.0001, 6.488, 0.752, 0.145, 1.557, 3.939, 1.383, 2.415, 1.123, 6.407, 0.628, 2.151, 3.099, 2.563, 5.616, 2.172, 0.663, 0.005, 6.541, 3.536, 4.094, 3.571, 2.164, 0.044, 0.017, 0.862, 0.025, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.054, 0.049, 0.002, 0.002, 0.002, 0.004, 0.003, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.091, 0.001, 0.001, 0.001, 0.001, 0.001, 0.033, 0.002, 0.001, 0.002, 0.001, 0.017, 0.004, 0.006, 0.001, 0.006, 0.009, 0.001, 0.001, 0.01, 0.939, 0.001, 0.001, 0.016, 0.008, 0.277, 0.003, 0.006, 0.007, 0.001, 0.002, 0.001, 1.13, 0.001, 0.004, 1.899, 0.003, 0.003, 0.718, 0.002, 0.002, 0.014, 0.001, 0.801, 0.002, 0.333, 0.003, 0.004, 0.203, 0.003, 0.002, 0.0001, 0.0001, 0.022, 6.504, 0.004, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.012, 0.005, 0.009, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.004, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.053, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"frp\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.788, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.014, 0.012, 0.659, 0.001, 0.0001, 0.001, 0.001, 0.361, 0.368, 0.368, 0.001, 0.0001, 0.743, 0.467, 0.873, 0.02, 0.214, 0.426, 0.274, 0.128, 0.113, 0.117, 0.113, 0.107, 0.116, 0.228, 0.11, 0.019, 0.081, 0.005, 0.081, 0.002, 0.0001, 0.35, 0.279, 0.333, 0.142, 0.141, 0.152, 0.135, 0.066, 0.159, 0.087, 0.033, 0.593, 0.22, 0.099, 0.082, 0.206, 0.019, 0.236, 0.314, 0.121, 0.062, 0.179, 0.013, 0.025, 0.027, 0.009, 0.022, 0.0001, 0.022, 0.0001, 0.006, 0.0001, 6.3, 0.639, 2.237, 2.924, 6.953, 0.549, 0.996, 0.581, 3.639, 0.252, 0.124, 3.838, 1.505, 5.552, 4.982, 1.442, 0.366, 4.363, 4.487, 4.4, 2.763, 0.919, 0.029, 0.168, 0.501, 0.132, 0.0001, 0.008, 0.0001, 0.001, 0.0001, 0.591, 0.012, 0.04, 0.026, 0.003, 0.003, 0.002, 0.002, 0.077, 0.083, 0.002, 0.003, 0.004, 0.003, 0.002, 0.005, 0.007, 0.003, 0.002, 0.023, 0.039, 0.002, 0.001, 0.002, 0.013, 0.56, 0.002, 0.002, 0.004, 0.004, 0.002, 0.004, 0.079, 0.014, 0.761, 0.004, 0.005, 0.003, 0.004, 0.044, 1.724, 0.994, 0.451, 0.049, 0.014, 0.007, 0.008, 0.004, 0.024, 0.005, 0.02, 0.03, 0.411, 0.012, 0.002, 0.176, 0.006, 0.01, 0.014, 0.089, 0.007, 0.007, 0.007, 0.005, 0.0001, 0.0001, 0.277, 4.789, 0.008, 0.018, 0.001, 0.0001, 0.0001, 0.008, 0.004, 0.004, 0.003, 0.001, 0.014, 0.004, 0.075, 0.032, 0.0001, 0.0001, 0.001, 0.005, 0.001, 0.004, 0.007, 0.005, 0.0001, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.004, 0.024, 0.586, 0.003, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"frr\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.212, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.548, 0.003, 0.682, 0.0001, 0.001, 0.008, 0.0001, 0.237, 0.407, 0.407, 0.015, 0.002, 0.738, 0.264, 1.349, 0.032, 0.426, 0.487, 0.285, 0.155, 0.131, 0.142, 0.153, 0.132, 0.154, 0.213, 0.163, 0.033, 0.094, 0.019, 0.094, 0.014, 0.0001, 0.424, 0.235, 0.114, 0.463, 0.142, 0.219, 0.132, 0.243, 0.123, 0.143, 0.217, 0.156, 0.239, 0.202, 0.1, 0.178, 0.008, 0.163, 0.493, 0.169, 0.107, 0.04, 0.158, 0.005, 0.006, 0.018, 0.02, 0.0001, 0.02, 0.0001, 0.015, 0.0001, 7.38, 1.026, 0.694, 2.643, 7.751, 1.48, 1.329, 1.414, 5.143, 0.835, 1.946, 2.506, 1.658, 6.635, 2.847, 0.757, 0.017, 4.866, 3.953, 4.835, 3.559, 0.125, 1.078, 0.025, 0.13, 0.078, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.121, 0.04, 0.005, 0.007, 0.011, 0.01, 0.004, 0.003, 0.014, 0.003, 0.007, 0.003, 0.003, 0.004, 0.004, 0.002, 0.006, 0.041, 0.003, 0.029, 0.004, 0.002, 0.039, 0.002, 0.005, 0.057, 0.003, 0.003, 0.033, 0.001, 0.015, 0.005, 0.043, 0.01, 0.008, 0.004, 0.702, 0.24, 0.006, 0.008, 0.007, 0.041, 0.006, 0.01, 0.003, 0.013, 0.002, 0.004, 0.015, 0.008, 0.014, 0.009, 0.006, 0.008, 0.971, 0.003, 0.022, 0.007, 0.006, 0.005, 0.964, 0.005, 0.004, 0.003, 0.0001, 0.0001, 0.041, 3.039, 0.101, 0.012, 0.0001, 0.001, 0.0001, 0.016, 0.008, 0.014, 0.003, 0.0001, 0.014, 0.006, 0.019, 0.007, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.018, 0.017, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.024, 0.122, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"fur\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.465, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.803, 0.002, 0.385, 0.0001, 0.0001, 0.006, 0.001, 0.135, 0.204, 0.203, 0.001, 0.001, 0.945, 0.084, 1.045, 0.015, 0.262, 0.474, 0.24, 0.16, 0.15, 0.158, 0.149, 0.15, 0.168, 0.219, 0.076, 0.046, 0.024, 0.003, 0.006, 0.002, 0.0001, 0.268, 0.102, 0.337, 0.116, 0.078, 0.115, 0.121, 0.022, 0.278, 0.048, 0.02, 0.218, 0.154, 0.07, 0.05, 0.172, 0.005, 0.086, 0.217, 0.131, 0.073, 0.108, 0.016, 0.024, 0.002, 0.027, 0.004, 0.001, 0.004, 0.0001, 0.016, 0.0001, 6.873, 0.54, 3.119, 3.521, 7.672, 0.855, 0.912, 0.901, 8.131, 0.838, 0.065, 4.486, 1.745, 5.361, 3.491, 1.873, 0.016, 4.269, 4.833, 4.488, 2.566, 1.056, 0.024, 0.012, 0.029, 0.497, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.039, 0.005, 0.002, 0.002, 0.002, 0.001, 0.001, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.007, 0.001, 0.0001, 0.001, 0.0001, 0.002, 0.013, 0.0001, 0.001, 0.008, 0.008, 0.0001, 0.002, 0.187, 0.005, 0.973, 0.001, 0.004, 0.001, 0.001, 0.127, 0.268, 0.009, 0.161, 0.005, 0.069, 0.003, 0.185, 0.001, 0.033, 0.003, 0.05, 0.006, 0.254, 0.001, 0.005, 0.001, 0.001, 0.015, 0.003, 0.208, 0.005, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.042, 2.523, 0.01, 0.009, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.01, 0.005, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.038, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"fy\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.82, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.701, 0.001, 0.398, 0.0001, 0.001, 0.014, 0.003, 0.455, 0.166, 0.166, 0.001, 0.0001, 0.747, 0.192, 0.908, 0.008, 0.277, 0.415, 0.181, 0.098, 0.101, 0.113, 0.107, 0.108, 0.145, 0.219, 0.052, 0.025, 0.005, 0.001, 0.005, 0.002, 0.0001, 0.213, 0.183, 0.091, 0.343, 0.093, 0.196, 0.109, 0.18, 0.193, 0.088, 0.132, 0.122, 0.161, 0.156, 0.11, 0.106, 0.003, 0.108, 0.302, 0.13, 0.038, 0.048, 0.113, 0.006, 0.12, 0.009, 0.007, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 6.027, 1.091, 0.691, 3.439, 11.73, 1.889, 1.306, 1.373, 5.412, 1.009, 2.464, 2.808, 1.837, 7.368, 3.471, 1.074, 0.006, 5.381, 4.264, 5.226, 1.268, 0.226, 1.241, 0.017, 1.595, 0.254, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.043, 0.003, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.007, 0.001, 0.0001, 0.001, 0.001, 0.004, 0.021, 0.0001, 0.001, 0.004, 0.003, 0.001, 0.001, 0.013, 0.004, 0.263, 0.001, 0.008, 0.001, 0.001, 0.002, 0.002, 0.01, 0.258, 0.016, 0.001, 0.003, 0.001, 0.013, 0.005, 0.004, 0.006, 0.003, 0.084, 0.002, 0.006, 0.001, 0.003, 0.002, 0.21, 0.348, 0.006, 0.003, 0.002, 0.001, 0.0001, 0.0001, 0.02, 1.229, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.006, 0.003, 0.015, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.042, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ga\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.234, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.249, 0.002, 0.288, 0.0001, 0.001, 0.013, 0.002, 0.109, 0.15, 0.15, 0.0001, 0.002, 0.872, 0.193, 0.872, 0.017, 0.241, 0.359, 0.187, 0.093, 0.09, 0.096, 0.095, 0.093, 0.117, 0.202, 0.044, 0.013, 0.002, 0.003, 0.002, 0.002, 0.0001, 0.26, 0.338, 0.441, 0.188, 0.097, 0.15, 0.18, 0.066, 0.279, 0.041, 0.036, 0.154, 0.249, 0.121, 0.062, 0.138, 0.005, 0.145, 0.311, 0.272, 0.036, 0.033, 0.04, 0.007, 0.009, 0.007, 0.033, 0.0001, 0.031, 0.0001, 0.002, 0.0001, 11.315, 1.29, 2.859, 2.236, 4.184, 0.692, 2.117, 5.503, 7.212, 0.011, 0.093, 2.991, 1.605, 6.018, 2.868, 0.471, 0.007, 4.409, 3.653, 3.32, 1.715, 0.088, 0.061, 0.021, 0.135, 0.028, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.063, 0.032, 0.004, 0.003, 0.002, 0.001, 0.001, 0.002, 0.002, 0.059, 0.001, 0.002, 0.002, 0.009, 0.001, 0.001, 0.001, 0.001, 0.001, 0.044, 0.003, 0.001, 0.001, 0.001, 0.005, 0.023, 0.008, 0.001, 0.006, 0.006, 0.0001, 0.001, 0.02, 1.278, 0.008, 0.002, 0.005, 0.001, 0.001, 0.002, 0.002, 1.021, 0.001, 0.002, 0.002, 1.343, 0.001, 0.001, 0.006, 0.004, 0.004, 0.674, 0.002, 0.003, 0.005, 0.001, 0.004, 0.002, 0.624, 0.002, 0.005, 0.004, 0.003, 0.002, 0.0001, 0.0001, 0.022, 5.087, 0.004, 0.005, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.011, 0.005, 0.021, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.061, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"gag\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.391, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.28, 0.016, 0.1, 0.0001, 0.001, 0.011, 0.0001, 0.023, 0.153, 0.154, 0.0001, 0.0001, 0.918, 0.454, 1.065, 0.029, 0.183, 0.22, 0.131, 0.062, 0.067, 0.072, 0.06, 0.06, 0.062, 0.143, 0.13, 0.023, 0.015, 0.004, 0.015, 0.028, 0.0001, 0.378, 0.403, 0.048, 0.156, 0.135, 0.049, 0.237, 0.117, 0.049, 0.029, 0.415, 0.105, 0.242, 0.108, 0.187, 0.134, 0.003, 0.159, 0.189, 0.383, 0.092, 0.136, 0.005, 0.011, 0.079, 0.04, 0.002, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 9.932, 1.463, 0.503, 2.949, 4.34, 0.314, 1.11, 0.547, 5.816, 0.052, 2.859, 4.285, 1.983, 5.174, 2.034, 0.659, 0.007, 5.297, 2.304, 2.138, 2.154, 0.741, 0.006, 0.007, 1.825, 1.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.33, 0.02, 0.014, 0.017, 0.012, 0.006, 0.003, 0.09, 0.004, 0.002, 0.005, 0.006, 0.005, 0.012, 0.002, 0.007, 0.002, 0.004, 0.002, 0.034, 0.066, 0.003, 0.036, 0.002, 0.004, 0.038, 0.004, 0.03, 0.138, 0.106, 0.046, 1.073, 0.005, 0.011, 0.07, 0.054, 1.028, 0.001, 0.006, 0.629, 0.003, 0.006, 0.12, 0.006, 0.002, 0.001, 0.004, 0.003, 0.193, 2.957, 0.016, 0.012, 0.008, 0.03, 0.347, 0.009, 0.023, 0.005, 0.018, 0.016, 1.278, 0.014, 0.03, 0.005, 0.0001, 0.0001, 0.012, 3.601, 3.155, 1.149, 0.001, 0.0001, 0.029, 0.03, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.224, 0.106, 0.002, 0.001, 0.0001, 0.0001, 0.001, 0.003, 0.019, 0.015, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.005, 0.317, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"gan\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.76, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.481, 0.002, 0.018, 0.0001, 0.0001, 0.024, 0.003, 0.008, 0.023, 0.024, 0.002, 0.006, 0.023, 0.038, 0.047, 0.01, 0.315, 0.585, 0.297, 0.204, 0.191, 0.202, 0.185, 0.171, 0.182, 0.259, 0.005, 0.001, 0.007, 0.003, 0.007, 0.002, 0.0001, 0.033, 0.019, 0.032, 0.016, 0.012, 0.012, 0.016, 0.021, 0.015, 0.011, 0.012, 0.015, 0.023, 0.016, 0.01, 0.022, 0.002, 0.018, 0.031, 0.022, 0.006, 0.009, 0.014, 0.002, 0.005, 0.001, 0.008, 0.001, 0.008, 0.0001, 0.002, 0.0001, 0.219, 0.03, 0.061, 0.069, 0.246, 0.023, 0.046, 0.084, 0.187, 0.005, 0.034, 0.116, 0.064, 0.184, 0.17, 0.035, 0.003, 0.158, 0.115, 0.138, 0.085, 0.023, 0.019, 0.007, 0.04, 0.007, 0.001, 0.001, 0.001, 0.0001, 0.0001, 3.244, 1.279, 2.127, 0.643, 0.387, 0.883, 0.435, 0.848, 1.431, 1.201, 0.629, 1.296, 2.258, 1.163, 0.623, 0.808, 0.937, 0.395, 0.26, 0.593, 0.667, 0.608, 1.031, 1.999, 0.578, 0.845, 0.936, 0.665, 1.536, 0.644, 0.439, 0.928, 0.498, 0.603, 0.631, 0.704, 0.585, 0.768, 0.515, 0.538, 0.76, 0.649, 0.365, 0.712, 0.597, 1.095, 0.882, 0.565, 2.328, 1.119, 0.438, 0.543, 1.012, 0.372, 0.441, 0.708, 1.829, 1.205, 1.47, 1.203, 2.219, 1.044, 0.843, 1.251, 0.0001, 0.0001, 0.055, 0.02, 0.005, 0.006, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.002, 0.0001, 0.018, 0.009, 0.031, 0.009, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.007, 0.036, 0.032, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.029, 0.011, 0.055, 2.062, 3.549, 9.312, 4.838, 3.056, 2.889, 2.67, 0.003, 0.005, 0.013, 0.003, 0.002, 1.772, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"gd\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.483, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.829, 0.001, 0.374, 0.0001, 0.0001, 0.027, 0.001, 0.653, 0.225, 0.224, 0.001, 0.001, 0.74, 0.732, 0.959, 0.016, 0.275, 0.512, 0.251, 0.163, 0.143, 0.16, 0.146, 0.151, 0.187, 0.234, 0.126, 0.023, 0.004, 0.003, 0.004, 0.002, 0.0001, 0.399, 0.354, 0.494, 0.195, 0.121, 0.114, 0.226, 0.061, 0.158, 0.033, 0.034, 0.204, 0.239, 0.107, 0.062, 0.151, 0.004, 0.164, 0.477, 0.402, 0.038, 0.033, 0.037, 0.023, 0.009, 0.008, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.001, 13.191, 1.481, 2.674, 2.933, 4.722, 0.55, 2.044, 6.832, 6.396, 0.019, 0.13, 2.757, 1.684, 7.147, 2.433, 0.32, 0.014, 3.962, 3.004, 2.554, 2.054, 0.073, 0.068, 0.016, 0.125, 0.044, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.262, 0.013, 0.005, 0.005, 0.004, 0.003, 0.002, 0.002, 0.031, 0.005, 0.004, 0.001, 0.011, 0.003, 0.002, 0.001, 0.006, 0.003, 0.012, 0.014, 0.004, 0.002, 0.002, 0.002, 0.027, 0.178, 0.003, 0.005, 0.012, 0.011, 0.001, 0.003, 0.677, 0.029, 0.002, 0.003, 0.009, 0.003, 0.004, 0.005, 0.218, 0.029, 0.004, 0.003, 0.303, 0.022, 0.002, 0.003, 0.018, 0.008, 0.323, 0.026, 0.004, 0.004, 0.01, 0.002, 0.006, 0.223, 0.01, 0.003, 0.014, 0.004, 0.005, 0.002, 0.0001, 0.0001, 0.041, 1.912, 0.009, 0.011, 0.0001, 0.0001, 0.0001, 0.018, 0.01, 0.015, 0.003, 0.0001, 0.015, 0.007, 0.02, 0.006, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.008, 0.009, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.004, 0.244, 0.002, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"gl\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.812, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.39, 0.002, 0.342, 0.0001, 0.0001, 0.01, 0.001, 0.013, 0.144, 0.144, 0.001, 0.002, 1.02, 0.075, 0.726, 0.01, 0.251, 0.326, 0.181, 0.093, 0.083, 0.092, 0.082, 0.082, 0.102, 0.185, 0.047, 0.021, 0.003, 0.002, 0.002, 0.001, 0.0001, 0.331, 0.122, 0.257, 0.114, 0.192, 0.107, 0.104, 0.065, 0.139, 0.039, 0.03, 0.104, 0.167, 0.127, 0.177, 0.186, 0.007, 0.111, 0.187, 0.12, 0.054, 0.074, 0.026, 0.055, 0.009, 0.01, 0.005, 0.0001, 0.005, 0.0001, 0.003, 0.0001, 9.121, 0.85, 3.271, 4.11, 8.668, 0.721, 0.784, 0.524, 5.185, 0.017, 0.092, 2.548, 2.069, 5.528, 7.673, 1.889, 0.464, 5.046, 5.357, 3.627, 2.8, 0.704, 0.036, 0.564, 0.085, 0.291, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.015, 0.013, 0.003, 0.002, 0.002, 0.001, 0.002, 0.002, 0.001, 0.012, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.006, 0.003, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.003, 0.003, 0.0001, 0.001, 0.028, 0.396, 0.001, 0.002, 0.002, 0.001, 0.001, 0.003, 0.003, 0.383, 0.003, 0.007, 0.001, 0.442, 0.001, 0.001, 0.005, 0.193, 0.006, 0.599, 0.002, 0.002, 0.003, 0.001, 0.003, 0.002, 0.219, 0.007, 0.008, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.05, 2.267, 0.004, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.011, 0.005, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.014, 0.003, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"glk\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.405, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.911, 0.005, 0.048, 0.0001, 0.0001, 0.001, 0.0001, 0.017, 0.104, 0.105, 0.0001, 0.001, 0.019, 0.086, 0.553, 0.019, 0.043, 0.074, 0.037, 0.051, 0.028, 0.037, 0.027, 0.021, 0.025, 0.021, 0.078, 0.0001, 0.005, 0.006, 0.007, 0.001, 0.0001, 0.004, 0.003, 0.008, 0.003, 0.003, 0.002, 0.002, 0.002, 0.003, 0.001, 0.001, 0.002, 0.004, 0.002, 0.001, 0.003, 0.0001, 0.003, 0.005, 0.008, 0.006, 0.001, 0.002, 0.0001, 0.001, 0.001, 0.003, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.177, 0.041, 0.015, 0.061, 0.102, 0.013, 0.028, 0.036, 0.115, 0.015, 0.035, 0.047, 0.057, 0.128, 0.083, 0.024, 0.008, 0.098, 0.063, 0.07, 0.058, 0.027, 0.009, 0.013, 0.021, 0.022, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 0.159, 0.386, 0.313, 0.148, 1.28, 2.09, 3.65, 3.311, 2.644, 0.084, 1.185, 0.003, 3.768, 0.001, 0.042, 0.015, 0.057, 0.007, 0.001, 0.006, 0.005, 0.0001, 0.0001, 0.0001, 0.027, 0.07, 0.004, 0.009, 0.002, 0.001, 0.0001, 0.024, 0.001, 0.005, 0.174, 0.185, 0.526, 0.0001, 0.349, 5.779, 1.561, 0.992, 2.058, 0.045, 0.725, 0.235, 0.5, 2.399, 0.083, 3.048, 0.622, 2.068, 1.214, 0.15, 0.072, 0.14, 0.046, 0.343, 0.079, 0.014, 0.001, 0.0001, 0.271, 0.0001, 0.0001, 0.0001, 0.044, 0.065, 0.002, 0.021, 0.0001, 0.003, 0.0001, 0.068, 0.0001, 0.285, 0.001, 0.0001, 0.001, 0.0001, 0.005, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 21.901, 14.77, 1.833, 3.683, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.141, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"gn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.37, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.685, 0.002, 0.175, 0.0001, 0.0001, 0.007, 0.0001, 0.625, 0.171, 0.173, 0.001, 0.002, 1.108, 0.288, 0.925, 0.011, 0.221, 0.306, 0.165, 0.094, 0.084, 0.088, 0.078, 0.08, 0.105, 0.172, 0.107, 0.053, 0.096, 0.001, 0.096, 0.001, 0.0001, 0.314, 0.094, 0.194, 0.053, 0.124, 0.074, 0.125, 0.128, 0.14, 0.097, 0.171, 0.086, 0.202, 0.077, 0.188, 0.312, 0.005, 0.119, 0.136, 0.188, 0.098, 0.072, 0.01, 0.015, 0.073, 0.005, 0.013, 0.0001, 0.013, 0.0001, 0.0001, 0.003, 10.368, 1.134, 1.037, 1.076, 7.653, 0.097, 1.575, 2.931, 4.208, 1.013, 1.951, 0.867, 2.302, 2.015, 5.216, 3.17, 0.036, 4.342, 1.438, 2.887, 4.01, 2.242, 0.013, 0.023, 2.052, 0.103, 0.002, 0.004, 0.0001, 0.0001, 0.0001, 1.036, 0.019, 0.002, 0.069, 0.003, 0.001, 0.001, 0.001, 0.002, 0.002, 0.001, 0.003, 0.001, 0.004, 0.0001, 0.001, 0.002, 0.06, 0.001, 0.011, 0.003, 0.002, 0.001, 0.001, 0.002, 0.862, 0.001, 0.001, 0.08, 0.08, 0.0001, 0.001, 0.04, 0.787, 0.003, 0.72, 0.019, 0.002, 0.003, 0.003, 0.016, 1.383, 0.003, 0.016, 0.003, 0.256, 0.002, 0.013, 0.007, 0.786, 0.011, 0.399, 0.027, 0.172, 0.004, 0.001, 0.003, 0.065, 0.529, 0.066, 0.013, 0.527, 0.003, 0.003, 0.0001, 0.0001, 0.078, 4.545, 0.392, 0.1, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.002, 0.065, 0.0001, 0.004, 0.002, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.015, 0.405, 1.034, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"gom\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.459, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.409, 0.004, 0.032, 0.0001, 0.0001, 0.004, 0.001, 0.065, 0.082, 0.086, 0.0001, 0.001, 0.31, 0.092, 0.614, 0.025, 0.037, 0.076, 0.035, 0.025, 0.021, 0.026, 0.02, 0.018, 0.022, 0.033, 0.044, 0.02, 0.0001, 0.003, 0.0001, 0.007, 0.0001, 0.043, 0.023, 0.024, 0.027, 0.017, 0.011, 0.022, 0.028, 0.023, 0.016, 0.018, 0.014, 0.036, 0.017, 0.018, 0.033, 0.001, 0.019, 0.035, 0.046, 0.011, 0.011, 0.005, 0.004, 0.003, 0.004, 0.001, 0.0001, 0.001, 0.0001, 0.007, 0.001, 1.398, 0.134, 0.264, 0.41, 0.83, 0.062, 0.182, 0.613, 0.742, 0.041, 0.372, 0.505, 0.457, 0.862, 0.987, 0.203, 0.002, 0.568, 0.367, 0.732, 0.344, 0.233, 0.034, 0.102, 0.093, 0.096, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 1.014, 0.337, 1.717, 0.03, 0.0001, 0.2, 0.49, 1.091, 0.031, 0.184, 0.021, 0.587, 0.017, 1.766, 0.002, 0.057, 0.002, 0.019, 0.005, 0.016, 0.001, 1.048, 0.146, 0.534, 0.083, 0.03, 0.659, 0.006, 0.381, 0.026, 0.006, 0.26, 0.031, 0.261, 0.004, 0.294, 21.971, 5.237, 0.529, 0.24, 0.912, 0.021, 0.699, 0.107, 0.285, 0.131, 0.613, 1.017, 1.508, 0.008, 1.629, 0.499, 0.008, 0.864, 0.313, 0.067, 0.93, 0.419, 0.0001, 0.006, 0.002, 0.0001, 3.471, 0.661, 0.0001, 0.0001, 0.008, 0.024, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 26.65, 0.0001, 0.078, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"got\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.339, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.094, 0.003, 1.291, 0.0001, 0.0001, 0.0001, 0.0001, 0.038, 0.115, 0.115, 0.004, 0.002, 1.558, 0.264, 1.449, 0.007, 0.147, 0.29, 0.265, 0.261, 0.158, 0.118, 0.082, 0.102, 0.128, 0.101, 0.042, 0.039, 0.006, 0.003, 0.008, 0.017, 0.0001, 0.013, 0.006, 0.028, 0.006, 0.126, 0.004, 0.142, 0.123, 0.192, 0.004, 0.003, 0.01, 0.007, 0.004, 0.248, 0.007, 0.0001, 0.011, 0.012, 0.024, 0.014, 0.037, 0.008, 0.0001, 0.001, 0.001, 0.004, 0.0001, 0.004, 0.0001, 0.0001, 0.001, 1.416, 0.252, 0.277, 0.447, 1.622, 0.345, 0.341, 0.482, 1.005, 0.151, 0.167, 0.523, 0.441, 1.296, 0.895, 0.224, 0.019, 0.998, 0.984, 0.975, 0.495, 0.221, 0.388, 0.018, 0.135, 0.029, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.227, 0.027, 0.943, 1.525, 0.623, 0.417, 0.281, 0.024, 0.043, 0.442, 0.017, 0.001, 12.517, 4.391, 0.0001, 0.004, 16.904, 0.001, 0.002, 0.005, 0.001, 0.003, 0.004, 0.004, 0.0001, 0.003, 0.047, 0.043, 0.003, 0.003, 0.004, 0.002, 1.424, 0.066, 0.105, 0.001, 0.004, 0.001, 0.009, 0.002, 0.017, 0.005, 0.002, 0.005, 0.002, 0.035, 0.001, 0.003, 3.235, 0.309, 0.439, 0.698, 0.617, 0.042, 0.143, 0.379, 0.509, 2.203, 0.495, 0.498, 0.573, 1.215, 0.432, 0.907, 0.0001, 0.0001, 1.432, 0.164, 0.007, 0.004, 0.001, 0.002, 0.0001, 0.004, 0.018, 0.002, 0.024, 0.0001, 0.034, 0.019, 0.033, 0.012, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.003, 0.002, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.003, 0.09, 0.108, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.902, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"gv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.271, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.449, 0.001, 0.421, 0.002, 0.001, 0.012, 0.004, 0.833, 0.218, 0.217, 0.001, 0.004, 1.036, 0.572, 0.962, 0.016, 0.26, 0.327, 0.165, 0.089, 0.084, 0.093, 0.087, 0.088, 0.107, 0.189, 0.087, 0.045, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.277, 0.194, 0.324, 0.123, 0.14, 0.124, 0.191, 0.146, 0.079, 0.067, 0.068, 0.129, 0.207, 0.152, 0.084, 0.138, 0.02, 0.179, 0.402, 0.526, 0.063, 0.198, 0.043, 0.005, 0.079, 0.006, 0.009, 0.0001, 0.009, 0.0001, 0.001, 0.0001, 8.563, 0.792, 1.691, 1.903, 8.594, 0.377, 2.885, 5.368, 3.902, 0.512, 0.598, 3.599, 1.506, 6.663, 4.174, 0.394, 0.032, 4.839, 4.581, 2.625, 1.233, 0.647, 0.279, 0.018, 6.112, 0.053, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.052, 0.016, 0.011, 0.007, 0.006, 0.002, 0.002, 0.033, 0.003, 0.008, 0.001, 0.001, 0.002, 0.004, 0.001, 0.003, 0.004, 0.002, 0.001, 0.008, 0.005, 0.001, 0.002, 0.002, 0.007, 0.032, 0.003, 0.002, 0.002, 0.002, 0.001, 0.001, 0.013, 0.034, 0.001, 0.003, 0.006, 0.002, 0.002, 0.259, 0.003, 0.024, 0.003, 0.005, 0.003, 0.021, 0.001, 0.003, 0.016, 0.006, 0.011, 0.021, 0.003, 0.006, 0.005, 0.006, 0.011, 0.008, 0.015, 0.005, 0.007, 0.005, 0.006, 0.004, 0.0001, 0.0001, 0.024, 0.446, 0.012, 0.021, 0.0001, 0.001, 0.0001, 0.006, 0.003, 0.004, 0.002, 0.0001, 0.012, 0.007, 0.044, 0.019, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.006, 0.05, 0.002, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ha\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.755, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.253, 0.006, 0.093, 0.001, 0.0001, 0.003, 0.001, 0.233, 0.264, 0.267, 0.0001, 0.001, 0.745, 0.202, 0.904, 0.054, 0.25, 0.351, 0.185, 0.101, 0.092, 0.11, 0.102, 0.101, 0.107, 0.226, 0.077, 0.015, 0.009, 0.002, 0.009, 0.006, 0.001, 0.703, 0.295, 0.111, 0.155, 0.055, 0.098, 0.113, 0.13, 0.318, 0.133, 0.225, 0.088, 0.271, 0.163, 0.048, 0.074, 0.005, 0.115, 0.268, 0.173, 0.05, 0.018, 0.079, 0.003, 0.091, 0.042, 0.023, 0.0001, 0.025, 0.0001, 0.021, 0.001, 18.747, 1.651, 0.919, 2.906, 2.679, 0.906, 1.302, 1.831, 6.455, 0.467, 3.514, 2.109, 2.474, 6.749, 1.839, 0.213, 0.023, 4.031, 3.401, 2.21, 3.388, 0.067, 1.617, 0.015, 2.266, 0.447, 0.001, 0.001, 0.003, 0.002, 0.0001, 0.116, 0.007, 0.003, 0.001, 0.009, 0.005, 0.003, 0.003, 0.007, 0.002, 0.01, 0.001, 0.001, 0.0001, 0.003, 0.001, 0.002, 0.002, 0.001, 0.029, 0.002, 0.001, 0.0001, 0.094, 0.018, 0.242, 0.0001, 0.001, 0.01, 0.009, 0.001, 0.004, 0.02, 0.005, 0.002, 0.006, 0.0001, 0.001, 0.003, 0.015, 0.004, 0.013, 0.004, 0.002, 0.002, 0.004, 0.002, 0.003, 0.004, 0.011, 0.001, 0.005, 0.011, 0.003, 0.002, 0.003, 0.002, 0.004, 0.002, 0.001, 0.003, 0.003, 0.0001, 0.002, 0.0001, 0.0001, 0.03, 0.04, 0.008, 0.004, 0.18, 0.0001, 0.0001, 0.118, 0.001, 0.004, 0.0001, 0.0001, 0.001, 0.001, 0.011, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.044, 0.043, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.115, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hak\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.757, 0.001, 0.06, 0.0001, 0.0001, 0.014, 0.0001, 0.007, 0.281, 0.281, 0.0001, 0.001, 0.836, 6.558, 0.681, 0.018, 0.337, 0.407, 0.278, 0.148, 0.134, 0.137, 0.13, 0.123, 0.136, 0.173, 0.065, 0.014, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.079, 0.057, 0.378, 0.035, 0.025, 0.133, 0.042, 0.182, 0.024, 0.017, 0.335, 0.169, 0.185, 0.174, 0.026, 0.169, 0.016, 0.027, 0.334, 0.366, 0.012, 0.069, 0.025, 0.002, 0.166, 0.009, 0.007, 0.0001, 0.007, 0.0001, 0.003, 0.0001, 1.796, 0.086, 1.609, 0.16, 2.657, 0.577, 2.978, 5.312, 4.077, 0.022, 2.986, 0.978, 0.836, 6.046, 1.214, 0.924, 0.006, 0.355, 1.925, 2.85, 1.719, 0.417, 0.063, 0.011, 1.033, 0.05, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.624, 0.347, 0.489, 0.127, 0.115, 0.217, 0.079, 0.128, 0.198, 0.185, 0.153, 0.188, 0.325, 0.981, 0.111, 0.161, 0.125, 0.071, 0.062, 0.072, 0.143, 0.099, 0.164, 0.124, 0.154, 0.164, 0.132, 0.118, 0.284, 0.116, 0.086, 0.138, 0.587, 0.284, 0.798, 0.114, 0.117, 0.11, 0.089, 0.096, 0.66, 0.449, 0.294, 0.11, 0.804, 0.308, 1.169, 0.118, 0.192, 0.187, 0.596, 1.486, 0.608, 0.076, 0.115, 0.115, 0.317, 1.436, 0.679, 1.022, 0.36, 0.128, 0.129, 0.134, 0.0001, 0.0001, 0.018, 7.409, 0.013, 0.036, 0.003, 0.005, 0.0001, 0.003, 0.001, 0.002, 1.194, 0.0001, 0.01, 0.005, 0.045, 0.02, 0.001, 0.001, 0.0001, 0.0001, 0.003, 0.013, 0.006, 0.004, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 1.011, 0.064, 0.254, 0.448, 1.333, 0.785, 0.602, 0.451, 0.439, 0.002, 0.004, 0.008, 0.003, 0.0001, 0.269, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"haw\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.221, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.294, 0.012, 0.203, 0.0001, 0.0001, 0.0001, 0.001, 0.132, 0.34, 0.352, 0.0001, 0.001, 1.505, 0.111, 0.979, 0.007, 0.17, 0.218, 0.129, 0.06, 0.059, 0.065, 0.059, 0.085, 0.093, 0.096, 0.074, 0.017, 0.01, 0.0001, 0.01, 0.007, 0.0001, 0.393, 0.447, 0.582, 0.062, 0.097, 0.065, 0.079, 0.798, 0.153, 0.05, 0.341, 0.594, 0.369, 0.112, 0.254, 0.296, 0.019, 0.112, 0.703, 0.122, 0.065, 0.176, 0.058, 0.005, 0.01, 0.09, 0.005, 0.0001, 0.006, 0.0001, 0.003, 0.006, 12.798, 0.268, 0.355, 0.813, 5.652, 0.089, 0.569, 1.324, 6.125, 0.081, 4.131, 4.145, 2.483, 4.121, 5.223, 1.895, 0.028, 1.627, 1.407, 0.928, 3.376, 0.134, 0.71, 0.014, 0.137, 0.178, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 1.411, 1.393, 0.012, 0.01, 0.008, 0.004, 0.003, 0.003, 0.01, 0.001, 0.006, 0.004, 0.027, 0.239, 0.001, 0.004, 0.011, 0.006, 0.002, 0.111, 0.01, 0.006, 0.002, 0.004, 1.323, 0.019, 0.006, 0.004, 0.007, 0.006, 0.005, 0.004, 0.006, 0.059, 0.005, 0.006, 0.006, 0.004, 0.001, 0.013, 0.01, 0.035, 0.014, 0.268, 0.004, 0.047, 0.003, 0.004, 0.012, 0.061, 0.008, 0.113, 0.006, 0.007, 0.004, 0.005, 0.011, 0.005, 0.014, 1.288, 0.011, 0.01, 0.006, 0.004, 0.0001, 0.0001, 0.011, 0.331, 1.585, 0.461, 0.0001, 0.008, 0.0001, 0.011, 1.285, 0.01, 0.001, 0.0001, 0.031, 0.013, 0.031, 0.011, 0.001, 0.0001, 0.0001, 0.0001, 0.004, 0.043, 0.02, 0.017, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.013, 1.362, 0.0001, 0.001, 0.006, 0.003, 0.002, 0.001, 0.001, 0.002, 0.007, 0.008, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hif\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.441, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.1, 0.004, 0.114, 0.0001, 0.001, 0.009, 0.004, 0.035, 0.174, 0.173, 0.001, 0.001, 0.931, 0.131, 1.205, 0.021, 0.405, 0.564, 0.33, 0.156, 0.134, 0.134, 0.137, 0.132, 0.161, 0.312, 0.075, 0.009, 0.003, 0.005, 0.003, 0.001, 0.0001, 0.456, 0.322, 0.355, 0.197, 0.18, 0.293, 0.19, 0.17, 0.372, 0.16, 0.181, 0.207, 0.307, 0.247, 0.078, 0.33, 0.012, 0.201, 0.529, 0.239, 0.168, 0.085, 0.095, 0.004, 0.174, 0.017, 0.016, 0.0001, 0.016, 0.0001, 0.019, 0.0001, 12.241, 1.338, 1.486, 1.704, 7.791, 0.593, 1.202, 3.829, 6.515, 0.754, 3.146, 2.684, 2.468, 4.596, 2.829, 0.958, 0.04, 4.362, 3.289, 3.315, 2.328, 0.443, 0.421, 0.04, 0.804, 0.089, 0.0001, 0.003, 0.001, 0.0001, 0.0001, 0.026, 0.089, 0.009, 0.002, 0.003, 0.005, 0.002, 0.014, 0.008, 0.001, 0.001, 0.004, 0.002, 0.02, 0.003, 0.003, 0.003, 0.002, 0.001, 0.042, 0.003, 0.007, 0.001, 0.003, 0.002, 0.005, 0.002, 0.011, 0.004, 0.001, 0.001, 0.013, 0.018, 0.009, 0.001, 0.005, 0.072, 0.024, 0.007, 0.009, 0.007, 0.012, 0.004, 0.029, 0.003, 0.013, 0.009, 0.007, 0.017, 0.022, 0.012, 0.006, 0.004, 0.005, 0.014, 0.002, 0.01, 0.032, 0.005, 0.004, 0.012, 0.003, 0.011, 0.006, 0.0001, 0.0001, 0.028, 0.074, 0.148, 0.035, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.005, 0.003, 0.0001, 0.004, 0.001, 0.016, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.009, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.105, 0.033, 0.022, 0.002, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ho\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.445, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.244, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.681, 0.84, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.042, 0.0001, 0.0001, 0.0001, 0.84, 1.681, 0.0001, 0.84, 0.0001, 0.0001, 1.681, 1.681, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.966, 0.84, 0.0001, 0.0001, 7.563, 0.0001, 0.84, 0.0001, 5.042, 0.0001, 0.0001, 2.521, 1.681, 5.882, 12.605, 1.681, 0.0001, 3.361, 1.681, 1.681, 0.0001, 1.681, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hsb\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.885, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.708, 0.001, 0.633, 0.0001, 0.0001, 0.02, 0.001, 0.006, 0.349, 0.351, 0.019, 0.001, 0.617, 0.09, 1.156, 0.027, 0.335, 0.549, 0.233, 0.161, 0.153, 0.173, 0.141, 0.134, 0.184, 0.278, 0.061, 0.039, 0.002, 0.004, 0.002, 0.001, 0.0001, 0.124, 0.184, 0.091, 0.139, 0.058, 0.04, 0.059, 0.112, 0.043, 0.103, 0.189, 0.142, 0.204, 0.143, 0.038, 0.227, 0.002, 0.151, 0.281, 0.088, 0.037, 0.024, 0.263, 0.004, 0.003, 0.079, 0.006, 0.0001, 0.006, 0.0001, 0.001, 0.0001, 6.697, 1.107, 1.636, 2.081, 6.467, 0.188, 0.301, 1.756, 3.527, 3.654, 2.787, 1.99, 1.872, 3.895, 5.864, 1.456, 0.004, 3.313, 3.645, 2.804, 2.176, 0.063, 3.44, 0.018, 1.507, 1.152, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.124, 0.059, 0.706, 0.009, 0.065, 0.003, 0.009, 0.741, 0.003, 0.001, 0.002, 0.003, 0.046, 0.476, 0.001, 0.004, 0.004, 0.003, 0.003, 0.038, 0.003, 0.003, 0.002, 0.005, 0.005, 0.336, 0.005, 1.33, 0.03, 0.004, 0.03, 0.009, 0.055, 0.752, 0.003, 0.003, 0.008, 0.003, 0.002, 0.003, 0.003, 0.008, 0.001, 0.003, 0.001, 0.011, 0.001, 0.003, 0.025, 0.008, 0.015, 0.5, 0.006, 0.013, 0.014, 0.003, 0.014, 0.005, 0.495, 0.011, 0.018, 0.033, 0.697, 0.004, 0.0001, 0.0001, 0.023, 0.573, 2.597, 3.085, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.0001, 0.001, 0.0001, 0.01, 0.005, 0.144, 0.055, 0.001, 0.001, 0.001, 0.011, 0.004, 0.015, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.004, 0.112, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ht\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.728, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.82, 0.005, 0.108, 0.0001, 0.0001, 0.002, 0.001, 0.044, 0.203, 0.204, 0.004, 0.0001, 1.177, 0.16, 1.277, 0.006, 1.128, 0.316, 0.385, 0.25, 0.229, 0.141, 0.146, 0.14, 0.135, 0.218, 0.285, 0.009, 0.002, 0.002, 0.002, 0.073, 0.0001, 0.308, 0.114, 0.31, 0.14, 0.272, 0.082, 0.233, 0.111, 0.459, 0.083, 0.546, 0.457, 0.33, 0.177, 0.124, 0.284, 0.004, 0.107, 0.215, 0.201, 0.017, 0.07, 0.083, 0.002, 0.054, 0.009, 0.003, 0.0001, 0.009, 0.003, 0.007, 0.0001, 8.338, 0.713, 0.405, 1.058, 6.922, 0.493, 1.121, 0.484, 5.37, 0.359, 1.684, 3.389, 1.726, 9.14, 4.981, 1.524, 0.032, 2.005, 4.201, 3.104, 1.485, 1.12, 1.14, 0.06, 3.565, 0.552, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.036, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.007, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.023, 0.0001, 0.0001, 0.002, 0.002, 0.0001, 0.0001, 0.04, 0.015, 0.008, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.815, 0.088, 0.005, 0.004, 0.0001, 0.019, 0.002, 0.004, 0.0001, 0.005, 0.398, 0.011, 0.003, 0.0001, 0.001, 0.0001, 0.001, 0.002, 0.004, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.04, 1.397, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.036, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hy\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.597, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.8, 0.0001, 0.032, 0.0001, 0.0001, 0.007, 0.0001, 0.004, 0.144, 0.144, 0.0001, 0.001, 0.586, 0.166, 0.08, 0.013, 0.201, 0.284, 0.165, 0.087, 0.08, 0.086, 0.077, 0.075, 0.09, 0.155, 0.113, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.01, 0.007, 0.01, 0.005, 0.005, 0.004, 0.004, 0.004, 0.018, 0.002, 0.002, 0.005, 0.008, 0.005, 0.004, 0.006, 0.001, 0.005, 0.011, 0.008, 0.002, 0.006, 0.004, 0.006, 0.001, 0.001, 0.002, 0.0001, 0.002, 0.0001, 0.003, 0.016, 0.046, 0.006, 0.015, 0.015, 0.053, 0.008, 0.009, 0.013, 0.038, 0.001, 0.005, 0.027, 0.014, 0.034, 0.04, 0.008, 0.001, 0.036, 0.026, 0.029, 0.018, 0.005, 0.004, 0.002, 0.008, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.414, 0.639, 1.93, 0.109, 0.557, 0.081, 0.132, 0.316, 0.027, 0.402, 0.05, 0.015, 0.042, 0.09, 0.048, 0.034, 0.002, 0.004, 0.002, 0.02, 0.024, 0.021, 0.028, 0.001, 0.0001, 0.001, 0.001, 0.003, 0.001, 0.123, 0.001, 0.0001, 0.042, 6.697, 0.434, 0.595, 0.616, 2.608, 0.304, 0.502, 0.621, 0.7, 0.114, 2.81, 0.856, 0.261, 0.333, 1.634, 0.66, 0.29, 0.576, 0.139, 1.799, 1.257, 4.145, 0.346, 3.356, 0.233, 0.437, 0.354, 0.321, 1.025, 1.123, 1.395, 0.0001, 0.0001, 0.223, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.029, 0.01, 0.0001, 0.0001, 0.652, 36.534, 7.186, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.026, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"hz\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 25.0, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.667, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 8.333, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ia\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.646, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.141, 0.002, 0.329, 0.0001, 0.0001, 0.008, 0.001, 0.021, 0.18, 0.18, 0.001, 0.003, 1.016, 0.166, 0.808, 0.013, 0.184, 0.275, 0.136, 0.08, 0.078, 0.085, 0.074, 0.075, 0.088, 0.135, 0.073, 0.032, 0.002, 0.003, 0.002, 0.002, 0.0001, 0.226, 0.141, 0.296, 0.096, 0.165, 0.073, 0.096, 0.067, 0.304, 0.056, 0.03, 0.329, 0.174, 0.087, 0.056, 0.186, 0.016, 0.106, 0.253, 0.115, 0.079, 0.078, 0.031, 0.017, 0.009, 0.016, 0.018, 0.0001, 0.018, 0.0001, 0.006, 0.0001, 7.783, 0.726, 2.95, 2.701, 11.264, 0.558, 0.916, 0.687, 6.773, 0.133, 0.102, 4.659, 2.226, 5.924, 5.837, 2.221, 0.5, 4.607, 4.777, 5.188, 3.143, 1.186, 0.054, 0.19, 0.205, 0.091, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.033, 0.014, 0.005, 0.003, 0.003, 0.002, 0.001, 0.002, 0.002, 0.002, 0.001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.002, 0.002, 0.001, 0.01, 0.005, 0.001, 0.001, 0.002, 0.003, 0.007, 0.001, 0.002, 0.005, 0.005, 0.001, 0.001, 0.01, 0.02, 0.003, 0.006, 0.005, 0.003, 0.002, 0.005, 0.004, 0.021, 0.003, 0.011, 0.003, 0.018, 0.002, 0.002, 0.005, 0.011, 0.005, 0.019, 0.002, 0.002, 0.005, 0.002, 0.004, 0.005, 0.009, 0.012, 0.006, 0.004, 0.003, 0.005, 0.0001, 0.0001, 0.026, 0.115, 0.014, 0.006, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.019, 0.01, 0.009, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.005, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.009, 0.031, 0.001, 0.001, 0.004, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ie\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.521, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.809, 0.001, 0.247, 0.0001, 0.0001, 0.004, 0.006, 0.019, 0.181, 0.18, 0.001, 0.0001, 0.349, 1.199, 1.232, 0.019, 0.563, 0.838, 0.612, 0.225, 0.226, 0.24, 0.207, 0.206, 0.219, 0.382, 0.048, 0.005, 0.006, 0.001, 0.006, 0.0001, 0.0001, 0.309, 0.238, 0.27, 0.166, 0.167, 0.136, 0.184, 0.184, 0.432, 0.068, 0.108, 0.44, 0.245, 0.141, 0.106, 0.224, 0.013, 0.149, 0.37, 0.155, 0.122, 0.083, 0.052, 0.007, 0.021, 0.046, 0.016, 0.0001, 0.017, 0.0001, 0.0001, 0.0001, 6.871, 0.75, 2.348, 2.768, 8.937, 0.584, 0.855, 0.832, 7.829, 0.174, 0.338, 4.504, 1.934, 5.948, 3.473, 1.445, 0.281, 4.186, 4.435, 5.077, 2.881, 0.926, 0.103, 0.2, 0.235, 0.149, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.011, 0.008, 0.006, 0.012, 0.004, 0.002, 0.001, 0.038, 0.001, 0.002, 0.0001, 0.001, 0.004, 0.016, 0.001, 0.001, 0.004, 0.003, 0.001, 0.004, 0.002, 0.001, 0.002, 0.003, 0.002, 0.005, 0.002, 0.003, 0.002, 0.002, 0.002, 0.01, 0.018, 0.107, 0.002, 0.003, 0.013, 0.003, 0.002, 0.004, 0.007, 0.34, 0.002, 0.007, 0.001, 0.079, 0.001, 0.001, 0.011, 0.009, 0.004, 0.067, 0.003, 0.005, 0.02, 0.001, 0.012, 0.001, 0.025, 0.006, 0.034, 0.009, 0.011, 0.002, 0.0001, 0.0001, 0.012, 0.714, 0.064, 0.047, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.007, 0.004, 0.047, 0.017, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.01, 0.006, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ig\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.656, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.908, 0.0001, 0.773, 0.0001, 0.001, 0.002, 0.004, 0.323, 0.322, 0.321, 0.001, 0.0001, 0.801, 0.348, 0.989, 0.009, 0.349, 0.514, 0.434, 0.211, 0.141, 0.149, 0.14, 0.138, 0.136, 0.236, 0.05, 0.04, 0.002, 0.002, 0.002, 0.002, 0.001, 0.437, 0.139, 0.167, 0.124, 0.161, 0.093, 0.162, 0.08, 0.189, 0.162, 0.069, 0.102, 0.246, 0.453, 0.257, 0.111, 0.006, 0.081, 0.231, 0.102, 0.09, 0.023, 0.104, 0.006, 0.033, 0.007, 0.018, 0.0001, 0.018, 0.0001, 0.003, 0.003, 8.644, 2.249, 1.219, 1.734, 6.313, 0.761, 1.664, 1.979, 4.558, 0.257, 2.436, 1.453, 1.97, 6.1, 4.008, 0.966, 0.02, 3.332, 1.739, 2.408, 2.889, 0.225, 1.517, 0.034, 1.16, 0.258, 0.0001, 0.007, 0.001, 0.0001, 0.0001, 0.13, 0.024, 0.004, 0.004, 0.007, 0.006, 0.001, 0.002, 0.001, 0.004, 0.004, 0.623, 0.118, 0.962, 0.001, 0.0001, 0.002, 0.001, 0.004, 0.022, 0.005, 0.001, 0.031, 0.002, 0.001, 0.082, 0.001, 0.002, 0.004, 0.003, 0.0001, 0.0001, 0.34, 0.233, 0.001, 0.001, 0.012, 1.142, 0.001, 0.003, 0.105, 0.2, 0.002, 0.014, 0.036, 0.183, 0.028, 0.019, 0.004, 0.004, 0.029, 0.095, 0.002, 0.001, 0.009, 0.001, 0.003, 0.143, 0.201, 2.836, 0.03, 0.001, 0.002, 0.002, 0.0001, 0.0001, 0.146, 1.356, 0.009, 0.021, 0.0001, 0.014, 0.039, 0.003, 0.0001, 0.001, 0.029, 0.0001, 0.005, 0.001, 0.008, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 2.927, 0.109, 0.003, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ii\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.208, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.146, 0.0001, 1.029, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.343, 0.343, 0.0001, 0.0001, 0.0001, 0.0001, 0.686, 0.0001, 4.803, 0.172, 2.401, 0.0001, 0.0001, 0.172, 0.343, 0.686, 0.686, 0.343, 0.0001, 0.0001, 0.0001, 0.0001, 0.515, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.172, 0.0001, 0.172, 0.0001, 0.0001, 0.0001, 0.172, 0.858, 0.0001, 0.343, 0.343, 0.0001, 0.0001, 0.0001, 0.172, 0.0001, 0.0001, 0.0001, 0.343, 0.343, 0.343, 0.172, 0.0001, 0.172, 0.343, 0.0001, 0.515, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.172, 0.0001, 0.172, 0.0001, 0.0001, 2.573, 1.887, 1.544, 0.858, 2.058, 0.343, 1.715, 0.343, 0.343, 0.0001, 0.172, 1.201, 1.887, 1.029, 1.372, 0.172, 0.343, 1.029, 0.686, 0.172, 0.172, 0.172, 0.686, 0.858, 0.686, 0.172, 0.343, 0.0001, 0.515, 0.172, 0.343, 0.686, 0.343, 0.172, 0.0001, 0.0001, 0.343, 0.0001, 0.172, 1.544, 0.172, 0.343, 0.686, 0.858, 0.686, 0.858, 0.343, 0.686, 0.172, 0.343, 0.343, 0.515, 2.744, 0.172, 0.0001, 0.343, 0.858, 2.916, 0.343, 0.686, 0.343, 0.0001, 0.343, 0.172, 0.0001, 0.0001, 1.372, 0.0001, 0.172, 0.172, 0.0001, 0.0001, 0.0001, 0.172, 0.172, 0.0001, 0.515, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.515, 1.029, 4.631, 1.029, 0.686, 0.0001, 0.858, 10.635, 0.0001, 0.0001, 0.0001, 0.0001, 0.343, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ik\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.089, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.203, 0.0001, 1.053, 0.0001, 0.0001, 0.002, 0.004, 0.019, 1.043, 1.038, 0.0001, 0.004, 0.489, 0.212, 1.246, 0.04, 0.17, 0.264, 0.162, 0.084, 0.065, 0.055, 0.132, 0.065, 0.076, 0.124, 0.136, 0.025, 0.013, 0.002, 0.002, 0.0001, 0.0001, 0.824, 0.109, 0.155, 0.065, 0.052, 0.025, 0.048, 0.076, 0.634, 0.073, 0.409, 0.111, 0.352, 0.545, 0.069, 0.285, 0.321, 0.155, 0.436, 0.755, 0.409, 0.076, 0.046, 0.008, 0.044, 0.002, 0.01, 0.0001, 0.015, 0.0001, 0.008, 0.0001, 9.867, 0.308, 1.051, 0.799, 2.404, 0.327, 1.605, 1.011, 7.743, 0.063, 2.113, 2.725, 1.552, 3.495, 1.668, 1.101, 3.682, 2.526, 3.015, 4.496, 6.747, 0.801, 0.229, 0.065, 0.707, 0.103, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.097, 0.073, 0.183, 0.023, 0.006, 0.031, 0.013, 0.103, 0.05, 0.008, 0.004, 0.952, 0.019, 0.055, 0.002, 0.023, 0.013, 0.008, 0.0001, 0.002, 0.008, 0.067, 0.013, 0.015, 0.004, 0.002, 0.04, 0.01, 0.017, 0.01, 0.002, 0.019, 0.013, 0.843, 0.013, 0.029, 1.206, 0.281, 0.025, 0.006, 0.067, 0.006, 0.055, 0.004, 0.038, 0.017, 0.078, 0.034, 0.172, 0.862, 0.059, 0.01, 0.01, 0.067, 0.008, 0.187, 0.269, 0.134, 0.055, 0.021, 0.038, 0.019, 0.189, 0.046, 0.0001, 0.0001, 0.008, 0.944, 0.835, 1.051, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.006, 0.008, 0.0001, 0.025, 0.023, 0.42, 0.185, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.408, 0.191, 0.013, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ilo\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.301, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.793, 0.111, 0.271, 0.0001, 0.001, 0.008, 0.001, 0.018, 0.172, 0.172, 0.003, 0.003, 0.83, 0.304, 0.649, 0.008, 0.21, 0.325, 0.155, 0.097, 0.09, 0.094, 0.083, 0.085, 0.1, 0.166, 0.048, 0.03, 0.005, 0.001, 0.005, 0.001, 0.0001, 0.349, 0.225, 0.111, 0.217, 0.099, 0.09, 0.118, 0.1, 0.241, 0.035, 0.175, 0.118, 0.232, 0.163, 0.054, 0.279, 0.009, 0.088, 0.233, 0.461, 0.072, 0.035, 0.029, 0.006, 0.014, 0.012, 0.036, 0.0001, 0.036, 0.0001, 0.001, 0.0001, 16.461, 1.586, 0.189, 2.856, 3.734, 0.046, 3.664, 0.306, 10.008, 0.024, 3.303, 2.245, 1.909, 7.066, 3.393, 2.095, 0.012, 2.409, 3.066, 6.078, 2.307, 0.067, 0.549, 0.014, 1.361, 0.048, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.043, 0.006, 0.003, 0.001, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.004, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.033, 0.004, 0.001, 0.001, 0.001, 0.001, 0.004, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.015, 0.007, 0.001, 0.001, 0.003, 0.002, 0.001, 0.003, 0.002, 0.009, 0.002, 0.004, 0.001, 0.006, 0.002, 0.001, 0.013, 0.007, 0.003, 0.007, 0.004, 0.001, 0.003, 0.001, 0.004, 0.002, 0.004, 0.002, 0.003, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.027, 0.049, 0.008, 0.009, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.007, 0.004, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.004, 0.043, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"io\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.24, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.878, 0.001, 0.226, 0.0001, 0.145, 0.683, 0.001, 0.155, 0.19, 0.19, 0.002, 0.001, 1.502, 0.399, 2.049, 0.127, 1.123, 1.116, 0.845, 0.565, 0.593, 0.586, 0.516, 0.395, 0.578, 0.496, 0.056, 0.039, 0.008, 0.001, 0.008, 0.001, 0.0001, 0.262, 0.119, 0.082, 0.063, 0.203, 0.065, 0.07, 0.089, 0.077, 0.042, 0.131, 0.387, 0.144, 0.086, 0.052, 0.185, 0.006, 0.067, 0.238, 0.064, 0.127, 0.06, 0.027, 0.002, 0.018, 0.007, 0.008, 0.0001, 0.008, 0.0001, 0.001, 0.0001, 8.848, 0.82, 0.665, 2.487, 7.044, 0.592, 0.8, 0.82, 7.741, 0.211, 1.575, 3.65, 2.435, 4.587, 5.58, 1.431, 0.273, 4.484, 4.553, 2.971, 2.4, 1.482, 0.052, 0.098, 0.514, 0.499, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.011, 0.008, 0.004, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.003, 0.002, 0.001, 0.0001, 0.002, 0.001, 0.004, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.096, 0.01, 0.001, 0.003, 0.003, 0.001, 0.001, 0.003, 0.002, 0.015, 0.001, 0.004, 0.001, 0.008, 0.001, 0.001, 0.005, 0.003, 0.253, 0.006, 0.002, 0.002, 0.003, 0.001, 0.003, 0.002, 0.004, 0.005, 0.003, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.356, 0.056, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.005, 0.003, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.01, 0.006, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"is\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.97, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.586, 0.001, 0.206, 0.0001, 0.0001, 0.008, 0.002, 0.007, 0.124, 0.124, 0.0001, 0.001, 0.499, 0.123, 1.026, 0.011, 0.247, 0.371, 0.182, 0.092, 0.087, 0.097, 0.09, 0.091, 0.11, 0.206, 0.046, 0.01, 0.008, 0.003, 0.008, 0.001, 0.0001, 0.15, 0.156, 0.07, 0.071, 0.123, 0.128, 0.099, 0.219, 0.049, 0.069, 0.114, 0.102, 0.145, 0.086, 0.033, 0.076, 0.003, 0.091, 0.275, 0.089, 0.03, 0.081, 0.029, 0.009, 0.012, 0.006, 0.003, 0.0001, 0.003, 0.0001, 0.004, 0.0001, 6.834, 0.671, 0.132, 1.35, 4.383, 2.01, 2.575, 1.208, 5.351, 0.788, 2.14, 3.293, 2.549, 5.87, 1.807, 0.609, 0.005, 6.508, 4.164, 3.597, 3.384, 1.444, 0.036, 0.05, 0.678, 0.023, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.079, 0.071, 0.003, 0.002, 0.002, 0.001, 0.004, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.093, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.031, 0.002, 0.001, 0.01, 0.001, 0.001, 0.001, 0.008, 0.001, 0.029, 0.003, 0.162, 0.001, 0.007, 1.147, 0.001, 0.001, 0.003, 0.001, 0.559, 0.002, 0.002, 0.221, 0.001, 0.001, 0.001, 1.194, 0.001, 0.001, 2.653, 0.002, 0.003, 0.801, 0.002, 0.002, 0.588, 0.001, 0.004, 0.002, 0.419, 0.002, 0.005, 0.183, 0.658, 0.001, 0.0001, 0.0001, 0.014, 8.751, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.004, 0.011, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.079, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"iu\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.678, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.59, 0.002, 0.2, 0.001, 0.003, 0.002, 0.003, 0.015, 0.174, 0.173, 0.0001, 0.002, 0.361, 0.227, 0.729, 0.057, 0.148, 0.113, 0.088, 0.041, 0.055, 0.057, 0.038, 0.037, 0.041, 0.075, 0.086, 0.012, 0.034, 0.007, 0.037, 0.002, 0.001, 0.071, 0.015, 0.028, 0.016, 0.027, 0.021, 0.01, 0.09, 0.106, 0.011, 0.032, 0.025, 0.039, 0.045, 0.015, 0.038, 0.016, 0.009, 0.047, 0.052, 0.031, 0.008, 0.018, 0.002, 0.004, 0.0001, 0.042, 0.001, 0.038, 0.0001, 0.005, 0.0001, 3.23, 0.132, 0.269, 0.325, 0.963, 0.137, 0.72, 0.796, 3.179, 0.121, 0.846, 1.083, 0.73, 1.94, 0.65, 0.417, 0.773, 0.909, 0.645, 2.138, 2.24, 0.378, 0.125, 0.018, 0.352, 0.009, 0.063, 0.016, 0.064, 0.0001, 0.0001, 0.195, 0.104, 0.722, 2.277, 0.527, 2.845, 0.283, 0.577, 0.292, 0.16, 1.105, 0.322, 0.08, 0.107, 0.751, 0.183, 5.797, 4.461, 2.284, 5.133, 1.078, 2.99, 2.939, 0.631, 0.063, 0.2, 0.245, 0.066, 0.007, 0.045, 0.004, 0.005, 0.011, 0.073, 0.039, 0.009, 0.018, 0.411, 1.389, 0.092, 0.103, 0.04, 1.113, 0.086, 0.008, 0.439, 0.043, 0.681, 0.098, 0.475, 0.284, 0.202, 0.231, 0.064, 0.012, 0.003, 0.129, 0.126, 0.009, 0.223, 0.007, 0.015, 0.041, 0.125, 0.0001, 0.0001, 0.1, 0.024, 0.004, 0.006, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.004, 0.002, 0.0001, 0.0001, 0.001, 0.131, 0.044, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.009, 0.014, 0.007, 0.001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 21.14, 0.181, 0.003, 0.004, 0.004, 0.004, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"jam\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.114, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.204, 0.0001, 0.437, 0.0001, 0.002, 0.015, 0.001, 0.02, 0.246, 0.245, 0.0001, 0.001, 1.286, 0.238, 0.848, 0.011, 0.225, 0.262, 0.144, 0.074, 0.072, 0.083, 0.075, 0.089, 0.083, 0.132, 0.047, 0.038, 0.014, 0.002, 0.014, 0.002, 0.0001, 0.33, 0.18, 0.107, 0.273, 0.095, 0.097, 0.106, 0.027, 0.375, 0.206, 0.243, 0.122, 0.218, 0.135, 0.052, 0.177, 0.004, 0.132, 0.376, 0.091, 0.038, 0.042, 0.116, 0.003, 0.09, 0.009, 0.033, 0.0001, 0.032, 0.0001, 0.0001, 0.0001, 11.751, 1.124, 0.777, 3.113, 4.403, 1.279, 0.882, 3.07, 11.71, 0.554, 2.318, 3.164, 2.271, 5.696, 3.101, 1.655, 0.006, 3.113, 3.636, 3.486, 2.985, 0.575, 1.126, 0.207, 0.616, 0.737, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.054, 0.013, 0.006, 0.006, 0.011, 0.005, 0.006, 0.002, 0.011, 0.004, 0.003, 0.002, 0.005, 0.004, 0.004, 0.001, 0.005, 0.003, 0.003, 0.026, 0.009, 0.002, 0.001, 0.003, 0.001, 0.005, 0.001, 0.003, 0.008, 0.008, 0.001, 0.0001, 0.066, 0.009, 0.001, 0.004, 0.01, 0.005, 0.003, 0.008, 0.009, 0.009, 0.007, 0.005, 0.005, 0.009, 0.003, 0.008, 0.005, 0.014, 0.004, 0.031, 0.002, 0.005, 0.002, 0.003, 0.006, 0.009, 0.008, 0.006, 0.007, 0.007, 0.004, 0.009, 0.0001, 0.0001, 0.069, 0.057, 0.012, 0.006, 0.0001, 0.001, 0.0001, 0.011, 0.004, 0.01, 0.001, 0.0001, 0.049, 0.024, 0.009, 0.004, 0.0001, 0.0001, 0.0001, 0.007, 0.002, 0.004, 0.025, 0.021, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.017, 0.008, 0.051, 0.0001, 0.002, 0.004, 0.003, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"jbo\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 18.83, 0.0001, 0.137, 0.0001, 0.0001, 0.001, 0.0001, 3.634, 0.016, 0.016, 0.0001, 0.0001, 0.047, 0.014, 2.419, 0.008, 0.161, 0.318, 0.2, 0.098, 0.09, 0.087, 0.087, 0.089, 0.108, 0.172, 0.004, 0.001, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.035, 0.022, 0.028, 0.013, 0.017, 0.01, 0.012, 0.013, 0.015, 0.011, 0.012, 0.019, 0.022, 0.019, 0.015, 0.017, 0.001, 0.021, 0.029, 0.019, 0.006, 0.006, 0.008, 0.002, 0.002, 0.004, 0.002, 0.0001, 0.003, 0.0001, 0.003, 0.0001, 7.616, 1.669, 2.73, 1.665, 6.288, 0.906, 1.481, 0.055, 8.607, 1.326, 2.153, 5.868, 2.296, 4.062, 6.049, 1.389, 0.004, 2.945, 3.13, 2.492, 4.934, 0.679, 0.018, 0.589, 0.829, 0.701, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.01, 0.006, 0.006, 0.011, 0.005, 0.003, 0.004, 0.008, 0.003, 0.008, 0.003, 0.003, 0.002, 0.002, 0.002, 0.003, 0.002, 0.001, 0.003, 0.004, 0.004, 0.001, 0.002, 0.001, 0.006, 0.001, 0.002, 0.002, 0.002, 0.001, 0.002, 0.003, 0.006, 0.002, 0.003, 0.005, 0.003, 0.006, 0.013, 0.006, 0.012, 0.005, 0.003, 0.005, 0.008, 0.003, 0.005, 0.01, 0.01, 0.007, 0.008, 0.003, 0.008, 0.002, 0.003, 0.01, 0.005, 0.017, 0.007, 0.005, 0.005, 0.004, 0.003, 0.0001, 0.0001, 0.004, 0.032, 0.005, 0.003, 0.0001, 0.0001, 0.0001, 0.006, 0.004, 0.004, 0.002, 0.0001, 0.016, 0.008, 0.046, 0.017, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.007, 0.035, 0.026, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.02, 0.006, 0.007, 0.005, 0.001, 0.004, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"jv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.393, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.972, 0.002, 0.376, 0.0001, 0.001, 0.006, 0.002, 0.021, 0.174, 0.174, 0.0001, 0.002, 1.038, 0.248, 0.884, 0.027, 0.242, 0.269, 0.17, 0.077, 0.072, 0.083, 0.071, 0.073, 0.084, 0.157, 0.063, 0.014, 0.005, 0.004, 0.005, 0.001, 0.0001, 0.298, 0.282, 0.139, 0.175, 0.052, 0.071, 0.122, 0.1, 0.279, 0.193, 0.491, 0.137, 0.261, 0.155, 0.054, 0.404, 0.008, 0.134, 0.426, 0.252, 0.063, 0.039, 0.13, 0.007, 0.04, 0.014, 0.024, 0.0001, 0.024, 0.0001, 0.001, 0.0001, 13.56, 1.271, 0.49, 2.178, 3.499, 0.191, 4.675, 1.646, 6.887, 0.617, 3.65, 2.625, 2.089, 9.349, 2.312, 1.888, 0.011, 3.362, 3.296, 3.086, 3.992, 0.185, 1.217, 0.022, 0.792, 0.054, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.032, 0.007, 0.005, 0.003, 0.005, 0.004, 0.003, 0.003, 0.005, 0.012, 0.002, 0.001, 0.001, 0.004, 0.005, 0.002, 0.003, 0.002, 0.003, 0.01, 0.003, 0.002, 0.001, 0.001, 0.003, 0.007, 0.001, 0.003, 0.006, 0.006, 0.001, 0.001, 0.039, 0.004, 0.002, 0.003, 0.006, 0.005, 0.004, 0.006, 0.531, 1.186, 0.005, 0.003, 0.002, 0.005, 0.002, 0.002, 0.007, 0.006, 0.005, 0.006, 0.002, 0.002, 0.002, 0.001, 0.008, 0.005, 0.003, 0.002, 0.004, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.048, 1.757, 0.01, 0.01, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.007, 0.003, 0.009, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.015, 0.024, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.005, 0.031, 0.001, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ka\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.399, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.467, 0.006, 0.05, 0.004, 0.0001, 0.005, 0.0001, 0.002, 0.08, 0.08, 0.0001, 0.001, 0.389, 0.146, 0.411, 0.004, 0.126, 0.165, 0.095, 0.049, 0.046, 0.05, 0.043, 0.044, 0.053, 0.091, 0.022, 0.014, 0.001, 0.004, 0.001, 0.001, 0.0001, 0.009, 0.008, 0.011, 0.006, 0.004, 0.005, 0.005, 0.004, 0.036, 0.001, 0.002, 0.005, 0.008, 0.005, 0.004, 0.007, 0.001, 0.005, 0.01, 0.01, 0.002, 0.01, 0.003, 0.012, 0.001, 0.001, 0.002, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.058, 0.009, 0.021, 0.031, 0.066, 0.012, 0.013, 0.018, 0.047, 0.001, 0.009, 0.032, 0.017, 0.042, 0.05, 0.013, 0.001, 0.044, 0.039, 0.037, 0.025, 0.006, 0.004, 0.003, 0.014, 0.002, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.133, 0.001, 0.001, 30.616, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.203, 1.072, 0.617, 1.27, 2.811, 0.901, 0.251, 0.727, 3.846, 0.492, 1.468, 1.522, 1.35, 1.641, 0.3, 0.022, 1.853, 2.198, 0.578, 0.828, 0.251, 0.28, 0.142, 0.172, 0.523, 0.1, 0.36, 0.118, 0.305, 0.039, 0.412, 0.073, 0.048, 0.001, 0.009, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.002, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.023, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.013, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 30.616, 0.133, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kaa\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.138, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.976, 0.002, 0.25, 0.0001, 0.0001, 0.021, 0.001, 3.483, 0.188, 0.189, 0.0001, 0.005, 0.857, 0.333, 1.07, 0.015, 0.261, 0.313, 0.191, 0.105, 0.096, 0.102, 0.088, 0.083, 0.102, 0.171, 0.072, 0.024, 0.027, 0.002, 0.027, 0.004, 0.0001, 0.278, 0.237, 0.043, 0.064, 0.083, 0.059, 0.068, 0.047, 0.074, 0.082, 0.125, 0.051, 0.155, 0.064, 0.155, 0.081, 0.145, 0.091, 0.217, 0.129, 0.07, 0.048, 0.02, 0.093, 0.029, 0.015, 0.006, 0.0001, 0.006, 0.0001, 0.001, 0.174, 10.672, 1.536, 0.082, 2.608, 4.846, 0.175, 1.712, 1.492, 6.03, 0.848, 1.882, 4.983, 2.176, 5.612, 2.458, 1.187, 1.824, 4.354, 3.602, 3.336, 1.517, 0.264, 0.926, 0.28, 1.912, 0.845, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.223, 0.008, 0.008, 0.008, 0.005, 0.003, 0.002, 0.003, 0.003, 0.001, 0.002, 0.004, 0.002, 0.002, 0.002, 0.003, 0.008, 0.001, 0.002, 0.024, 0.089, 0.003, 0.024, 0.001, 0.007, 0.086, 0.002, 0.002, 0.011, 0.008, 0.005, 0.018, 0.016, 0.006, 0.003, 0.007, 0.007, 0.002, 0.001, 0.006, 0.003, 0.004, 0.002, 0.012, 0.002, 0.006, 0.001, 0.009, 0.142, 4.113, 0.008, 0.007, 0.005, 0.049, 0.005, 0.002, 0.01, 0.003, 0.009, 0.024, 0.009, 0.012, 0.029, 0.003, 0.0001, 0.0001, 0.053, 0.053, 4.15, 0.014, 0.0001, 0.002, 0.0001, 0.001, 0.004, 0.0001, 0.0001, 0.0001, 0.009, 0.003, 0.235, 0.052, 0.004, 0.001, 0.001, 0.003, 0.0001, 0.002, 0.013, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.006, 0.216, 0.0001, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kab\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.63, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.911, 0.026, 0.321, 0.0001, 0.0001, 0.004, 0.001, 0.063, 0.372, 0.372, 0.003, 0.002, 1.155, 1.35, 0.936, 0.038, 0.266, 0.305, 0.202, 0.119, 0.101, 0.107, 0.102, 0.091, 0.121, 0.181, 0.18, 0.035, 0.01, 0.019, 0.017, 0.012, 0.0001, 0.467, 0.145, 0.091, 0.191, 0.038, 0.078, 0.046, 0.038, 0.257, 0.025, 0.056, 0.211, 0.226, 0.079, 0.026, 0.046, 0.014, 0.063, 0.175, 0.468, 0.109, 0.016, 0.087, 0.018, 0.161, 0.062, 0.014, 0.0001, 0.014, 0.0001, 0.076, 0.0001, 9.2, 0.865, 0.672, 3.845, 7.769, 0.911, 1.711, 0.292, 5.622, 0.162, 1.372, 2.877, 2.842, 6.403, 0.445, 0.148, 0.603, 3.37, 3.532, 4.882, 2.937, 0.09, 1.486, 0.181, 2.148, 0.956, 0.0001, 0.011, 0.0001, 0.0001, 0.0001, 0.079, 0.004, 0.004, 0.003, 0.012, 0.007, 0.008, 0.003, 0.006, 0.01, 0.01, 0.0001, 0.003, 0.341, 0.007, 0.006, 0.04, 0.002, 0.003, 0.156, 0.017, 0.001, 0.002, 0.001, 0.004, 0.031, 0.007, 0.42, 0.023, 0.016, 0.002, 0.009, 0.025, 0.004, 0.015, 1.13, 0.027, 0.269, 0.019, 0.099, 0.024, 0.093, 0.007, 0.018, 0.013, 0.276, 0.002, 0.011, 0.014, 0.01, 0.006, 0.129, 0.025, 0.113, 0.001, 0.005, 0.586, 0.529, 0.15, 0.021, 0.004, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.056, 0.149, 0.056, 0.01, 0.05, 0.088, 0.0001, 1.3, 0.001, 0.001, 0.003, 0.0001, 0.203, 0.001, 0.007, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.008, 0.063, 0.048, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 1.257, 0.143, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kbd\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.877, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.792, 0.0001, 0.075, 0.0001, 0.001, 0.011, 0.0001, 0.003, 0.141, 0.14, 0.0001, 0.003, 0.771, 0.205, 0.683, 0.004, 0.136, 0.165, 0.088, 0.057, 0.049, 0.062, 0.042, 0.043, 0.05, 0.078, 0.043, 0.017, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.002, 0.001, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.304, 0.0001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.003, 0.0001, 0.001, 0.002, 0.001, 0.001, 0.008, 0.001, 0.014, 0.0001, 0.0001, 0.008, 0.0001, 0.008, 0.0001, 0.0001, 0.0001, 0.03, 0.003, 0.008, 0.007, 0.025, 0.002, 0.005, 0.007, 0.023, 0.001, 0.003, 0.195, 0.01, 0.018, 0.016, 0.007, 0.001, 0.016, 0.015, 0.015, 0.012, 0.002, 0.002, 0.001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.555, 0.735, 0.993, 2.937, 0.251, 2.403, 0.222, 0.078, 0.401, 1.056, 2.895, 2.991, 0.659, 6.305, 0.005, 0.318, 0.209, 0.078, 0.019, 0.048, 0.196, 0.035, 0.012, 0.035, 0.104, 0.003, 0.214, 0.042, 0.072, 0.044, 0.009, 0.068, 0.044, 0.057, 0.06, 0.06, 0.029, 0.076, 0.012, 0.013, 0.04, 0.033, 0.001, 0.031, 0.001, 0.015, 0.002, 0.026, 2.293, 0.626, 0.124, 1.164, 0.956, 0.819, 0.63, 0.861, 1.412, 0.263, 1.894, 1.024, 2.202, 1.111, 0.541, 0.996, 0.0001, 0.0001, 0.089, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.007, 0.001, 0.005, 0.002, 18.347, 24.31, 0.001, 1.322, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.003, 0.144, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kbp\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.616, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.57, 0.002, 0.071, 0.0001, 0.0001, 0.002, 0.0001, 0.006, 0.116, 0.115, 0.0001, 0.002, 0.667, 0.749, 0.841, 0.003, 0.17, 0.215, 0.112, 0.059, 0.061, 0.065, 0.059, 0.058, 0.083, 0.103, 0.043, 0.02, 0.0001, 0.014, 0.0001, 0.002, 0.0001, 0.165, 0.045, 0.077, 0.029, 0.079, 0.067, 0.032, 0.069, 0.044, 0.029, 0.18, 0.072, 0.11, 0.068, 0.032, 0.297, 0.002, 0.044, 0.127, 0.122, 0.017, 0.016, 0.035, 0.003, 0.036, 0.006, 0.025, 0.0001, 0.025, 0.0001, 0.0001, 0.0001, 8.914, 0.693, 0.409, 0.775, 2.26, 0.236, 0.534, 0.509, 1.986, 0.346, 2.598, 2.297, 1.559, 3.608, 1.061, 1.995, 0.02, 0.616, 1.735, 2.888, 0.861, 0.082, 0.914, 0.015, 2.587, 0.783, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.019, 0.022, 0.002, 0.003, 0.114, 0.001, 0.002, 0.001, 0.002, 0.067, 2.039, 2.33, 0.002, 0.002, 0.001, 0.0001, 0.179, 0.013, 0.001, 0.001, 2.735, 0.001, 1.381, 0.0001, 0.001, 0.007, 0.0001, 5.08, 0.004, 0.003, 0.0001, 0.0001, 0.004, 0.008, 0.005, 1.151, 0.003, 0.001, 0.001, 0.004, 0.013, 4.529, 0.002, 0.019, 0.001, 0.004, 0.003, 0.007, 0.003, 0.207, 0.003, 0.002, 0.003, 0.002, 0.003, 0.001, 0.006, 0.003, 0.002, 0.02, 0.003, 0.002, 0.002, 0.006, 0.0001, 0.0001, 0.035, 0.33, 0.004, 1.069, 0.247, 0.001, 0.0001, 14.815, 3.312, 0.001, 0.128, 0.0001, 0.011, 0.006, 0.009, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.007, 0.013, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kg\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.239, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.886, 0.007, 0.336, 0.0001, 0.0001, 0.012, 0.002, 0.078, 0.324, 0.324, 0.001, 0.002, 0.656, 0.558, 1.416, 0.029, 0.129, 0.236, 0.138, 0.114, 0.084, 0.072, 0.081, 0.089, 0.086, 0.136, 0.151, 0.002, 0.018, 0.006, 0.018, 0.004, 0.0001, 0.545, 0.514, 0.255, 0.205, 0.262, 0.18, 0.131, 0.1, 0.152, 0.059, 0.708, 0.293, 0.752, 0.533, 0.094, 0.176, 0.01, 0.241, 0.354, 0.199, 0.108, 0.106, 0.05, 0.006, 0.156, 0.054, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.001, 12.562, 2.277, 0.331, 1.547, 5.722, 0.691, 1.741, 0.399, 6.386, 0.118, 3.863, 3.599, 2.582, 6.478, 2.883, 0.88, 0.049, 1.355, 2.305, 2.139, 4.827, 0.445, 0.58, 0.051, 3.145, 1.337, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.071, 0.012, 0.003, 0.002, 0.01, 0.004, 0.003, 0.001, 0.004, 0.005, 0.003, 0.002, 0.002, 0.003, 0.001, 0.001, 0.006, 0.001, 0.001, 0.062, 0.006, 0.002, 0.0001, 0.001, 0.001, 0.009, 0.002, 0.004, 0.002, 0.001, 0.001, 0.001, 0.037, 0.129, 0.127, 0.006, 0.006, 0.002, 0.017, 0.018, 0.037, 0.077, 0.018, 0.027, 0.002, 0.034, 0.066, 0.007, 0.011, 0.023, 0.005, 0.025, 0.156, 0.002, 0.001, 0.003, 0.003, 0.002, 0.014, 0.066, 0.013, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.038, 0.798, 0.007, 0.01, 0.0001, 0.0001, 0.0001, 0.013, 0.006, 0.004, 0.0001, 0.001, 0.012, 0.004, 0.009, 0.006, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.021, 0.018, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.016, 0.007, 0.069, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.005, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ki\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.157, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.033, 0.001, 0.1, 0.001, 0.0001, 0.002, 0.005, 0.051, 0.116, 0.115, 0.0001, 0.0001, 0.384, 0.122, 1.505, 0.04, 0.182, 0.215, 0.151, 0.09, 0.071, 0.091, 0.067, 0.064, 0.059, 0.089, 0.065, 0.003, 0.006, 0.002, 0.008, 0.01, 0.0001, 0.273, 0.233, 0.763, 0.125, 0.089, 0.072, 0.168, 0.139, 0.145, 0.105, 0.364, 0.123, 0.376, 0.291, 0.066, 0.138, 0.046, 0.111, 0.252, 0.278, 0.132, 0.045, 0.093, 0.048, 0.079, 0.058, 0.02, 0.0001, 0.02, 0.0001, 0.015, 0.0001, 10.33, 0.786, 1.541, 0.901, 3.961, 0.163, 2.604, 2.426, 8.641, 0.326, 1.916, 0.633, 2.867, 6.576, 3.627, 0.309, 0.068, 4.492, 0.79, 3.938, 2.525, 0.104, 1.397, 0.085, 2.472, 0.26, 0.003, 0.0001, 0.003, 0.0001, 0.0001, 0.05, 0.003, 0.002, 0.001, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.037, 0.01, 0.0001, 0.001, 0.003, 0.0001, 0.001, 0.001, 0.013, 0.005, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.06, 4.809, 0.0001, 0.069, 0.002, 0.006, 0.001, 0.004, 0.016, 0.25, 0.0001, 0.009, 0.003, 0.002, 0.0001, 0.009, 0.003, 0.001, 0.003, 0.026, 0.003, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.021, 0.032, 2.884, 2.321, 0.0001, 0.002, 0.0001, 0.002, 0.021, 0.001, 0.0001, 0.0001, 0.002, 0.001, 0.009, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.048, 0.0001, 0.0001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kj\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.677, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.71, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.323, 0.323, 0.0001, 0.0001, 0.645, 0.0001, 0.323, 0.0001, 1.613, 0.0001, 0.323, 0.323, 0.0001, 0.0001, 0.645, 0.323, 0.0001, 0.0001, 0.968, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.0001, 0.0001, 0.645, 0.0001, 0.0001, 0.323, 0.0001, 2.903, 0.323, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.097, 3.226, 0.0001, 6.774, 6.774, 1.935, 1.29, 2.903, 10.645, 0.0001, 3.226, 3.226, 6.129, 2.581, 8.065, 0.323, 0.0001, 0.0001, 0.968, 0.323, 1.935, 0.645, 2.581, 0.0001, 0.323, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kk\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.706, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.721, 0.001, 0.107, 0.001, 0.0001, 0.006, 0.0001, 0.002, 0.151, 0.153, 0.001, 0.003, 0.51, 0.246, 0.686, 0.019, 0.221, 0.251, 0.17, 0.104, 0.092, 0.099, 0.09, 0.082, 0.09, 0.149, 0.04, 0.015, 0.002, 0.003, 0.002, 0.002, 0.0001, 0.007, 0.006, 0.025, 0.003, 0.016, 0.005, 0.021, 0.002, 0.024, 0.001, 0.002, 0.003, 0.008, 0.018, 0.004, 0.01, 0.0001, 0.004, 0.021, 0.003, 0.003, 0.004, 0.004, 0.005, 0.001, 0.003, 0.003, 0.0001, 0.003, 0.0001, 0.002, 0.0001, 0.029, 0.005, 0.01, 0.008, 0.028, 0.004, 0.005, 0.007, 0.042, 0.0001, 0.004, 0.014, 0.009, 0.02, 0.023, 0.007, 0.0001, 0.021, 0.015, 0.017, 0.01, 0.003, 0.003, 0.001, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.667, 1.698, 2.42, 0.825, 0.088, 0.097, 0.101, 0.026, 0.573, 0.004, 0.008, 3.51, 0.092, 0.043, 0.029, 0.226, 0.151, 0.105, 0.034, 0.789, 0.175, 0.042, 2.128, 0.009, 0.046, 0.288, 0.211, 1.223, 0.099, 0.041, 0.082, 0.052, 0.093, 0.104, 0.084, 0.646, 0.032, 0.032, 0.003, 0.007, 0.066, 0.363, 0.0001, 0.047, 0.001, 0.014, 0.013, 0.268, 5.447, 1.26, 0.188, 0.541, 1.919, 3.092, 0.668, 0.583, 0.903, 0.603, 1.169, 2.242, 1.309, 3.102, 1.26, 0.548, 0.0001, 0.0001, 0.145, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 25.612, 14.266, 3.356, 0.697, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.197, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kl\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.635, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.811, 0.001, 0.341, 0.0001, 0.0001, 0.006, 0.002, 0.029, 0.34, 0.34, 0.002, 0.001, 0.7, 0.575, 1.063, 0.02, 0.29, 0.457, 0.333, 0.207, 0.125, 0.141, 0.14, 0.129, 0.134, 0.187, 0.161, 0.026, 0.008, 0.005, 0.008, 0.0001, 0.0001, 0.28, 0.09, 0.107, 0.095, 0.075, 0.086, 0.052, 0.078, 0.183, 0.108, 0.243, 0.074, 0.184, 0.254, 0.06, 0.118, 0.079, 0.062, 0.257, 0.166, 0.177, 0.059, 0.032, 0.002, 0.012, 0.015, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 12.711, 0.276, 0.184, 0.432, 3.419, 0.731, 1.669, 0.268, 10.409, 0.217, 2.335, 4.586, 2.81, 6.778, 2.817, 1.735, 2.883, 5.126, 5.68, 6.217, 6.675, 0.652, 0.054, 0.023, 0.144, 0.055, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.031, 0.035, 0.014, 0.011, 0.03, 0.017, 0.013, 0.007, 0.019, 0.003, 0.015, 0.002, 0.011, 0.008, 0.02, 0.004, 0.014, 0.011, 0.007, 0.013, 0.007, 0.01, 0.01, 0.01, 0.008, 0.016, 0.005, 0.005, 0.009, 0.004, 0.004, 0.006, 0.015, 0.021, 0.01, 0.011, 0.03, 0.03, 0.045, 0.038, 0.021, 0.031, 0.02, 0.014, 0.007, 0.018, 0.005, 0.016, 0.035, 0.027, 0.022, 0.024, 0.011, 0.015, 0.016, 0.009, 0.078, 0.018, 0.016, 0.011, 0.01, 0.011, 0.018, 0.012, 0.0001, 0.0001, 0.012, 0.201, 0.031, 0.017, 0.001, 0.001, 0.0001, 0.004, 0.004, 0.004, 0.004, 0.0001, 0.016, 0.008, 0.085, 0.031, 0.0001, 0.001, 0.0001, 0.0001, 0.024, 0.053, 0.13, 0.094, 0.007, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.065, 0.039, 0.022, 0.0001, 0.001, 0.011, 0.007, 0.001, 0.001, 0.003, 0.001, 0.002, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"km\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.234, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.565, 0.004, 0.038, 0.0001, 0.0001, 0.004, 0.0001, 0.009, 0.049, 0.049, 0.0001, 0.001, 0.07, 0.028, 0.072, 0.003, 0.02, 0.022, 0.013, 0.008, 0.007, 0.007, 0.006, 0.006, 0.007, 0.012, 0.008, 0.003, 0.007, 0.012, 0.008, 0.004, 0.0001, 0.018, 0.012, 0.02, 0.008, 0.012, 0.009, 0.007, 0.009, 0.013, 0.004, 0.006, 0.012, 0.012, 0.009, 0.006, 0.011, 0.001, 0.009, 0.018, 0.02, 0.004, 0.004, 0.006, 0.002, 0.002, 0.001, 0.022, 0.0001, 0.022, 0.0001, 0.004, 0.0001, 0.403, 0.068, 0.154, 0.173, 0.554, 0.096, 0.093, 0.2, 0.358, 0.005, 0.025, 0.201, 0.122, 0.348, 0.339, 0.093, 0.005, 0.306, 0.292, 0.378, 0.132, 0.051, 0.059, 0.012, 0.073, 0.006, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.652, 0.801, 0.696, 0.139, 1.351, 0.735, 0.591, 0.836, 0.083, 0.299, 0.563, 1.859, 0.05, 0.041, 0.223, 1.134, 0.273, 0.671, 2.897, 1.707, 1.359, 0.097, 0.57, 0.24, 1.039, 0.72, 1.493, 0.708, 0.482, 0.006, 22.614, 7.802, 0.292, 0.16, 0.416, 0.027, 0.02, 0.041, 0.016, 0.053, 0.015, 0.021, 0.003, 0.006, 0.019, 0.002, 0.004, 0.041, 0.002, 0.021, 0.047, 0.001, 0.001, 0.001, 2.388, 0.829, 0.388, 0.131, 0.053, 0.602, 0.318, 0.199, 0.385, 0.021, 0.0001, 0.0001, 0.017, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 29.306, 1.288, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.22, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.193, 0.001, 0.077, 0.0001, 0.001, 0.006, 0.001, 0.024, 0.05, 0.05, 0.001, 0.001, 0.263, 0.039, 0.387, 0.008, 0.055, 0.048, 0.031, 0.015, 0.013, 0.017, 0.014, 0.014, 0.016, 0.027, 0.012, 0.01, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.007, 0.004, 0.007, 0.004, 0.004, 0.003, 0.002, 0.002, 0.007, 0.001, 0.001, 0.002, 0.005, 0.003, 0.003, 0.004, 0.0001, 0.003, 0.008, 0.004, 0.004, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.005, 0.0001, 0.005, 0.0001, 0.001, 0.001, 0.019, 0.003, 0.007, 0.007, 0.022, 0.004, 0.004, 0.008, 0.016, 0.001, 0.002, 0.009, 0.007, 0.014, 0.015, 0.005, 0.0001, 0.014, 0.012, 0.015, 0.006, 0.002, 0.003, 0.001, 0.003, 0.001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.377, 1.744, 1.056, 0.052, 0.0001, 0.294, 1.302, 0.476, 0.14, 0.07, 0.25, 0.184, 0.18, 3.237, 0.115, 0.016, 0.01, 0.0001, 0.076, 0.009, 0.004, 1.075, 0.058, 1.134, 0.019, 0.006, 0.205, 0.005, 0.214, 0.004, 0.012, 0.397, 0.02, 0.439, 0.004, 0.214, 1.341, 0.105, 1.57, 0.184, 1.477, 0.01, 0.553, 0.067, 0.408, 0.14, 0.772, 0.936, 1.909, 0.0001, 24.738, 8.223, 0.0001, 1.147, 0.212, 0.182, 0.975, 0.409, 0.0001, 0.0001, 0.001, 0.0001, 1.605, 2.364, 0.0001, 0.0001, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 31.178, 0.0001, 0.177, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"koi\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.5, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.538, 0.003, 0.105, 0.0001, 0.0001, 0.012, 0.0001, 0.066, 0.298, 0.299, 0.001, 0.003, 0.665, 0.135, 0.828, 0.022, 0.193, 0.238, 0.151, 0.096, 0.069, 0.095, 0.069, 0.062, 0.067, 0.14, 0.09, 0.011, 0.011, 0.003, 0.011, 0.012, 0.0001, 0.012, 0.004, 0.007, 0.003, 0.004, 0.002, 0.002, 0.009, 0.016, 0.003, 0.015, 0.007, 0.012, 0.004, 0.018, 0.015, 0.0001, 0.004, 0.016, 0.008, 0.003, 0.011, 0.001, 0.01, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.204, 0.031, 0.062, 0.036, 0.122, 0.007, 0.019, 0.037, 0.201, 0.009, 0.035, 0.077, 0.03, 0.109, 0.075, 0.025, 0.001, 0.099, 0.08, 0.059, 0.07, 0.013, 0.004, 0.003, 0.013, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.529, 2.707, 1.977, 1.216, 0.076, 0.086, 0.043, 0.37, 0.314, 0.008, 0.015, 2.496, 1.151, 0.356, 0.143, 0.67, 0.146, 0.176, 0.113, 0.201, 0.173, 0.031, 0.44, 0.017, 0.071, 0.03, 0.345, 0.07, 0.14, 0.058, 0.096, 0.178, 0.09, 0.17, 0.07, 0.048, 0.048, 0.034, 0.04, 1.604, 0.03, 0.001, 0.001, 0.041, 0.0001, 0.038, 0.042, 0.02, 3.314, 0.375, 1.556, 0.398, 1.638, 1.43, 1.24, 0.859, 1.906, 0.527, 1.802, 1.787, 1.504, 2.903, 2.273, 0.718, 0.0001, 0.0001, 0.079, 0.905, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.021, 0.0001, 0.001, 0.001, 25.357, 14.367, 0.001, 1.602, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.0001, 0.293, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kr\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 25.0, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.667, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 8.333, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 8.333, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"krc\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.633, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.611, 0.001, 0.078, 0.0001, 0.002, 0.011, 0.0001, 0.002, 0.139, 0.14, 0.0001, 0.001, 0.591, 0.21, 0.542, 0.004, 0.138, 0.24, 0.114, 0.076, 0.067, 0.073, 0.058, 0.056, 0.073, 0.12, 0.04, 0.013, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.004, 0.004, 0.006, 0.003, 0.003, 0.002, 0.003, 0.002, 0.03, 0.001, 0.002, 0.003, 0.004, 0.002, 0.002, 0.002, 0.001, 0.002, 0.005, 0.004, 0.001, 0.01, 0.002, 0.017, 0.0001, 0.001, 0.014, 0.0001, 0.014, 0.0001, 0.001, 0.0001, 0.038, 0.009, 0.012, 0.014, 0.044, 0.006, 0.01, 0.013, 0.029, 0.002, 0.006, 0.019, 0.015, 0.026, 0.027, 0.008, 0.001, 0.031, 0.024, 0.024, 0.014, 0.007, 0.003, 0.002, 0.005, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.564, 1.141, 1.579, 1.633, 0.148, 0.303, 0.164, 0.503, 0.456, 0.003, 1.306, 2.454, 0.134, 0.294, 0.686, 0.418, 0.168, 0.334, 0.032, 0.057, 0.2, 0.019, 0.006, 0.011, 0.056, 0.006, 0.17, 0.03, 0.076, 0.03, 0.044, 0.051, 0.143, 0.104, 0.049, 0.03, 0.04, 0.03, 0.005, 0.024, 0.052, 0.001, 0.001, 0.061, 0.001, 0.035, 0.01, 0.009, 6.121, 1.252, 0.294, 1.328, 2.359, 2.412, 0.507, 0.442, 2.779, 0.471, 1.691, 3.418, 1.039, 3.402, 1.301, 0.302, 0.0001, 0.0001, 0.209, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 0.0001, 0.006, 0.002, 30.423, 13.816, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.14, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ks\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.09, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.116, 0.0001, 0.395, 0.007, 0.0001, 0.0001, 0.004, 0.023, 0.126, 0.124, 0.0001, 0.001, 0.08, 0.153, 0.257, 0.005, 0.042, 0.08, 0.041, 0.04, 0.021, 0.031, 0.018, 0.019, 0.02, 0.048, 0.059, 0.003, 0.053, 0.167, 0.053, 0.0001, 0.0001, 0.005, 0.003, 0.008, 0.007, 0.01, 0.007, 0.001, 0.002, 0.016, 0.003, 0.003, 0.003, 0.004, 0.019, 0.008, 0.002, 0.0001, 0.005, 0.01, 0.013, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.198, 0.016, 0.127, 0.13, 0.17, 0.01, 0.074, 0.033, 0.171, 0.002, 0.016, 0.235, 0.092, 0.256, 0.101, 0.014, 0.0001, 0.218, 0.145, 0.254, 0.031, 0.065, 0.054, 0.0001, 0.012, 0.0001, 0.0001, 0.009, 0.0001, 0.0001, 0.0001, 0.451, 1.562, 0.534, 0.248, 1.154, 1.571, 2.211, 0.263, 1.281, 0.132, 0.341, 0.16, 1.683, 0.702, 0.993, 0.637, 0.623, 0.052, 0.37, 0.043, 0.331, 0.813, 0.313, 0.319, 0.042, 0.007, 0.092, 0.282, 0.326, 0.013, 0.006, 0.065, 0.245, 0.114, 0.179, 0.083, 8.684, 2.577, 0.461, 2.698, 1.217, 0.995, 1.306, 0.114, 0.545, 0.242, 0.666, 1.253, 0.811, 1.543, 0.848, 0.915, 0.434, 0.417, 0.188, 0.11, 0.481, 0.73, 0.156, 0.08, 0.312, 0.004, 2.104, 0.512, 0.0001, 0.0001, 0.188, 0.0001, 0.017, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.031, 0.0001, 0.008, 0.002, 0.028, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.045, 10.482, 2.72, 3.264, 0.0001, 0.0001, 0.0001, 0.0001, 10.717, 0.001, 0.078, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.129, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ksh\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.3, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.544, 0.007, 0.195, 0.001, 0.0001, 0.006, 0.002, 0.064, 0.077, 0.076, 0.018, 0.0001, 0.951, 0.126, 1.237, 0.01, 0.258, 0.351, 0.176, 0.091, 0.09, 0.099, 0.083, 0.083, 0.116, 0.206, 0.046, 0.013, 0.003, 0.002, 0.003, 0.004, 0.0001, 0.29, 0.361, 0.086, 0.549, 0.218, 0.205, 0.059, 0.258, 0.102, 0.404, 0.343, 0.228, 0.359, 0.191, 0.138, 0.226, 0.009, 0.194, 0.601, 0.11, 0.081, 0.179, 0.232, 0.004, 0.009, 0.121, 0.015, 0.0001, 0.017, 0.0001, 0.132, 0.0001, 4.203, 0.771, 2.07, 4.046, 9.612, 0.707, 0.863, 3.367, 3.374, 1.363, 1.028, 2.762, 2.009, 5.309, 3.906, 0.798, 0.006, 4.302, 3.629, 3.86, 2.108, 1.453, 1.005, 0.049, 0.079, 0.749, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.123, 0.002, 0.002, 0.001, 0.082, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.005, 0.0001, 0.002, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.005, 0.011, 0.0001, 0.082, 0.036, 0.001, 0.009, 0.0001, 0.001, 0.047, 0.002, 0.044, 0.413, 0.03, 0.004, 0.001, 0.001, 1.721, 0.001, 0.004, 0.002, 0.003, 0.013, 0.001, 0.084, 0.001, 0.002, 0.0001, 0.008, 0.003, 0.001, 0.005, 0.025, 0.002, 0.001, 1.538, 0.002, 0.001, 0.001, 0.002, 0.003, 0.531, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.029, 4.494, 0.051, 0.013, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.001, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.124, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ku\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.393, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.949, 0.005, 0.256, 0.0001, 0.0001, 0.006, 0.001, 0.113, 0.21, 0.21, 0.006, 0.003, 0.803, 0.095, 1.083, 0.023, 0.197, 0.26, 0.139, 0.073, 0.067, 0.078, 0.071, 0.068, 0.08, 0.162, 0.084, 0.022, 0.008, 0.003, 0.008, 0.006, 0.0001, 0.192, 0.235, 0.084, 0.253, 0.208, 0.064, 0.107, 0.175, 0.032, 0.081, 0.247, 0.144, 0.22, 0.115, 0.035, 0.144, 0.05, 0.104, 0.203, 0.123, 0.017, 0.026, 0.063, 0.071, 0.053, 0.061, 0.009, 0.0001, 0.009, 0.0001, 0.003, 0.002, 7.122, 1.894, 0.369, 2.998, 7.334, 0.296, 0.895, 1.263, 5.92, 0.932, 2.612, 1.973, 1.601, 5.257, 1.434, 0.604, 0.177, 4.384, 1.643, 2.193, 1.071, 1.195, 1.296, 0.608, 2.23, 0.722, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.075, 0.006, 0.004, 0.007, 0.009, 0.007, 0.012, 0.062, 0.012, 0.002, 0.018, 0.001, 0.015, 0.003, 0.068, 0.002, 0.001, 0.001, 0.002, 0.006, 0.001, 0.004, 0.003, 0.001, 0.007, 0.019, 0.001, 0.009, 0.013, 0.012, 0.074, 0.658, 0.013, 0.005, 0.004, 0.002, 0.003, 0.002, 0.004, 0.335, 0.008, 0.015, 3.587, 0.004, 0.003, 0.008, 2.776, 0.007, 0.009, 0.038, 0.007, 0.007, 0.016, 0.003, 0.007, 0.002, 0.005, 0.004, 0.005, 1.203, 0.019, 0.003, 0.004, 0.002, 0.0001, 0.0001, 0.03, 8.061, 0.042, 0.73, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.01, 0.005, 0.017, 0.006, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.003, 0.063, 0.061, 0.005, 0.012, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.006, 0.073, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.403, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.572, 0.002, 0.112, 0.0001, 0.0001, 0.004, 0.0001, 0.006, 0.253, 0.254, 0.001, 0.001, 0.652, 0.237, 0.796, 0.019, 0.199, 0.362, 0.176, 0.114, 0.094, 0.095, 0.085, 0.09, 0.105, 0.228, 0.059, 0.022, 0.003, 0.001, 0.004, 0.001, 0.0001, 0.008, 0.005, 0.011, 0.005, 0.005, 0.004, 0.005, 0.005, 0.02, 0.004, 0.011, 0.006, 0.006, 0.007, 0.004, 0.005, 0.002, 0.005, 0.011, 0.006, 0.004, 0.008, 0.003, 0.013, 0.003, 0.002, 0.039, 0.0001, 0.039, 0.0001, 0.0001, 0.0001, 0.115, 0.019, 0.016, 0.027, 0.069, 0.009, 0.022, 0.03, 0.155, 0.012, 0.027, 0.043, 0.035, 0.068, 0.048, 0.017, 0.005, 0.048, 0.041, 0.048, 0.044, 0.012, 0.013, 0.005, 0.014, 0.009, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.292, 3.131, 1.711, 1.163, 0.094, 0.093, 0.068, 0.394, 0.316, 0.013, 0.283, 2.417, 0.996, 0.157, 0.138, 0.933, 0.185, 0.144, 0.161, 0.163, 0.266, 0.043, 0.288, 0.024, 0.072, 0.036, 0.257, 0.079, 0.116, 0.07, 0.051, 0.125, 0.115, 0.273, 0.082, 0.056, 0.037, 0.021, 0.028, 2.085, 0.052, 0.007, 0.009, 0.112, 0.005, 0.032, 0.025, 0.019, 3.353, 0.487, 1.618, 0.507, 1.803, 1.293, 0.994, 0.555, 1.916, 0.692, 1.739, 1.936, 1.41, 2.819, 2.119, 0.641, 0.0001, 0.0001, 0.211, 0.665, 0.015, 0.015, 0.006, 0.004, 0.002, 0.021, 0.019, 0.013, 0.017, 0.003, 0.013, 0.006, 25.09, 14.142, 0.006, 2.075, 0.003, 0.001, 0.001, 0.003, 0.01, 0.008, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.016, 0.023, 0.303, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.018, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"kw\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.271, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.698, 0.003, 0.428, 0.0001, 0.0001, 0.066, 0.001, 0.379, 0.501, 0.501, 0.001, 0.001, 1.043, 0.466, 1.444, 0.019, 0.602, 0.995, 0.55, 0.31, 0.288, 0.284, 0.283, 0.29, 0.333, 0.472, 0.076, 0.113, 0.013, 0.003, 0.013, 0.002, 0.0001, 0.413, 0.261, 0.217, 0.211, 0.211, 0.117, 0.24, 0.182, 0.089, 0.078, 0.627, 0.279, 0.264, 0.166, 0.089, 0.271, 0.018, 0.16, 0.497, 0.182, 0.13, 0.12, 0.167, 0.003, 0.325, 0.009, 0.005, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 7.247, 1.102, 0.333, 2.599, 6.963, 0.397, 1.527, 3.863, 2.585, 0.151, 1.745, 2.596, 1.638, 6.936, 4.292, 0.693, 0.011, 4.812, 4.449, 2.907, 1.185, 1.044, 2.706, 0.022, 4.495, 0.043, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.063, 0.013, 0.005, 0.007, 0.005, 0.004, 0.003, 0.003, 0.006, 0.002, 0.002, 0.001, 0.003, 0.005, 0.002, 0.002, 0.003, 0.003, 0.001, 0.033, 0.004, 0.002, 0.002, 0.004, 0.004, 0.02, 0.001, 0.001, 0.003, 0.002, 0.001, 0.002, 0.025, 0.014, 0.004, 0.005, 0.015, 0.004, 0.003, 0.007, 0.004, 0.018, 0.007, 0.005, 0.002, 0.009, 0.005, 0.004, 0.012, 0.007, 0.012, 0.009, 0.008, 0.009, 0.007, 0.003, 0.012, 0.008, 0.009, 0.008, 0.006, 0.007, 0.012, 0.004, 0.0001, 0.0001, 0.028, 0.1, 0.011, 0.012, 0.0001, 0.001, 0.0001, 0.007, 0.003, 0.004, 0.004, 0.0001, 0.012, 0.004, 0.062, 0.02, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.004, 0.013, 0.011, 0.001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.013, 0.007, 0.058, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ky\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.608, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.786, 0.001, 0.076, 0.0001, 0.0001, 0.007, 0.001, 0.001, 0.181, 0.185, 0.0001, 0.003, 0.592, 0.375, 0.793, 0.011, 0.212, 0.26, 0.154, 0.095, 0.087, 0.095, 0.083, 0.081, 0.088, 0.165, 0.05, 0.023, 0.024, 0.003, 0.024, 0.002, 0.0001, 0.006, 0.009, 0.006, 0.003, 0.002, 0.01, 0.002, 0.004, 0.023, 0.001, 0.001, 0.003, 0.004, 0.009, 0.004, 0.011, 0.0001, 0.003, 0.019, 0.005, 0.001, 0.003, 0.002, 0.004, 0.001, 0.0001, 0.007, 0.0001, 0.008, 0.0001, 0.002, 0.0001, 0.034, 0.028, 0.011, 0.01, 0.036, 0.004, 0.007, 0.01, 0.029, 0.001, 0.005, 0.017, 0.009, 0.023, 0.028, 0.008, 0.001, 0.047, 0.02, 0.022, 0.013, 0.003, 0.003, 0.002, 0.006, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 2.775, 1.184, 2.443, 1.907, 0.113, 0.08, 0.128, 0.561, 0.622, 0.004, 0.005, 2.414, 0.086, 0.264, 0.107, 0.368, 0.184, 0.149, 0.029, 0.1, 0.146, 0.009, 0.06, 0.008, 0.039, 0.003, 0.233, 0.023, 0.133, 0.051, 0.082, 0.045, 0.072, 0.109, 0.101, 0.162, 0.039, 0.017, 0.003, 0.031, 0.045, 0.843, 0.0001, 0.06, 0.001, 0.049, 0.011, 1.059, 5.238, 0.934, 0.249, 1.237, 1.665, 2.222, 0.662, 0.522, 2.314, 0.665, 2.431, 2.219, 1.157, 3.498, 1.756, 0.567, 0.0001, 0.0001, 0.135, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 28.842, 12.881, 1.192, 0.856, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.186, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"la\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.703, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.582, 0.002, 0.557, 0.0001, 0.0001, 0.004, 0.001, 0.038, 0.296, 0.296, 0.016, 0.002, 1.029, 0.127, 0.917, 0.01, 0.288, 0.518, 0.368, 0.158, 0.135, 0.172, 0.155, 0.139, 0.169, 0.292, 0.103, 0.058, 0.002, 0.004, 0.002, 0.002, 0.0001, 0.441, 0.179, 0.385, 0.16, 0.131, 0.176, 0.158, 0.144, 0.363, 0.023, 0.04, 0.184, 0.266, 0.121, 0.103, 0.293, 0.049, 0.202, 0.319, 0.152, 0.063, 0.122, 0.033, 0.022, 0.01, 0.013, 0.004, 0.0001, 0.004, 0.0001, 0.004, 0.0001, 7.718, 1.137, 2.983, 1.877, 7.832, 0.566, 0.934, 0.721, 8.862, 0.018, 0.079, 2.703, 3.638, 5.533, 4.661, 1.753, 0.47, 5.095, 5.379, 5.968, 5.347, 0.814, 0.036, 0.291, 0.205, 0.069, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.045, 0.018, 0.011, 0.014, 0.009, 0.005, 0.004, 0.005, 0.004, 0.018, 0.002, 0.002, 0.005, 0.007, 0.002, 0.002, 0.004, 0.003, 0.002, 0.014, 0.007, 0.002, 0.002, 0.002, 0.003, 0.004, 0.003, 0.002, 0.004, 0.003, 0.003, 0.004, 0.013, 0.011, 0.004, 0.003, 0.009, 0.004, 0.004, 0.006, 0.01, 0.02, 0.004, 0.013, 0.003, 0.007, 0.003, 0.005, 0.044, 0.013, 0.009, 0.008, 0.006, 0.012, 0.008, 0.005, 0.014, 0.01, 0.011, 0.011, 0.013, 0.014, 0.01, 0.01, 0.0001, 0.0001, 0.047, 0.083, 0.019, 0.012, 0.0001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.062, 0.03, 0.07, 0.024, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.005, 0.012, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 0.015, 0.037, 0.003, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lad\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.233, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.114, 0.001, 0.334, 0.0001, 0.0001, 0.009, 0.001, 0.032, 0.169, 0.169, 0.0001, 0.0001, 1.028, 0.087, 0.763, 0.008, 0.237, 0.25, 0.147, 0.074, 0.074, 0.086, 0.072, 0.065, 0.078, 0.138, 0.053, 0.043, 0.005, 0.001, 0.005, 0.001, 0.0001, 0.303, 0.15, 0.122, 0.124, 0.422, 0.07, 0.094, 0.073, 0.145, 0.052, 0.173, 0.269, 0.273, 0.076, 0.097, 0.169, 0.01, 0.114, 0.279, 0.178, 0.068, 0.08, 0.015, 0.026, 0.054, 0.024, 0.01, 0.0001, 0.01, 0.0001, 0.0001, 0.0001, 10.092, 0.654, 0.428, 4.329, 9.389, 0.52, 0.701, 0.524, 5.468, 0.466, 2.315, 4.475, 1.912, 5.533, 6.266, 1.56, 0.038, 4.367, 5.784, 3.223, 2.47, 1.069, 0.027, 0.043, 1.039, 0.561, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.02, 0.012, 0.005, 0.005, 0.009, 0.006, 0.004, 0.004, 0.005, 0.003, 0.006, 0.002, 0.002, 0.003, 0.003, 0.001, 0.015, 0.012, 0.004, 0.011, 0.018, 0.019, 0.004, 0.004, 0.007, 0.031, 0.002, 0.006, 0.016, 0.006, 0.01, 0.011, 0.017, 0.14, 0.008, 0.005, 0.006, 0.003, 0.003, 0.02, 0.02, 0.11, 0.013, 0.01, 0.003, 0.09, 0.002, 0.005, 0.012, 0.024, 0.019, 0.137, 0.008, 0.006, 0.005, 0.006, 0.008, 0.006, 0.03, 0.011, 0.011, 0.004, 0.004, 0.004, 0.0001, 0.0001, 0.044, 0.511, 0.018, 0.013, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.017, 0.007, 0.023, 0.009, 0.0001, 0.0001, 0.0001, 0.005, 0.02, 0.199, 0.037, 0.028, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.006, 0.018, 0.0001, 0.001, 0.003, 0.002, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lb\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.412, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.95, 0.002, 0.355, 0.0001, 0.0001, 0.008, 0.002, 0.417, 0.145, 0.146, 0.001, 0.003, 0.802, 0.307, 1.03, 0.016, 0.348, 0.52, 0.266, 0.139, 0.134, 0.143, 0.128, 0.134, 0.162, 0.294, 0.059, 0.012, 0.015, 0.003, 0.015, 0.001, 0.0001, 0.428, 0.324, 0.254, 0.594, 0.233, 0.259, 0.289, 0.233, 0.12, 0.196, 0.27, 0.284, 0.379, 0.192, 0.132, 0.314, 0.012, 0.243, 0.585, 0.165, 0.101, 0.142, 0.167, 0.006, 0.01, 0.098, 0.005, 0.0001, 0.005, 0.0001, 0.003, 0.0001, 4.931, 0.886, 1.95, 2.841, 11.151, 0.974, 2.202, 2.438, 4.449, 0.072, 0.85, 2.736, 2.142, 6.511, 2.976, 0.873, 0.044, 5.369, 4.192, 4.448, 3.418, 0.952, 0.815, 0.087, 0.179, 0.783, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.039, 0.004, 0.003, 0.002, 0.022, 0.001, 0.001, 0.002, 0.001, 0.016, 0.001, 0.02, 0.001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.001, 0.012, 0.001, 0.0001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.01, 0.002, 0.008, 0.002, 0.053, 0.005, 0.005, 0.001, 0.485, 0.001, 0.003, 0.007, 0.029, 0.959, 0.004, 0.541, 0.001, 0.003, 0.002, 0.002, 0.009, 0.004, 0.006, 0.005, 0.01, 0.003, 0.01, 0.001, 0.004, 0.002, 0.003, 0.005, 0.046, 0.003, 0.003, 0.002, 0.0001, 0.0001, 0.061, 2.169, 0.003, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 0.004, 0.024, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.037, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lbe\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.255, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.9, 0.001, 0.252, 0.0001, 0.0001, 0.001, 0.0001, 0.011, 0.416, 0.416, 0.0001, 0.003, 0.481, 0.136, 0.815, 0.07, 0.265, 0.236, 0.199, 0.107, 0.105, 0.116, 0.098, 0.098, 0.121, 0.12, 0.136, 0.067, 0.071, 0.002, 0.067, 0.006, 0.0001, 0.016, 0.004, 0.021, 0.002, 0.004, 0.005, 0.004, 0.003, 0.485, 0.0001, 0.002, 0.006, 0.012, 0.003, 0.003, 0.014, 0.001, 0.005, 0.011, 0.004, 0.002, 0.006, 0.002, 0.003, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.216, 0.084, 0.071, 0.045, 0.128, 0.01, 0.022, 0.031, 0.155, 0.002, 0.014, 0.09, 0.049, 0.088, 0.086, 0.051, 0.003, 0.174, 0.114, 0.069, 0.102, 0.012, 0.003, 0.009, 0.024, 0.006, 0.001, 0.001, 0.001, 0.001, 0.0001, 3.391, 1.985, 1.311, 3.41, 0.076, 1.237, 0.309, 0.579, 0.377, 0.095, 0.645, 0.087, 1.158, 0.044, 0.125, 0.671, 0.313, 0.089, 0.058, 0.221, 0.212, 0.014, 0.015, 0.044, 0.077, 0.005, 0.185, 0.069, 0.144, 0.054, 0.029, 0.04, 0.037, 0.075, 0.123, 0.038, 0.018, 0.116, 0.052, 0.091, 0.05, 0.027, 0.003, 0.033, 0.004, 0.04, 0.009, 0.029, 7.018, 0.742, 1.169, 0.714, 1.012, 0.485, 0.137, 0.404, 2.976, 0.818, 1.445, 2.805, 1.012, 2.921, 0.476, 0.297, 0.0001, 0.0001, 0.062, 0.008, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 26.245, 14.532, 0.0001, 0.534, 0.0001, 0.001, 0.0001, 0.009, 0.088, 0.067, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.019, 0.318, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lez\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.788, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.917, 0.001, 0.11, 0.0001, 0.0001, 0.014, 0.0001, 0.0001, 0.118, 0.119, 0.0001, 0.001, 0.531, 0.18, 0.599, 0.004, 0.16, 0.227, 0.133, 0.076, 0.063, 0.071, 0.067, 0.062, 0.08, 0.115, 0.048, 0.01, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.003, 0.002, 0.005, 0.001, 0.002, 0.001, 0.001, 0.001, 0.351, 0.0001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.01, 0.001, 0.037, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.023, 0.003, 0.006, 0.005, 0.017, 0.002, 0.004, 0.004, 0.014, 0.001, 0.003, 0.017, 0.004, 0.011, 0.011, 0.005, 0.0001, 0.013, 0.01, 0.009, 0.009, 0.002, 0.001, 0.001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.387, 1.088, 1.449, 2.206, 0.197, 0.805, 0.228, 0.469, 0.264, 0.003, 0.696, 0.03, 1.797, 0.123, 0.075, 0.643, 0.214, 0.062, 0.057, 0.099, 0.243, 0.013, 0.01, 0.016, 0.072, 0.013, 0.197, 0.034, 0.095, 0.027, 0.015, 0.041, 0.182, 0.109, 0.046, 0.053, 0.025, 0.087, 0.024, 0.038, 0.035, 0.001, 0.001, 0.075, 0.001, 0.022, 0.007, 0.017, 6.908, 0.463, 1.591, 1.254, 1.853, 1.815, 0.195, 0.884, 4.344, 1.376, 1.744, 1.947, 0.879, 2.868, 0.597, 0.413, 0.0001, 0.0001, 0.264, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.007, 0.0001, 0.004, 0.002, 30.62, 13.045, 0.0001, 0.248, 0.0001, 0.001, 0.0001, 0.001, 0.004, 0.007, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.149, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lg\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.42, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.857, 0.039, 0.098, 0.0001, 0.0001, 0.008, 0.0001, 0.193, 0.619, 0.652, 0.006, 0.013, 0.576, 0.063, 0.759, 0.031, 0.142, 0.149, 0.106, 0.065, 0.048, 0.064, 0.043, 0.046, 0.035, 0.039, 0.112, 0.029, 0.002, 0.038, 0.003, 0.025, 0.0001, 0.202, 0.147, 0.077, 0.032, 0.406, 0.021, 0.082, 0.019, 0.071, 0.01, 0.184, 0.083, 0.172, 0.138, 0.35, 0.039, 0.002, 0.027, 0.089, 0.063, 0.041, 0.016, 0.06, 0.001, 0.036, 0.019, 0.012, 0.0001, 0.012, 0.0001, 0.01, 0.001, 11.513, 4.158, 0.451, 1.382, 6.569, 0.546, 2.789, 0.349, 6.274, 0.363, 4.548, 2.809, 3.269, 5.614, 5.854, 0.404, 0.013, 2.198, 2.205, 2.706, 6.16, 0.367, 2.254, 0.045, 2.427, 1.395, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 1.181, 0.001, 0.015, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.0001, 0.0001, 0.0001, 0.006, 0.008, 0.842, 0.001, 0.0001, 0.092, 0.085, 0.0001, 0.0001, 0.001, 0.001, 0.113, 0.0001, 0.0001, 0.0001, 0.017, 0.01, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.006, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.181, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.019, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"li\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.944, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.135, 0.002, 0.418, 0.0001, 0.0001, 0.017, 0.001, 1.033, 0.22, 0.22, 0.002, 0.001, 0.717, 0.245, 0.974, 0.02, 0.269, 0.322, 0.176, 0.093, 0.094, 0.096, 0.096, 0.091, 0.103, 0.161, 0.092, 0.054, 0.018, 0.002, 0.018, 0.001, 0.0001, 0.18, 0.177, 0.097, 0.347, 0.099, 0.066, 0.119, 0.148, 0.188, 0.05, 0.105, 0.134, 0.157, 0.158, 0.108, 0.098, 0.003, 0.104, 0.185, 0.093, 0.028, 0.141, 0.105, 0.003, 0.004, 0.083, 0.008, 0.001, 0.008, 0.0001, 0.0001, 0.001, 5.507, 1.139, 0.937, 3.64, 13.741, 0.575, 2.233, 1.264, 5.103, 1.163, 1.751, 2.989, 1.798, 6.008, 4.376, 1.144, 0.011, 4.793, 3.527, 4.666, 1.997, 1.767, 1.153, 0.045, 0.112, 0.704, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.031, 0.005, 0.002, 0.002, 0.002, 0.002, 0.001, 0.001, 0.004, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.01, 0.001, 0.0001, 0.002, 0.0001, 0.007, 0.018, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.002, 0.004, 0.007, 0.003, 0.002, 0.113, 0.003, 0.001, 0.003, 0.424, 0.024, 0.004, 0.246, 0.001, 0.004, 0.001, 0.014, 0.003, 0.003, 0.027, 0.238, 0.005, 0.002, 0.354, 0.001, 0.005, 0.003, 0.003, 0.002, 0.014, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.028, 1.471, 0.005, 0.005, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.001, 0.001, 0.0001, 0.01, 0.004, 0.009, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.006, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.031, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lij\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.115, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.653, 0.006, 0.425, 0.0001, 0.0001, 0.003, 0.001, 1.006, 0.211, 0.212, 0.0001, 0.0001, 1.079, 0.522, 0.689, 0.013, 0.183, 0.34, 0.145, 0.099, 0.1, 0.107, 0.089, 0.101, 0.099, 0.127, 0.107, 0.071, 0.08, 0.003, 0.08, 0.006, 0.0001, 0.288, 0.108, 0.216, 0.12, 0.091, 0.089, 0.116, 0.025, 0.235, 0.016, 0.018, 0.145, 0.148, 0.083, 0.128, 0.166, 0.021, 0.11, 0.224, 0.097, 0.053, 0.082, 0.012, 0.025, 0.004, 0.066, 0.003, 0.0001, 0.003, 0.0001, 0.0001, 0.001, 7.807, 0.66, 2.955, 3.041, 7.727, 0.79, 1.509, 0.643, 7.15, 0.033, 0.062, 2.465, 2.057, 5.516, 6.662, 1.83, 0.232, 3.742, 3.269, 4.498, 2.078, 1.097, 0.032, 0.327, 0.052, 0.447, 0.0001, 0.015, 0.0001, 0.0001, 0.0001, 0.126, 0.011, 0.006, 0.006, 0.007, 0.003, 0.005, 0.015, 0.003, 0.004, 0.003, 0.001, 0.004, 0.006, 0.001, 0.002, 0.001, 0.002, 0.007, 0.108, 0.002, 0.001, 0.003, 0.001, 0.007, 0.097, 0.001, 0.002, 0.005, 0.004, 0.001, 0.001, 0.105, 0.013, 0.443, 0.002, 0.076, 0.002, 0.52, 0.668, 0.246, 0.118, 0.122, 0.032, 0.129, 0.012, 0.108, 0.033, 0.028, 0.081, 0.222, 0.058, 0.152, 0.005, 0.088, 0.002, 0.006, 0.059, 0.022, 0.1, 0.117, 0.007, 0.005, 0.013, 0.0001, 0.0001, 0.059, 3.444, 0.014, 0.118, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.003, 0.0001, 0.026, 0.013, 0.031, 0.013, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.016, 0.012, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.009, 0.005, 0.121, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lmo\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.694, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.003, 0.007, 0.496, 0.0001, 0.002, 0.011, 0.001, 1.536, 0.286, 0.286, 0.0001, 0.001, 1.048, 0.242, 0.905, 0.061, 0.214, 0.291, 0.19, 0.13, 0.124, 0.121, 0.109, 0.107, 0.118, 0.137, 0.12, 0.041, 0.23, 0.036, 0.23, 0.004, 0.0001, 0.256, 0.222, 0.29, 0.092, 0.333, 0.125, 0.138, 0.035, 0.151, 0.022, 0.022, 0.325, 0.213, 0.07, 0.062, 0.237, 0.013, 0.158, 0.284, 0.115, 0.042, 0.131, 0.03, 0.012, 0.005, 0.017, 0.008, 0.0001, 0.008, 0.0001, 0.008, 0.0001, 8.462, 0.843, 2.691, 3.762, 7.44, 0.686, 1.303, 1.109, 4.912, 0.086, 0.225, 4.93, 2.005, 5.17, 2.753, 1.529, 0.12, 4.31, 3.255, 3.626, 1.912, 0.803, 0.038, 0.028, 0.061, 0.501, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.065, 0.004, 0.005, 0.004, 0.003, 0.002, 0.001, 0.001, 0.002, 0.003, 0.001, 0.002, 0.002, 0.003, 0.001, 0.001, 0.001, 0.001, 0.002, 0.011, 0.002, 0.0001, 0.002, 0.001, 0.012, 0.042, 0.001, 0.001, 0.018, 0.002, 0.001, 0.003, 0.883, 0.012, 0.006, 0.001, 0.021, 0.001, 0.002, 0.005, 0.978, 0.311, 0.003, 0.015, 0.376, 0.025, 0.002, 0.002, 0.004, 0.005, 0.393, 0.184, 0.028, 0.003, 0.199, 0.002, 0.003, 0.227, 0.023, 0.007, 0.722, 0.004, 0.002, 0.004, 0.0001, 0.0001, 0.146, 4.287, 0.005, 0.015, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.019, 0.008, 0.013, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.061, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ln\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.397, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.893, 0.03, 0.25, 0.011, 0.001, 0.018, 0.001, 0.058, 0.196, 0.195, 0.019, 0.0001, 0.627, 0.278, 0.997, 0.017, 0.237, 0.316, 0.167, 0.077, 0.074, 0.095, 0.09, 0.07, 0.092, 0.208, 0.084, 0.022, 0.031, 0.028, 0.031, 0.008, 0.0001, 0.272, 0.381, 0.139, 0.08, 0.273, 0.07, 0.073, 0.036, 0.085, 0.047, 0.397, 0.244, 0.485, 0.279, 0.076, 0.136, 0.004, 0.069, 0.216, 0.116, 0.035, 0.048, 0.05, 0.005, 0.052, 0.034, 0.014, 0.018, 0.014, 0.0001, 0.004, 0.0001, 10.636, 2.915, 0.49, 0.988, 4.562, 0.3, 1.532, 0.289, 5.022, 0.059, 3.253, 3.932, 3.872, 5.27, 5.607, 1.218, 0.071, 1.319, 2.651, 2.571, 1.582, 0.27, 0.506, 0.061, 2.255, 1.262, 0.0001, 0.013, 0.0001, 0.0001, 0.0001, 0.045, 0.425, 0.034, 0.001, 0.004, 0.002, 0.016, 0.0001, 0.002, 0.009, 0.003, 0.0001, 0.047, 0.002, 0.03, 0.0001, 0.012, 0.0001, 0.122, 0.011, 0.585, 0.0001, 0.0001, 0.0001, 0.001, 0.025, 0.001, 0.584, 0.003, 0.002, 0.001, 0.0001, 0.21, 1.199, 0.019, 0.009, 0.001, 0.001, 0.002, 0.013, 0.036, 1.009, 0.021, 0.019, 0.002, 0.983, 0.006, 0.015, 0.003, 0.003, 0.005, 0.692, 0.016, 0.003, 0.001, 0.001, 0.001, 0.002, 0.332, 0.02, 0.002, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.228, 4.372, 0.034, 0.004, 0.008, 0.141, 0.0001, 1.137, 0.001, 0.001, 0.5, 0.0001, 0.009, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.014, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.057, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.442, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.079, 0.001, 0.049, 0.0001, 0.0001, 0.006, 0.0001, 0.004, 0.071, 0.071, 0.001, 0.001, 0.152, 0.034, 0.234, 0.012, 0.09, 0.111, 0.08, 0.044, 0.039, 0.045, 0.029, 0.03, 0.029, 0.051, 0.034, 0.006, 0.003, 0.002, 0.003, 0.001, 0.0001, 0.013, 0.008, 0.01, 0.008, 0.006, 0.005, 0.004, 0.005, 0.01, 0.003, 0.004, 0.008, 0.008, 0.007, 0.003, 0.012, 0.0001, 0.005, 0.013, 0.013, 0.003, 0.004, 0.004, 0.001, 0.001, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.157, 0.027, 0.063, 0.059, 0.202, 0.033, 0.037, 0.067, 0.14, 0.003, 0.015, 0.078, 0.05, 0.13, 0.139, 0.039, 0.002, 0.117, 0.112, 0.142, 0.055, 0.018, 0.023, 0.006, 0.028, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.694, 1.8, 0.562, 0.336, 0.72, 0.0001, 0.034, 1.352, 1.675, 1.044, 0.455, 0.525, 0.031, 0.727, 0.002, 0.001, 0.005, 0.004, 0.004, 0.01, 1.254, 0.484, 0.162, 0.734, 0.009, 2.294, 0.653, 0.586, 0.209, 0.092, 0.491, 0.038, 0.022, 1.013, 0.148, 0.224, 0.003, 0.796, 0.001, 0.85, 0.016, 0.008, 0.816, 0.509, 0.001, 1.145, 0.216, 0.004, 1.227, 1.202, 2.293, 0.24, 0.573, 0.78, 0.113, 0.39, 1.673, 0.52, 24.114, 5.723, 0.116, 0.133, 0.001, 0.0001, 0.0001, 0.0001, 0.085, 0.006, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.829, 0.002, 0.538, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"lrc\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.503, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.494, 0.003, 0.04, 0.0001, 0.0001, 0.008, 0.0001, 0.002, 0.084, 0.084, 0.002, 0.001, 0.009, 0.028, 0.484, 0.015, 0.026, 0.035, 0.019, 0.017, 0.01, 0.011, 0.009, 0.009, 0.015, 0.017, 0.04, 0.001, 0.009, 0.002, 0.009, 0.0001, 0.0001, 0.006, 0.003, 0.003, 0.006, 0.003, 0.002, 0.001, 0.002, 0.005, 0.001, 0.002, 0.002, 0.003, 0.002, 0.001, 0.003, 0.0001, 0.002, 0.003, 0.003, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.003, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.043, 0.006, 0.016, 0.023, 0.041, 0.004, 0.008, 0.011, 0.044, 0.001, 0.008, 0.022, 0.01, 0.037, 0.028, 0.011, 0.001, 0.026, 0.016, 0.024, 0.015, 0.007, 0.006, 0.002, 0.007, 0.003, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.141, 0.397, 0.205, 0.02, 1.605, 1.614, 3.115, 2.266, 3.47, 0.018, 0.099, 0.005, 5.078, 0.005, 0.021, 0.008, 0.02, 0.003, 0.001, 0.004, 0.007, 0.374, 0.0001, 0.001, 0.02, 0.38, 0.001, 0.042, 0.001, 0.001, 0.001, 0.003, 0.001, 0.003, 0.35, 0.86, 0.5, 0.014, 1.898, 5.042, 0.917, 1.365, 1.804, 0.048, 0.445, 0.131, 0.29, 3.021, 0.146, 3.091, 0.704, 1.565, 1.062, 0.145, 0.062, 0.1, 0.06, 0.212, 0.059, 0.025, 0.002, 0.001, 0.485, 0.001, 0.0001, 0.0001, 0.044, 0.006, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.001, 0.017, 0.007, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.007, 20.669, 13.379, 3.076, 5.814, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.002, 0.138, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ltg\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.505, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.915, 0.002, 0.48, 0.0001, 0.0001, 0.03, 0.001, 0.011, 0.307, 0.306, 0.001, 0.001, 1.036, 0.186, 1.128, 0.03, 0.246, 0.429, 0.198, 0.13, 0.123, 0.148, 0.107, 0.108, 0.116, 0.317, 0.161, 0.072, 0.029, 0.005, 0.029, 0.004, 0.0001, 0.199, 0.123, 0.06, 0.183, 0.07, 0.028, 0.064, 0.021, 0.12, 0.092, 0.204, 0.323, 0.133, 0.101, 0.065, 0.288, 0.001, 0.16, 0.239, 0.103, 0.036, 0.21, 0.007, 0.022, 0.001, 0.052, 0.002, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 7.469, 0.962, 0.824, 2.269, 4.253, 0.17, 1.648, 0.088, 6.306, 1.422, 2.423, 2.524, 1.996, 2.559, 4.514, 1.853, 0.001, 3.554, 6.061, 4.103, 4.999, 1.941, 0.013, 0.006, 1.629, 1.118, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.287, 1.24, 0.018, 0.009, 0.002, 0.008, 0.371, 0.004, 0.003, 0.001, 0.001, 0.004, 0.014, 0.08, 0.001, 0.011, 0.003, 0.002, 0.002, 0.271, 0.142, 0.001, 0.001, 0.003, 0.003, 0.007, 0.001, 0.001, 0.003, 0.032, 0.034, 0.002, 0.043, 0.709, 0.001, 0.005, 0.004, 0.002, 0.0001, 0.0001, 0.001, 0.001, 0.015, 2.24, 0.001, 0.001, 0.0001, 0.001, 0.04, 0.01, 0.024, 0.01, 0.013, 0.028, 0.003, 0.011, 0.033, 0.006, 0.014, 0.026, 0.687, 0.026, 0.226, 0.009, 0.0001, 0.0001, 0.023, 0.015, 3.578, 2.215, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.002, 0.0001, 0.003, 0.001, 0.252, 0.098, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.265, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mai\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.888, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.023, 0.001, 0.03, 0.0001, 0.0001, 0.003, 0.001, 0.013, 0.071, 0.074, 0.0001, 0.001, 0.267, 0.061, 0.074, 0.006, 0.01, 0.016, 0.009, 0.005, 0.004, 0.005, 0.004, 0.004, 0.005, 0.012, 0.021, 0.006, 0.004, 0.001, 0.004, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.018, 0.005, 0.005, 0.005, 0.017, 0.003, 0.004, 0.008, 0.015, 0.001, 0.002, 0.009, 0.005, 0.013, 0.013, 0.004, 0.001, 0.014, 0.017, 0.012, 0.006, 0.002, 0.003, 0.001, 0.003, 0.001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.792, 0.705, 0.351, 0.05, 0.0001, 0.548, 0.202, 1.331, 0.277, 0.165, 0.004, 0.356, 0.051, 2.185, 0.0001, 0.286, 0.005, 0.001, 0.0001, 0.066, 0.006, 1.874, 0.183, 0.514, 0.043, 0.102, 0.293, 0.463, 0.567, 0.024, 0.087, 0.255, 0.05, 0.178, 0.022, 0.166, 25.43, 6.866, 0.581, 0.373, 1.476, 0.06, 0.857, 0.137, 0.417, 0.41, 1.258, 0.71, 1.883, 0.001, 1.344, 0.001, 0.001, 0.686, 0.286, 0.227, 1.223, 0.469, 0.0001, 0.0001, 0.026, 0.025, 2.747, 1.736, 0.0001, 0.0001, 0.009, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.668, 0.0001, 0.037, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mdf\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.974, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.901, 0.002, 0.147, 0.0001, 0.0001, 0.003, 0.0001, 0.003, 0.239, 0.241, 0.0001, 0.001, 0.661, 0.233, 0.828, 0.004, 0.16, 0.227, 0.113, 0.065, 0.054, 0.071, 0.072, 0.058, 0.067, 0.13, 0.047, 0.019, 0.002, 0.0001, 0.002, 0.001, 0.0001, 0.006, 0.002, 0.008, 0.002, 0.002, 0.003, 0.002, 0.002, 0.025, 0.001, 0.002, 0.002, 0.005, 0.002, 0.002, 0.006, 0.001, 0.003, 0.005, 0.003, 0.001, 0.008, 0.001, 0.014, 0.0001, 0.0001, 0.004, 0.0001, 0.005, 0.0001, 0.002, 0.0001, 0.07, 0.006, 0.018, 0.016, 0.05, 0.004, 0.011, 0.014, 0.042, 0.003, 0.009, 0.03, 0.013, 0.041, 0.036, 0.013, 0.001, 0.037, 0.035, 0.028, 0.024, 0.005, 0.003, 0.003, 0.006, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.013, 2.98, 2.587, 0.748, 0.583, 0.414, 0.428, 0.203, 0.631, 0.045, 0.095, 0.17, 2.818, 0.257, 0.113, 1.375, 0.157, 0.181, 0.113, 0.066, 0.125, 0.013, 0.006, 0.022, 0.063, 0.005, 0.16, 0.068, 0.186, 0.053, 0.097, 0.114, 0.073, 0.188, 0.099, 0.03, 0.023, 0.016, 0.014, 0.014, 0.049, 0.003, 0.001, 0.054, 0.002, 0.05, 0.007, 0.022, 4.7, 0.292, 1.108, 0.449, 1.264, 2.755, 0.106, 0.711, 2.236, 0.41, 2.142, 1.743, 1.474, 3.418, 3.1, 0.637, 0.0001, 0.0001, 0.118, 0.006, 0.005, 0.003, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.003, 0.002, 0.0001, 0.004, 0.002, 28.205, 15.445, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.006, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.0001, 0.122, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mg\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.132, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.344, 0.0001, 0.051, 0.0001, 0.0001, 0.003, 0.0001, 1.722, 0.134, 0.134, 0.0001, 0.062, 0.6, 1.054, 1.426, 0.011, 0.88, 0.969, 0.776, 0.547, 0.574, 0.473, 0.464, 0.436, 0.531, 0.535, 0.029, 0.033, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.281, 0.132, 0.16, 0.072, 0.212, 0.148, 0.178, 0.056, 0.346, 0.102, 0.053, 0.101, 0.354, 0.788, 0.05, 0.139, 0.008, 0.098, 0.209, 0.172, 0.049, 0.057, 0.038, 0.005, 0.021, 0.009, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 15.071, 0.568, 0.216, 2.816, 2.902, 0.81, 0.249, 1.395, 7.562, 0.225, 1.469, 1.52, 3.108, 9.36, 4.666, 0.931, 0.023, 4.686, 1.843, 3.288, 0.414, 0.748, 0.044, 0.043, 4.297, 0.559, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.076, 0.002, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.002, 0.001, 0.008, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.001, 0.0001, 0.001, 0.052, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.017, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.15, 0.01, 0.007, 0.008, 0.001, 0.0001, 0.001, 0.006, 0.026, 0.088, 0.003, 0.004, 0.001, 0.005, 0.002, 0.002, 0.137, 0.002, 0.001, 0.004, 0.086, 0.001, 0.002, 0.001, 0.003, 0.001, 0.002, 0.01, 0.003, 0.001, 0.001, 0.008, 0.0001, 0.0001, 0.143, 0.408, 0.006, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.069, 0.004, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mh\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.376, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.504, 0.0001, 0.156, 0.0001, 0.0001, 0.0001, 0.039, 0.039, 0.039, 0.039, 0.0001, 0.0001, 1.325, 0.078, 1.247, 0.039, 0.156, 0.039, 0.078, 0.0001, 0.0001, 0.039, 0.0001, 0.0001, 0.039, 0.0001, 0.039, 0.078, 0.078, 0.039, 0.078, 0.0001, 0.0001, 0.701, 0.273, 0.156, 0.078, 0.312, 0.039, 0.156, 0.078, 0.351, 0.779, 0.779, 0.234, 0.779, 0.0001, 0.039, 0.156, 0.0001, 0.312, 0.195, 0.156, 0.195, 0.039, 0.078, 0.0001, 0.195, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.103, 1.558, 0.312, 0.818, 6.584, 0.078, 0.351, 1.013, 7.402, 4.675, 3.584, 3.039, 2.766, 5.804, 6.389, 0.779, 0.078, 4.753, 1.48, 2.337, 1.441, 0.117, 1.597, 0.0001, 1.558, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.351, 0.0001, 0.0001, 0.156, 0.0001, 0.039, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.039, 0.545, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.467, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.078, 0.0001, 0.117, 0.0001, 0.0001, 0.0001, 1.013, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.078, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.208, 0.429, 0.584, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.662, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mhr\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.247, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.433, 0.01, 0.105, 0.0001, 0.0001, 0.003, 0.0001, 0.003, 0.242, 0.243, 0.0001, 0.004, 0.563, 0.341, 0.763, 0.006, 0.23, 0.307, 0.193, 0.103, 0.088, 0.092, 0.076, 0.077, 0.081, 0.164, 0.099, 0.012, 0.003, 0.005, 0.003, 0.006, 0.0001, 0.002, 0.002, 0.003, 0.001, 0.001, 0.002, 0.001, 0.001, 0.045, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.016, 0.001, 0.019, 0.0001, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.02, 0.004, 0.007, 0.008, 0.02, 0.002, 0.003, 0.004, 0.014, 0.0001, 0.002, 0.01, 0.005, 0.01, 0.012, 0.004, 0.0001, 0.013, 0.009, 0.01, 0.005, 0.002, 0.002, 0.0001, 0.002, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.161, 0.998, 2.463, 1.262, 0.079, 0.06, 0.073, 0.732, 2.145, 0.012, 0.024, 3.429, 0.167, 0.157, 0.039, 0.3, 0.114, 0.051, 0.084, 0.076, 0.173, 0.021, 0.005, 0.012, 0.07, 0.035, 0.245, 0.039, 0.204, 0.055, 0.073, 0.108, 0.142, 0.124, 0.167, 0.046, 0.023, 0.257, 0.01, 0.146, 0.069, 0.001, 0.001, 0.099, 0.002, 0.093, 0.02, 0.031, 3.766, 0.43, 0.916, 0.689, 1.067, 3.621, 0.573, 0.276, 1.798, 1.177, 2.133, 2.766, 1.884, 2.711, 2.445, 0.765, 0.0001, 0.0001, 0.222, 0.109, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.008, 0.004, 28.363, 13.911, 0.249, 0.424, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.203, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mi\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.242, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 18.048, 0.002, 0.114, 0.0001, 0.0001, 0.007, 0.0001, 0.316, 0.24, 0.24, 0.0001, 0.0001, 0.815, 0.729, 1.027, 0.003, 0.15, 0.245, 0.11, 0.069, 0.067, 0.071, 0.069, 0.066, 0.083, 0.097, 0.029, 0.194, 0.002, 0.0001, 0.002, 0.002, 0.0001, 0.243, 0.042, 0.09, 0.013, 0.207, 0.019, 0.023, 0.227, 0.154, 0.011, 0.858, 0.022, 0.414, 0.264, 0.035, 0.344, 0.001, 0.143, 0.039, 1.088, 0.016, 0.015, 0.518, 0.001, 0.002, 0.003, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 10.57, 0.047, 0.232, 0.102, 7.727, 0.029, 1.763, 3.618, 6.701, 0.008, 3.514, 0.582, 0.854, 4.652, 6.133, 0.788, 0.003, 3.052, 0.255, 6.464, 3.231, 0.037, 1.326, 0.008, 0.217, 0.009, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.025, 2.749, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.072, 0.357, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.284, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.004, 0.003, 0.001, 0.001, 0.004, 0.001, 0.003, 0.004, 0.003, 0.003, 0.013, 0.525, 0.001, 0.002, 0.001, 0.002, 0.003, 0.004, 0.018, 0.005, 0.001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.019, 0.015, 3.257, 0.759, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.006, 0.008, 0.006, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.002, 0.004, 0.001, 0.0001, 0.002, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"min\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.172, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.612, 0.0001, 0.04, 0.005, 0.0001, 0.002, 0.004, 0.018, 0.155, 0.155, 0.0001, 0.0001, 1.063, 0.022, 1.041, 0.001, 0.404, 0.298, 0.265, 0.112, 0.103, 0.128, 0.132, 0.113, 0.114, 0.233, 0.009, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.635, 0.069, 0.223, 0.216, 0.107, 0.023, 0.035, 0.059, 0.25, 0.026, 0.062, 0.356, 0.142, 0.089, 0.046, 0.143, 0.014, 0.06, 0.402, 0.123, 0.018, 0.017, 0.016, 0.015, 0.037, 0.009, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.754, 1.953, 0.961, 4.093, 4.246, 0.532, 1.865, 1.575, 6.705, 0.46, 3.68, 3.421, 3.054, 5.905, 5.613, 2.448, 0.009, 4.152, 3.536, 3.358, 3.758, 0.175, 0.156, 0.045, 0.909, 0.044, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.018, 0.016, 0.004, 0.004, 0.011, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.017, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.005, 0.0001, 0.0001, 0.014, 0.007, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.016, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.029, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mk\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.442, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.507, 0.001, 0.094, 0.0001, 0.0001, 0.006, 0.001, 0.012, 0.086, 0.086, 0.001, 0.004, 0.588, 0.074, 0.535, 0.01, 0.197, 0.23, 0.143, 0.089, 0.082, 0.088, 0.076, 0.074, 0.08, 0.116, 0.032, 0.012, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.015, 0.008, 0.047, 0.006, 0.006, 0.005, 0.034, 0.005, 0.026, 0.002, 0.003, 0.006, 0.012, 0.023, 0.006, 0.014, 0.001, 0.007, 0.019, 0.01, 0.006, 0.006, 0.004, 0.004, 0.001, 0.001, 0.008, 0.0001, 0.008, 0.0001, 0.002, 0.0001, 0.08, 0.013, 0.03, 0.022, 0.08, 0.011, 0.022, 0.023, 0.061, 0.003, 0.011, 0.035, 0.039, 0.054, 0.06, 0.012, 0.001, 0.056, 0.049, 0.047, 0.027, 0.008, 0.006, 0.003, 0.012, 0.004, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.279, 1.922, 3.072, 0.896, 0.157, 0.085, 0.296, 0.344, 0.32, 0.001, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.012, 0.067, 0.066, 0.1, 0.11, 0.062, 0.046, 0.008, 0.029, 0.825, 0.009, 0.229, 0.032, 0.208, 0.077, 0.103, 0.118, 0.054, 0.125, 0.063, 0.016, 0.028, 0.03, 0.013, 0.01, 0.018, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 5.692, 0.585, 1.752, 0.746, 1.619, 3.647, 0.195, 0.665, 3.964, 0.001, 1.64, 1.494, 0.888, 3.068, 4.767, 1.117, 0.0001, 0.0001, 0.015, 0.006, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.003, 33.101, 10.345, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.096, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ml\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.283, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.554, 0.001, 0.034, 0.0001, 0.0001, 0.002, 0.0001, 0.013, 0.046, 0.046, 0.0001, 0.001, 0.155, 0.051, 0.434, 0.004, 0.069, 0.096, 0.051, 0.026, 0.025, 0.029, 0.025, 0.024, 0.03, 0.054, 0.011, 0.004, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.005, 0.003, 0.005, 0.002, 0.002, 0.002, 0.002, 0.002, 0.004, 0.001, 0.001, 0.002, 0.003, 0.002, 0.002, 0.004, 0.0001, 0.002, 0.005, 0.004, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.003, 0.0001, 0.001, 0.0001, 0.044, 0.007, 0.016, 0.014, 0.045, 0.007, 0.009, 0.015, 0.036, 0.001, 0.004, 0.022, 0.013, 0.031, 0.031, 0.01, 0.001, 0.031, 0.025, 0.029, 0.015, 0.004, 0.005, 0.002, 0.008, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.284, 1.637, 0.889, 0.045, 0.0001, 0.237, 0.843, 0.478, 0.108, 0.077, 0.086, 0.336, 0.062, 4.599, 0.152, 0.029, 0.008, 0.0001, 0.075, 0.022, 0.003, 1.759, 0.042, 0.219, 0.023, 0.382, 0.512, 0.004, 0.161, 0.001, 0.086, 0.887, 0.025, 0.094, 0.002, 0.484, 1.618, 0.083, 0.303, 0.146, 1.873, 0.0001, 0.931, 0.058, 0.143, 0.126, 0.78, 1.209, 1.122, 0.589, 0.667, 0.458, 22.229, 10.029, 0.199, 0.193, 0.652, 0.135, 0.025, 0.171, 0.328, 0.323, 1.631, 2.28, 0.0001, 0.0001, 0.014, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 31.391, 0.001, 0.071, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.502, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.684, 0.002, 0.094, 0.001, 0.001, 0.006, 0.001, 0.003, 0.078, 0.078, 0.001, 0.002, 0.423, 0.192, 0.522, 0.019, 0.207, 0.249, 0.16, 0.075, 0.065, 0.07, 0.06, 0.055, 0.066, 0.128, 0.025, 0.008, 0.003, 0.005, 0.004, 0.002, 0.0001, 0.018, 0.012, 0.019, 0.013, 0.012, 0.008, 0.007, 0.009, 0.026, 0.003, 0.004, 0.011, 0.017, 0.01, 0.009, 0.02, 0.002, 0.012, 0.024, 0.016, 0.006, 0.007, 0.006, 0.007, 0.003, 0.001, 0.006, 0.001, 0.006, 0.0001, 0.005, 0.0001, 0.097, 0.016, 0.039, 0.037, 0.119, 0.017, 0.023, 0.03, 0.088, 0.002, 0.012, 0.052, 0.031, 0.08, 0.086, 0.026, 0.002, 0.079, 0.064, 0.078, 0.038, 0.025, 0.012, 0.008, 0.018, 0.003, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 2.438, 1.425, 1.576, 1.589, 0.047, 1.639, 0.295, 0.416, 0.311, 0.001, 0.008, 0.672, 0.369, 2.886, 0.106, 0.163, 0.114, 0.151, 0.023, 0.067, 0.081, 0.017, 0.027, 0.033, 0.044, 0.004, 0.046, 0.028, 0.128, 0.083, 0.044, 0.031, 0.048, 0.074, 0.102, 0.063, 0.021, 0.125, 0.02, 0.022, 0.053, 1.026, 0.001, 0.019, 0.001, 0.067, 0.028, 1.192, 4.733, 1.04, 0.537, 2.615, 2.04, 0.399, 0.621, 0.396, 2.01, 1.723, 0.207, 2.589, 0.943, 3.889, 2.383, 0.107, 0.0001, 0.0001, 0.065, 0.012, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.001, 27.532, 13.908, 1.199, 1.049, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.072, 0.002, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.77, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.47, 0.002, 0.214, 0.0001, 0.0001, 0.017, 0.001, 0.035, 0.128, 0.128, 0.002, 0.001, 0.656, 0.155, 0.49, 0.006, 0.172, 0.19, 0.096, 0.052, 0.062, 0.054, 0.034, 0.043, 0.06, 0.129, 0.06, 0.015, 0.017, 0.012, 0.017, 0.0001, 0.0001, 0.018, 0.009, 0.023, 0.009, 0.011, 0.002, 0.006, 0.004, 0.035, 0.002, 0.005, 0.007, 0.014, 0.008, 0.008, 0.009, 0.001, 0.009, 0.019, 0.008, 0.005, 0.004, 0.007, 0.007, 0.001, 0.002, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.381, 0.035, 0.167, 0.122, 0.44, 0.045, 0.036, 0.034, 0.432, 0.005, 0.016, 0.206, 0.12, 0.248, 0.177, 0.096, 0.003, 0.253, 0.183, 0.236, 0.214, 0.038, 0.01, 0.011, 0.011, 0.03, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.01, 1.642, 2.712, 2.46, 0.4, 0.066, 0.487, 0.515, 0.507, 0.001, 0.001, 0.622, 0.372, 0.933, 0.029, 0.581, 0.134, 0.087, 0.042, 0.032, 0.081, 0.073, 0.022, 0.008, 0.061, 0.004, 0.139, 0.063, 0.145, 0.05, 0.043, 0.149, 0.144, 0.143, 0.069, 0.113, 0.038, 0.031, 0.007, 0.03, 0.013, 0.002, 0.001, 0.064, 0.002, 0.001, 0.029, 0.007, 3.78, 0.37, 0.558, 0.274, 1.316, 4.346, 0.072, 0.319, 3.558, 0.657, 1.356, 2.204, 1.073, 2.802, 2.13, 1.099, 0.0001, 0.0001, 0.025, 0.051, 0.091, 0.068, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.005, 0.0001, 0.008, 0.004, 27.537, 14.047, 0.001, 0.161, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.005, 0.022, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mr\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.525, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.348, 0.002, 0.043, 0.0001, 0.0001, 0.004, 0.0001, 0.024, 0.061, 0.064, 0.0001, 0.001, 0.221, 0.063, 0.539, 0.009, 0.009, 0.009, 0.006, 0.003, 0.003, 0.003, 0.003, 0.003, 0.003, 0.005, 0.03, 0.01, 0.003, 0.004, 0.003, 0.003, 0.0001, 0.008, 0.004, 0.006, 0.004, 0.003, 0.003, 0.003, 0.003, 0.007, 0.002, 0.002, 0.003, 0.006, 0.003, 0.002, 0.005, 0.0001, 0.004, 0.008, 0.009, 0.001, 0.002, 0.002, 0.0001, 0.001, 0.0001, 0.007, 0.0001, 0.007, 0.0001, 0.001, 0.0001, 0.138, 0.021, 0.046, 0.053, 0.162, 0.029, 0.028, 0.063, 0.114, 0.003, 0.011, 0.062, 0.038, 0.106, 0.103, 0.03, 0.002, 0.096, 0.09, 0.116, 0.04, 0.015, 0.019, 0.003, 0.023, 0.002, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 1.224, 0.397, 1.061, 0.056, 0.001, 0.297, 0.351, 1.664, 0.084, 0.127, 0.02, 0.461, 0.026, 2.286, 0.0001, 0.096, 0.005, 0.018, 0.001, 0.019, 0.005, 1.098, 0.145, 0.403, 0.083, 0.015, 0.659, 0.012, 0.404, 0.067, 0.014, 0.287, 0.125, 0.236, 0.039, 0.415, 24.995, 7.065, 0.585, 0.404, 1.081, 0.036, 0.727, 0.118, 0.317, 0.211, 0.844, 1.342, 1.809, 0.018, 1.056, 0.198, 0.001, 0.975, 0.327, 0.194, 1.035, 0.79, 0.001, 0.001, 0.003, 0.001, 3.71, 0.926, 0.0001, 0.0001, 0.015, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.418, 0.001, 0.048, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mrj\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.556, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.792, 0.004, 0.111, 0.0001, 0.0001, 0.008, 0.001, 0.036, 0.371, 0.372, 0.0001, 0.001, 0.508, 0.256, 0.9, 0.015, 0.334, 0.401, 0.27, 0.169, 0.152, 0.17, 0.137, 0.141, 0.168, 0.185, 0.1, 0.046, 0.009, 0.005, 0.008, 0.012, 0.0001, 0.017, 0.012, 0.012, 0.011, 0.006, 0.006, 0.008, 0.006, 0.083, 0.004, 0.011, 0.006, 0.014, 0.007, 0.024, 0.016, 0.001, 0.008, 0.014, 0.009, 0.003, 0.03, 0.002, 0.042, 0.002, 0.001, 0.008, 0.0001, 0.009, 0.0001, 0.003, 0.0001, 0.281, 0.025, 0.082, 0.065, 0.202, 0.013, 0.027, 0.052, 0.157, 0.003, 0.032, 0.08, 0.041, 0.09, 0.092, 0.03, 0.005, 0.117, 0.072, 0.076, 0.073, 0.015, 0.012, 0.004, 0.024, 0.01, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.356, 0.846, 2.179, 0.887, 0.116, 0.285, 0.312, 0.236, 2.316, 0.007, 0.004, 2.565, 0.266, 0.252, 0.05, 0.215, 0.187, 0.062, 0.078, 1.679, 0.285, 0.024, 0.005, 0.016, 0.067, 0.046, 0.237, 0.053, 0.116, 0.054, 0.059, 0.117, 0.058, 0.115, 0.145, 0.033, 0.102, 0.049, 0.064, 0.062, 0.066, 0.006, 0.001, 0.056, 0.003, 0.041, 0.007, 0.023, 2.651, 0.259, 1.194, 0.797, 1.113, 1.956, 0.572, 0.253, 2.277, 2.969, 1.78, 2.755, 1.532, 2.591, 1.704, 0.818, 0.0001, 0.0001, 0.138, 0.095, 0.012, 0.006, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.011, 0.0001, 0.008, 0.006, 24.363, 12.5, 0.002, 4.142, 0.0001, 0.0001, 0.0001, 0.001, 0.015, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.004, 0.341, 0.005, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ms\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.423, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.116, 0.004, 0.276, 0.001, 0.003, 0.028, 0.005, 0.04, 0.153, 0.154, 0.011, 0.002, 0.825, 0.313, 0.841, 0.02, 0.335, 0.324, 0.225, 0.11, 0.099, 0.112, 0.094, 0.087, 0.096, 0.171, 0.041, 0.019, 0.01, 0.005, 0.01, 0.002, 0.001, 0.327, 0.313, 0.169, 0.197, 0.08, 0.09, 0.097, 0.122, 0.22, 0.145, 0.326, 0.158, 0.369, 0.143, 0.065, 0.427, 0.013, 0.147, 0.487, 0.268, 0.071, 0.05, 0.063, 0.007, 0.038, 0.022, 0.015, 0.0001, 0.015, 0.0001, 0.002, 0.0001, 15.253, 2.008, 0.502, 3.234, 6.807, 0.209, 2.704, 2.141, 5.701, 0.605, 3.195, 3.049, 3.025, 7.562, 1.688, 2.054, 0.019, 4.172, 2.861, 3.513, 3.855, 0.159, 0.407, 0.024, 1.19, 0.123, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.025, 0.005, 0.003, 0.004, 0.003, 0.002, 0.002, 0.004, 0.003, 0.002, 0.002, 0.001, 0.002, 0.007, 0.004, 0.002, 0.001, 0.002, 0.001, 0.009, 0.003, 0.001, 0.001, 0.001, 0.002, 0.01, 0.001, 0.003, 0.004, 0.004, 0.001, 0.007, 0.031, 0.013, 0.003, 0.003, 0.003, 0.002, 0.001, 0.006, 0.007, 0.017, 0.002, 0.003, 0.001, 0.007, 0.002, 0.002, 0.004, 0.011, 0.003, 0.006, 0.005, 0.001, 0.006, 0.001, 0.004, 0.002, 0.003, 0.002, 0.008, 0.003, 0.003, 0.001, 0.0001, 0.0001, 0.034, 0.074, 0.022, 0.02, 0.0001, 0.0001, 0.001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.012, 0.015, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.024, 0.004, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mt\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.717, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.569, 0.003, 0.319, 0.001, 0.001, 0.009, 0.001, 0.699, 0.116, 0.117, 0.001, 0.002, 0.868, 2.789, 0.736, 0.014, 0.299, 0.341, 0.218, 0.093, 0.081, 0.087, 0.085, 0.082, 0.1, 0.201, 0.053, 0.022, 0.013, 0.012, 0.013, 0.002, 0.0001, 0.223, 0.171, 0.118, 0.162, 0.107, 0.236, 0.127, 0.076, 0.3, 0.048, 0.158, 0.199, 0.315, 0.08, 0.056, 0.187, 0.018, 0.103, 0.221, 0.127, 0.065, 0.054, 0.053, 0.02, 0.007, 0.009, 0.022, 0.0001, 0.023, 0.0001, 0.008, 0.002, 9.087, 1.533, 0.244, 1.812, 5.201, 1.498, 1.212, 0.809, 8.439, 2.13, 1.92, 5.784, 2.557, 4.221, 2.69, 1.16, 0.488, 3.837, 2.631, 5.521, 3.106, 0.451, 1.062, 0.484, 0.085, 0.753, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.211, 0.004, 0.004, 0.002, 0.003, 0.001, 0.001, 0.004, 0.002, 0.001, 0.016, 0.407, 0.001, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.042, 0.003, 0.001, 0.001, 0.001, 0.005, 0.141, 0.001, 0.001, 0.01, 0.01, 0.001, 0.002, 0.13, 0.527, 0.002, 0.004, 0.002, 0.001, 0.025, 1.521, 0.007, 0.014, 0.001, 0.004, 0.005, 0.008, 0.001, 0.001, 0.004, 0.005, 0.009, 0.004, 0.002, 0.002, 0.003, 0.001, 0.003, 0.01, 0.003, 0.015, 0.566, 0.003, 0.002, 0.002, 0.0001, 0.0001, 0.015, 0.129, 2.554, 0.578, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.011, 0.005, 0.011, 0.004, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.004, 0.006, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.004, 0.212, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mus\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.612, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 19.388, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.02, 0.0001, 1.02, 0.0001, 1.02, 1.02, 0.0001, 2.041, 1.02, 0.0001, 1.02, 1.02, 4.082, 1.02, 1.02, 2.041, 0.0001, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 0.0001, 1.02, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.061, 0.0001, 0.0001, 0.0001, 5.102, 0.0001, 1.02, 0.0001, 1.02, 0.0001, 5.102, 0.0001, 1.02, 1.02, 2.041, 0.0001, 0.0001, 0.0001, 2.041, 0.0001, 0.0001, 2.041, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.02, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.02, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"my\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.476, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.676, 0.0001, 0.018, 0.0001, 0.0001, 0.001, 0.0001, 0.009, 0.072, 0.072, 0.0001, 0.001, 0.013, 0.027, 0.014, 0.004, 0.007, 0.006, 0.005, 0.003, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.001, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.009, 0.007, 0.011, 0.006, 0.004, 0.004, 0.005, 0.004, 0.006, 0.002, 0.003, 0.004, 0.008, 0.005, 0.004, 0.007, 0.0001, 0.005, 0.011, 0.008, 0.003, 0.002, 0.003, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.087, 0.015, 0.033, 0.032, 0.11, 0.015, 0.02, 0.035, 0.072, 0.001, 0.01, 0.046, 0.027, 0.071, 0.073, 0.021, 0.001, 0.069, 0.054, 0.072, 0.03, 0.01, 0.011, 0.003, 0.016, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 32.171, 1.737, 0.141, 0.03, 1.382, 0.783, 0.273, 0.069, 0.03, 0.083, 0.874, 0.307, 0.061, 0.061, 0.009, 0.119, 1.037, 0.261, 0.115, 0.031, 0.966, 0.888, 0.304, 0.058, 0.131, 1.12, 0.266, 0.843, 0.619, 0.172, 1.057, 0.095, 0.006, 0.703, 0.001, 0.001, 0.009, 0.019, 0.041, 0.006, 0.0001, 0.005, 0.0001, 0.239, 1.811, 1.255, 0.357, 1.497, 0.246, 1.317, 0.249, 0.0001, 0.0001, 0.0001, 0.294, 0.751, 1.889, 0.152, 3.975, 0.6, 0.881, 0.616, 0.651, 0.004, 0.0001, 0.0001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 31.801, 0.03, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"myv\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.363, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.917, 0.015, 0.248, 0.0001, 0.0001, 0.022, 0.0001, 0.001, 0.283, 0.286, 0.002, 0.004, 0.691, 0.215, 0.812, 0.009, 0.174, 0.262, 0.16, 0.093, 0.073, 0.077, 0.073, 0.069, 0.078, 0.133, 0.142, 0.014, 0.011, 0.005, 0.01, 0.008, 0.0001, 0.003, 0.002, 0.005, 0.001, 0.001, 0.002, 0.001, 0.001, 0.012, 0.001, 0.001, 0.002, 0.003, 0.001, 0.001, 0.004, 0.0001, 0.002, 0.003, 0.002, 0.001, 0.004, 0.001, 0.007, 0.0001, 0.001, 0.004, 0.0001, 0.004, 0.0001, 0.0001, 0.003, 0.048, 0.012, 0.02, 0.007, 0.038, 0.002, 0.005, 0.006, 0.024, 0.002, 0.008, 0.023, 0.008, 0.017, 0.019, 0.008, 0.0001, 0.032, 0.018, 0.013, 0.014, 0.004, 0.001, 0.001, 0.004, 0.002, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 2.092, 2.863, 2.802, 0.895, 0.06, 0.084, 0.303, 0.361, 0.574, 0.012, 0.006, 0.456, 2.653, 0.734, 0.106, 1.014, 0.129, 0.284, 0.186, 0.058, 0.27, 0.019, 0.007, 0.024, 0.083, 0.006, 0.182, 0.079, 0.175, 0.059, 0.072, 0.148, 0.231, 0.176, 0.101, 0.047, 0.012, 0.013, 0.018, 0.046, 0.024, 0.001, 0.0001, 0.091, 0.002, 0.065, 0.014, 0.024, 3.393, 0.354, 1.588, 0.391, 1.0, 3.63, 0.2, 0.667, 2.033, 0.447, 2.062, 1.616, 1.324, 3.27, 3.572, 0.635, 0.0001, 0.0001, 0.332, 0.006, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.017, 0.0001, 0.001, 0.001, 27.959, 14.855, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.281, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.032, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"mzn\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.201, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.629, 0.002, 0.049, 0.0001, 0.0001, 0.001, 0.004, 0.002, 0.134, 0.134, 0.0001, 0.0001, 0.026, 0.054, 0.593, 0.02, 0.019, 0.017, 0.017, 0.008, 0.004, 0.006, 0.012, 0.005, 0.01, 0.015, 0.042, 0.0001, 0.001, 0.014, 0.001, 0.004, 0.0001, 0.004, 0.003, 0.005, 0.003, 0.006, 0.016, 0.002, 0.002, 0.003, 0.001, 0.001, 0.009, 0.006, 0.002, 0.001, 0.004, 0.0001, 0.003, 0.005, 0.007, 0.001, 0.001, 0.01, 0.0001, 0.001, 0.001, 0.002, 0.0001, 0.002, 0.0001, 0.009, 0.0001, 0.072, 0.016, 0.031, 0.045, 0.106, 0.011, 0.011, 0.023, 0.094, 0.004, 0.019, 0.044, 0.012, 0.044, 0.054, 0.042, 0.001, 0.056, 0.056, 0.055, 0.021, 0.007, 0.011, 0.005, 0.015, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.44, 0.427, 0.449, 0.013, 1.516, 2.042, 3.291, 3.912, 3.162, 0.001, 0.032, 0.014, 4.412, 0.002, 0.195, 0.007, 0.446, 0.17, 0.001, 0.002, 0.012, 0.004, 0.0001, 0.001, 0.045, 0.005, 0.0001, 0.006, 0.001, 0.0001, 0.0001, 0.003, 0.003, 0.013, 0.18, 0.011, 0.008, 0.001, 0.211, 5.124, 1.425, 1.013, 2.263, 0.078, 0.559, 0.214, 0.344, 2.205, 0.318, 3.605, 0.725, 1.866, 1.033, 0.295, 0.164, 0.271, 0.156, 0.676, 0.058, 0.031, 0.001, 0.001, 0.258, 0.0001, 0.0001, 0.0001, 0.056, 0.008, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.009, 0.003, 0.0001, 0.003, 0.0001, 0.001, 0.001, 0.002, 19.953, 15.923, 1.548, 5.327, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.427, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"na\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.998, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.709, 0.015, 0.646, 0.0001, 0.002, 0.0001, 0.004, 0.021, 0.659, 0.659, 0.017, 0.002, 0.883, 0.333, 1.719, 0.03, 1.451, 2.231, 1.063, 0.565, 0.61, 0.611, 0.586, 0.586, 0.597, 1.829, 0.199, 0.094, 0.009, 0.006, 0.009, 0.002, 0.0001, 0.49, 0.423, 0.263, 0.348, 0.486, 0.143, 0.225, 0.131, 0.617, 0.095, 0.263, 0.178, 0.552, 0.272, 0.136, 0.483, 0.013, 0.313, 0.36, 0.336, 0.074, 0.114, 0.249, 0.018, 0.046, 0.052, 0.006, 0.0001, 0.007, 0.0001, 0.006, 0.0001, 7.914, 1.267, 0.542, 1.393, 6.136, 0.161, 1.565, 0.525, 5.317, 0.298, 1.632, 1.173, 1.479, 6.133, 5.204, 0.602, 0.04, 3.812, 1.491, 2.75, 1.848, 0.267, 2.105, 0.037, 0.79, 0.308, 0.0001, 0.0001, 0.0001, 0.013, 0.0001, 0.299, 0.053, 0.064, 0.017, 0.038, 0.029, 0.014, 0.008, 0.026, 0.003, 0.007, 0.007, 0.016, 0.016, 0.006, 0.008, 0.013, 0.032, 0.004, 0.043, 0.108, 0.002, 0.004, 0.01, 0.017, 0.017, 0.017, 0.015, 0.011, 0.008, 0.009, 0.017, 0.085, 0.124, 0.117, 0.04, 0.025, 0.023, 0.025, 0.032, 0.017, 0.097, 0.007, 0.031, 0.013, 0.031, 0.009, 0.016, 0.079, 0.095, 0.056, 0.083, 0.021, 0.063, 0.052, 0.013, 0.046, 0.015, 0.047, 0.045, 0.034, 0.035, 0.045, 0.013, 0.0001, 0.0001, 0.04, 0.466, 0.079, 0.167, 0.001, 0.002, 0.0001, 0.023, 0.01, 0.013, 0.002, 0.001, 0.027, 0.006, 0.292, 0.12, 0.0001, 0.0001, 0.009, 0.199, 0.04, 0.007, 0.055, 0.037, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.021, 0.074, 0.244, 0.0001, 0.005, 0.01, 0.006, 0.001, 0.002, 0.006, 0.003, 0.003, 0.009, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nah\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.08, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.795, 0.004, 0.316, 0.0001, 0.0001, 0.001, 0.0001, 0.011, 0.502, 0.505, 0.006, 0.329, 1.148, 0.317, 0.599, 1.663, 0.178, 0.339, 0.18, 0.099, 0.094, 0.088, 0.135, 0.085, 0.092, 0.204, 0.686, 0.012, 0.001, 0.002, 0.002, 0.012, 0.0001, 0.583, 0.136, 0.486, 0.282, 0.369, 0.108, 0.135, 0.149, 0.382, 0.043, 0.01, 0.153, 0.41, 0.267, 0.154, 0.356, 0.041, 0.209, 0.348, 0.531, 0.077, 0.099, 0.006, 0.046, 0.078, 0.021, 0.306, 0.0001, 0.304, 0.0001, 0.018, 0.0001, 8.12, 0.52, 3.826, 1.716, 6.024, 0.239, 0.598, 2.703, 7.016, 0.169, 0.062, 5.071, 1.759, 4.856, 5.013, 1.61, 0.66, 3.183, 2.38, 4.798, 3.279, 0.368, 0.013, 0.578, 0.571, 0.892, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.023, 0.52, 0.003, 0.003, 0.002, 0.001, 0.001, 0.002, 0.002, 0.007, 0.001, 0.001, 0.005, 0.446, 0.001, 0.001, 0.001, 0.002, 0.001, 0.26, 0.002, 0.003, 0.001, 0.001, 0.001, 0.002, 0.003, 0.001, 0.002, 0.001, 0.001, 0.001, 0.003, 0.117, 0.005, 0.001, 0.004, 0.001, 0.001, 0.002, 0.003, 0.168, 0.013, 0.475, 0.001, 0.136, 0.018, 0.008, 0.004, 0.071, 0.003, 0.269, 0.002, 0.003, 0.001, 0.001, 0.003, 0.002, 0.068, 0.005, 0.005, 0.002, 0.003, 0.016, 0.0001, 0.0001, 0.033, 0.838, 1.259, 0.446, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.009, 0.004, 0.012, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.003, 0.005, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.027, 0.002, 0.008, 0.004, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nap\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.664, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.609, 0.012, 0.443, 0.0001, 0.0001, 0.005, 0.002, 3.603, 0.188, 0.187, 0.001, 0.001, 0.851, 0.111, 0.916, 0.025, 0.289, 0.464, 0.288, 0.212, 0.187, 0.195, 0.184, 0.177, 0.191, 0.229, 0.063, 0.027, 0.064, 0.004, 0.064, 0.004, 0.0001, 0.359, 0.17, 0.431, 0.088, 0.101, 0.128, 0.139, 0.019, 0.153, 0.024, 0.014, 0.18, 0.269, 0.172, 0.129, 0.252, 0.015, 0.136, 0.331, 0.141, 0.042, 0.154, 0.012, 0.021, 0.004, 0.014, 0.007, 0.0001, 0.007, 0.0001, 0.001, 0.0001, 8.472, 0.677, 3.575, 1.818, 8.836, 0.628, 1.161, 0.605, 5.326, 0.294, 0.072, 2.854, 1.959, 5.855, 5.118, 1.978, 0.107, 4.154, 2.774, 4.302, 3.256, 1.068, 0.047, 0.011, 0.049, 0.778, 0.0001, 0.014, 0.0001, 0.0001, 0.0001, 0.167, 0.005, 0.004, 0.005, 0.003, 0.002, 0.001, 0.001, 0.017, 0.002, 0.001, 0.001, 0.001, 0.003, 0.001, 0.001, 0.003, 0.001, 0.001, 0.008, 0.005, 0.001, 0.001, 0.001, 0.033, 0.118, 0.001, 0.001, 0.005, 0.005, 0.001, 0.002, 0.266, 0.085, 0.051, 0.002, 0.003, 0.001, 0.003, 0.003, 0.62, 0.161, 0.023, 0.139, 0.069, 0.03, 0.001, 0.002, 0.025, 0.004, 0.164, 0.026, 0.08, 0.002, 0.003, 0.002, 0.002, 0.107, 0.016, 0.007, 0.005, 0.003, 0.002, 0.002, 0.0001, 0.0001, 0.057, 1.779, 0.007, 0.055, 0.0001, 0.001, 0.0001, 0.003, 0.002, 0.003, 0.0001, 0.0001, 0.013, 0.006, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.002, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.165, 0.003, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nds\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.919, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.282, 0.001, 0.235, 0.0001, 0.0001, 0.09, 0.002, 0.046, 0.155, 0.155, 0.017, 0.001, 0.777, 0.214, 1.137, 0.014, 0.414, 0.571, 0.279, 0.157, 0.152, 0.165, 0.151, 0.162, 0.212, 0.299, 0.042, 0.019, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.383, 0.397, 0.141, 0.527, 0.184, 0.228, 0.238, 0.278, 0.228, 0.153, 0.317, 0.238, 0.331, 0.224, 0.164, 0.212, 0.007, 0.201, 0.654, 0.206, 0.119, 0.194, 0.231, 0.003, 0.008, 0.039, 0.011, 0.0001, 0.011, 0.0001, 0.001, 0.0001, 4.393, 1.051, 1.267, 3.394, 10.917, 0.767, 1.396, 2.581, 3.914, 0.085, 1.325, 2.618, 1.593, 8.321, 3.314, 0.889, 0.009, 5.125, 3.768, 5.421, 2.363, 1.177, 0.929, 0.056, 0.171, 0.239, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.454, 0.002, 0.002, 0.001, 0.006, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.023, 0.001, 0.0001, 0.022, 0.001, 0.009, 0.334, 0.001, 0.001, 0.057, 0.001, 0.038, 0.018, 0.048, 0.004, 0.001, 0.001, 0.208, 0.002, 0.001, 0.001, 0.002, 0.009, 0.001, 0.001, 0.0001, 0.002, 0.0001, 0.001, 0.005, 0.002, 0.014, 0.003, 0.002, 0.002, 0.82, 0.001, 0.004, 0.001, 0.001, 0.001, 0.763, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.055, 1.884, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.007, 0.003, 0.008, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.454, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ne\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.49, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.629, 0.002, 0.033, 0.0001, 0.0001, 0.006, 0.0001, 0.018, 0.053, 0.057, 0.0001, 0.001, 0.2, 0.042, 0.073, 0.008, 0.003, 0.003, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.015, 0.002, 0.005, 0.002, 0.006, 0.002, 0.0001, 0.004, 0.003, 0.004, 0.002, 0.002, 0.002, 0.002, 0.002, 0.003, 0.001, 0.001, 0.002, 0.002, 0.002, 0.002, 0.003, 0.0001, 0.002, 0.004, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 0.056, 0.012, 0.021, 0.02, 0.066, 0.011, 0.012, 0.023, 0.048, 0.001, 0.005, 0.027, 0.017, 0.042, 0.043, 0.015, 0.001, 0.044, 0.036, 0.045, 0.017, 0.005, 0.008, 0.001, 0.01, 0.001, 0.0001, 0.009, 0.0001, 0.0001, 0.0001, 0.608, 0.76, 0.405, 0.065, 0.0001, 0.231, 0.124, 1.05, 0.333, 0.225, 0.003, 1.089, 0.054, 2.553, 0.0001, 0.268, 0.005, 0.0001, 0.0001, 0.016, 0.009, 1.681, 0.188, 0.574, 0.05, 0.059, 0.235, 0.302, 0.411, 0.024, 0.038, 0.296, 0.063, 0.16, 0.029, 0.16, 24.986, 7.481, 0.637, 0.298, 1.766, 0.034, 0.895, 0.142, 0.406, 0.37, 1.169, 0.857, 2.172, 0.0001, 1.023, 0.0001, 0.0001, 0.652, 0.263, 0.209, 1.099, 0.646, 0.0001, 0.0001, 0.001, 0.001, 3.096, 1.51, 0.0001, 0.0001, 0.006, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.857, 0.0001, 0.028, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"new\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.658, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.736, 0.0001, 0.005, 0.0001, 0.0001, 0.016, 0.0001, 0.016, 0.053, 0.053, 0.0001, 0.0001, 0.168, 0.064, 0.05, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.014, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.003, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.002, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.045, 0.006, 0.015, 0.015, 0.048, 0.008, 0.009, 0.021, 0.034, 0.001, 0.003, 0.019, 0.011, 0.032, 0.03, 0.01, 0.0001, 0.03, 0.026, 0.034, 0.014, 0.004, 0.005, 0.001, 0.007, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.748, 1.473, 1.078, 0.313, 0.0001, 0.165, 0.087, 0.934, 0.049, 0.145, 0.004, 0.213, 0.295, 3.035, 0.001, 0.021, 0.01, 0.0001, 0.001, 0.003, 0.002, 0.891, 0.378, 1.026, 0.007, 0.007, 0.145, 0.227, 0.557, 0.002, 0.008, 0.138, 0.03, 0.275, 0.076, 0.203, 24.519, 8.651, 0.655, 0.317, 1.238, 0.066, 0.765, 0.114, 0.288, 0.474, 0.695, 2.038, 1.25, 0.006, 0.967, 0.005, 0.016, 1.209, 0.15, 0.223, 0.893, 0.295, 0.0001, 0.001, 0.016, 0.0001, 3.268, 1.125, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.648, 0.0001, 0.246, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ng\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.332, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.852, 0.014, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.028, 0.028, 0.0001, 0.0001, 0.569, 0.014, 0.833, 0.0001, 0.0001, 0.028, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.042, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.291, 0.028, 0.014, 0.069, 0.125, 0.0001, 0.194, 0.153, 0.5, 0.042, 0.069, 0.0001, 0.056, 0.153, 0.402, 0.083, 0.0001, 0.0001, 0.014, 0.18, 0.222, 0.0001, 0.222, 0.0001, 0.014, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.728, 1.221, 0.236, 1.443, 7.106, 0.347, 2.859, 3.65, 4.136, 0.125, 4.316, 3.539, 3.983, 7.412, 7.883, 1.596, 0.0001, 1.138, 1.901, 3.511, 6.62, 0.402, 2.776, 0.0001, 2.11, 0.194, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.028, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.014, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.056, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.056, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.028, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nov\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.223, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.739, 0.005, 0.371, 0.0001, 0.0001, 0.004, 0.0001, 0.014, 0.258, 0.258, 0.003, 0.008, 0.827, 0.256, 1.132, 0.016, 0.643, 0.521, 0.435, 0.122, 0.19, 0.117, 0.125, 0.112, 0.14, 0.247, 0.122, 0.023, 0.02, 0.012, 0.021, 0.004, 0.0001, 0.495, 0.126, 0.101, 0.11, 0.205, 0.084, 0.113, 0.079, 0.113, 0.072, 0.274, 0.517, 0.205, 0.19, 0.072, 0.148, 0.01, 0.107, 0.475, 0.124, 0.103, 0.078, 0.049, 0.006, 0.028, 0.032, 0.002, 0.0001, 0.003, 0.0001, 0.002, 0.0001, 6.407, 1.088, 0.275, 3.233, 10.596, 0.875, 0.958, 0.605, 7.974, 0.212, 2.738, 4.237, 2.737, 5.182, 4.585, 1.557, 0.08, 4.568, 4.834, 4.299, 2.875, 0.823, 0.156, 0.296, 0.238, 0.085, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.026, 0.009, 0.01, 0.005, 0.006, 0.003, 0.001, 0.003, 0.003, 0.003, 0.001, 0.001, 0.003, 0.004, 0.0001, 0.0001, 0.003, 0.001, 0.003, 0.016, 0.003, 0.003, 0.001, 0.001, 0.001, 0.007, 0.002, 0.003, 0.003, 0.007, 0.001, 0.0001, 0.012, 0.008, 0.002, 0.005, 0.005, 0.002, 0.003, 0.003, 0.008, 0.015, 0.002, 0.001, 0.004, 0.007, 0.004, 0.004, 0.003, 0.005, 0.01, 0.013, 0.003, 0.002, 0.006, 0.004, 0.012, 0.004, 0.008, 0.008, 0.012, 0.008, 0.004, 0.006, 0.0001, 0.0001, 0.013, 0.071, 0.012, 0.013, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.04, 0.019, 0.02, 0.007, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.008, 0.025, 0.004, 0.0001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nrm\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.521, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.376, 0.004, 0.467, 0.0001, 0.0001, 0.003, 0.0001, 1.947, 0.163, 0.162, 0.0001, 0.0001, 0.761, 0.157, 0.914, 0.012, 1.102, 1.934, 0.564, 0.483, 0.476, 0.487, 0.557, 0.61, 0.644, 0.66, 0.069, 0.027, 0.003, 0.001, 0.003, 0.001, 0.0001, 0.78, 0.094, 0.435, 0.253, 0.108, 0.06, 0.094, 0.042, 0.231, 0.101, 0.009, 0.332, 0.216, 0.104, 0.043, 0.114, 0.015, 0.096, 0.165, 0.06, 0.048, 0.107, 0.012, 0.147, 0.029, 0.002, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 4.848, 0.485, 1.639, 2.302, 7.56, 0.544, 0.696, 1.469, 3.879, 0.15, 0.04, 3.065, 1.372, 5.618, 3.164, 1.345, 0.499, 2.846, 4.958, 4.48, 3.809, 0.712, 0.015, 0.135, 0.467, 0.062, 0.0001, 1.427, 0.0001, 0.0001, 0.0001, 0.085, 0.003, 0.016, 0.002, 0.004, 0.002, 0.001, 0.0001, 0.002, 0.014, 0.026, 0.0001, 0.001, 0.001, 0.016, 0.0001, 0.001, 0.001, 0.001, 0.016, 0.002, 0.001, 0.0001, 0.0001, 0.002, 0.06, 0.0001, 0.023, 0.002, 0.001, 0.0001, 0.001, 0.219, 0.004, 0.233, 0.004, 0.005, 0.011, 0.0001, 0.016, 0.454, 2.065, 0.259, 0.013, 0.002, 0.004, 0.368, 0.008, 0.001, 0.008, 0.002, 0.004, 0.081, 0.004, 0.002, 0.012, 0.002, 0.151, 0.004, 0.112, 0.003, 0.005, 0.001, 0.003, 0.0001, 0.0001, 0.015, 4.079, 0.017, 0.014, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.029, 0.013, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.077, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nso\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.78, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.755, 0.002, 0.038, 0.001, 0.0001, 0.004, 0.0001, 0.009, 0.457, 0.457, 0.0001, 0.0001, 0.676, 0.052, 0.694, 0.005, 0.466, 0.863, 0.377, 0.269, 0.237, 0.244, 0.243, 0.245, 0.241, 0.264, 0.019, 0.004, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.278, 0.349, 0.291, 0.135, 0.079, 0.05, 0.105, 0.055, 0.212, 0.029, 0.164, 0.314, 0.912, 0.402, 0.025, 0.107, 0.005, 0.046, 0.407, 0.145, 0.02, 0.095, 0.034, 0.21, 0.005, 0.034, 0.003, 0.0001, 0.003, 0.0001, 0.001, 0.0001, 11.556, 1.304, 0.158, 0.76, 9.745, 0.742, 5.815, 1.511, 2.097, 0.062, 3.221, 3.795, 3.273, 4.341, 8.239, 1.391, 0.015, 2.291, 2.998, 2.898, 0.946, 0.091, 3.286, 0.014, 0.713, 0.058, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.019, 0.718, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.045, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.083, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.134, 0.0001, 0.733, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"nv\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.509, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.471, 0.0001, 0.553, 0.0001, 0.0001, 0.002, 0.0001, 0.001, 0.132, 0.131, 0.0001, 0.0001, 0.333, 0.05, 0.853, 0.008, 0.262, 0.198, 0.155, 0.093, 0.082, 0.107, 0.089, 0.066, 0.073, 0.069, 0.012, 0.267, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.105, 0.358, 0.12, 0.313, 0.022, 0.005, 0.023, 0.24, 0.014, 0.023, 0.045, 0.016, 0.036, 0.298, 0.014, 0.019, 0.001, 0.01, 0.1, 0.28, 0.004, 0.005, 0.04, 0.001, 0.048, 0.004, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 5.817, 1.241, 0.605, 3.905, 1.9, 0.016, 1.415, 4.266, 6.018, 0.364, 1.038, 1.394, 0.149, 2.559, 2.764, 0.063, 0.003, 0.149, 2.248, 2.042, 0.092, 0.019, 0.145, 0.037, 1.033, 1.245, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.08, 1.403, 1.567, 0.013, 0.082, 1.105, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.001, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.067, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.294, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 2.758, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 2.445, 0.0001, 0.311, 0.0001, 4.591, 0.0001, 0.504, 0.001, 0.001, 0.001, 2.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.013, 4.172, 0.001, 0.0001, 0.013, 0.0001, 0.0001, 0.002, 11.893, 1.899, 1.744, 0.0001, 0.311, 0.0001, 0.0001, 4.171, 0.0001, 1.234, 0.0001, 0.002, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.08, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ny\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.625, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.431, 0.001, 0.161, 0.0001, 0.001, 0.013, 0.004, 0.199, 0.151, 0.149, 0.001, 0.0001, 0.726, 0.052, 1.005, 0.011, 0.425, 0.321, 0.242, 0.114, 0.121, 0.146, 0.109, 0.097, 0.1, 0.188, 0.119, 0.008, 0.003, 0.006, 0.003, 0.001, 0.0001, 0.324, 0.194, 0.362, 0.113, 0.083, 0.06, 0.055, 0.057, 0.099, 0.059, 0.134, 0.101, 0.607, 0.172, 0.041, 0.204, 0.005, 0.064, 0.151, 0.1, 0.088, 0.025, 0.048, 0.003, 0.047, 0.083, 0.018, 0.0001, 0.019, 0.0001, 0.0001, 0.003, 13.746, 1.132, 1.623, 2.856, 4.347, 0.427, 1.15, 2.997, 7.993, 0.223, 3.837, 3.15, 3.985, 6.204, 4.4, 1.606, 0.018, 2.007, 1.898, 3.156, 4.108, 0.173, 2.53, 0.014, 1.184, 1.868, 0.0001, 0.0001, 0.001, 0.002, 0.0001, 0.066, 0.003, 0.003, 0.001, 0.003, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.004, 0.001, 0.001, 0.0001, 0.0001, 0.004, 0.05, 0.0001, 0.0001, 0.006, 0.005, 0.001, 0.001, 0.013, 0.003, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.013, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.002, 0.004, 0.001, 0.006, 0.001, 0.06, 0.0001, 0.011, 0.002, 0.003, 0.003, 0.003, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.023, 0.029, 0.004, 0.064, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.001, 0.0001, 0.0001, 0.009, 0.002, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.066, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"oc\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.196, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.065, 0.002, 0.316, 0.046, 0.0001, 0.007, 0.001, 0.708, 0.193, 0.193, 0.002, 0.001, 0.913, 0.337, 0.785, 0.01, 0.177, 0.339, 0.143, 0.091, 0.092, 0.097, 0.087, 0.093, 0.11, 0.155, 0.065, 0.02, 0.037, 0.032, 0.038, 0.002, 0.0001, 0.316, 0.163, 0.28, 0.118, 0.18, 0.101, 0.108, 0.046, 0.126, 0.059, 0.019, 0.366, 0.206, 0.087, 0.061, 0.2, 0.02, 0.116, 0.247, 0.092, 0.045, 0.108, 0.016, 0.03, 0.009, 0.007, 0.013, 0.0001, 0.013, 0.0001, 0.002, 0.0001, 9.22, 0.793, 2.634, 3.53, 8.653, 0.714, 1.084, 0.594, 5.176, 0.154, 0.069, 4.196, 2.017, 5.599, 4.023, 1.809, 0.517, 4.973, 5.482, 4.438, 3.287, 0.768, 0.022, 0.148, 0.146, 0.139, 0.0001, 0.011, 0.002, 0.002, 0.0001, 0.071, 0.004, 0.003, 0.002, 0.001, 0.001, 0.002, 0.002, 0.007, 0.009, 0.001, 0.001, 0.001, 0.004, 0.001, 0.001, 0.001, 0.001, 0.006, 0.007, 0.005, 0.001, 0.0001, 0.0001, 0.001, 0.051, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.145, 0.074, 0.024, 0.002, 0.002, 0.001, 0.002, 0.134, 0.929, 0.452, 0.016, 0.026, 0.001, 0.096, 0.005, 0.03, 0.005, 0.004, 0.448, 0.027, 0.01, 0.002, 0.002, 0.002, 0.002, 0.004, 0.011, 0.027, 0.011, 0.002, 0.002, 0.002, 0.0001, 0.0001, 0.068, 2.417, 0.003, 0.007, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.008, 0.004, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.067, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"olo\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.555, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.165, 0.001, 0.091, 0.0001, 0.0001, 0.009, 0.001, 0.061, 0.221, 0.221, 0.0001, 0.001, 0.891, 0.306, 1.442, 0.031, 0.305, 0.513, 0.256, 0.174, 0.154, 0.153, 0.124, 0.134, 0.157, 0.296, 0.102, 0.011, 0.01, 0.003, 0.01, 0.002, 0.001, 0.151, 0.077, 0.024, 0.041, 0.069, 0.036, 0.06, 0.101, 0.085, 0.112, 0.389, 0.128, 0.177, 0.115, 0.068, 0.258, 0.001, 0.092, 0.352, 0.141, 0.032, 0.27, 0.012, 0.021, 0.024, 0.01, 0.006, 0.0001, 0.006, 0.0001, 0.001, 0.005, 7.441, 0.351, 0.076, 1.841, 5.414, 0.122, 0.914, 2.187, 8.145, 1.143, 3.3, 4.3, 1.89, 6.075, 4.589, 1.29, 0.001, 2.71, 3.519, 3.872, 5.598, 2.436, 0.016, 0.008, 1.054, 0.985, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.389, 0.086, 0.073, 0.032, 0.01, 0.011, 0.006, 0.01, 0.008, 0.001, 0.002, 0.006, 0.036, 0.349, 0.005, 0.017, 0.009, 0.004, 0.01, 0.105, 0.013, 0.002, 0.002, 0.002, 0.005, 0.148, 0.017, 0.006, 0.011, 0.056, 0.001, 0.018, 0.051, 0.118, 0.003, 0.002, 1.86, 0.003, 0.001, 0.007, 0.003, 0.003, 0.001, 0.01, 0.001, 0.003, 0.001, 0.002, 0.143, 0.012, 0.077, 0.019, 0.054, 0.096, 0.298, 0.031, 0.077, 0.038, 0.064, 0.07, 0.021, 0.096, 0.259, 0.018, 0.0001, 0.0001, 0.075, 2.173, 0.359, 0.275, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.002, 0.001, 0.0001, 0.01, 0.006, 0.977, 0.34, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.008, 0.007, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.004, 0.001, 0.32, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"om\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.856, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.47, 0.007, 0.135, 0.001, 0.001, 0.02, 0.001, 0.415, 0.149, 0.148, 0.003, 0.001, 0.598, 0.088, 0.774, 0.024, 0.177, 0.196, 0.107, 0.055, 0.058, 0.069, 0.055, 0.055, 0.062, 0.111, 0.03, 0.031, 0.014, 0.003, 0.015, 0.004, 0.0001, 0.377, 0.227, 0.059, 0.14, 0.066, 0.078, 0.179, 0.124, 0.131, 0.063, 0.149, 0.064, 0.172, 0.076, 0.188, 0.061, 0.049, 0.057, 0.159, 0.099, 0.042, 0.016, 0.108, 0.015, 0.076, 0.01, 0.011, 0.0001, 0.011, 0.0001, 0.003, 0.13, 18.959, 2.318, 0.672, 2.536, 5.221, 1.607, 1.449, 2.234, 8.071, 0.935, 2.586, 2.201, 2.811, 5.266, 4.391, 0.29, 0.68, 3.658, 3.072, 4.065, 4.017, 0.087, 0.574, 0.143, 1.425, 0.099, 0.01, 0.001, 0.01, 0.0001, 0.0001, 0.186, 0.003, 0.002, 0.003, 0.006, 0.002, 0.002, 0.001, 0.004, 0.002, 0.003, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.006, 0.004, 0.0001, 0.0001, 0.0001, 0.007, 0.12, 0.0001, 0.0001, 0.011, 0.011, 0.005, 0.0001, 0.099, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.03, 0.008, 0.002, 0.002, 0.003, 0.001, 0.001, 0.002, 0.001, 0.002, 0.003, 0.004, 0.003, 0.002, 0.001, 0.001, 0.002, 0.001, 0.001, 0.003, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.105, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.002, 0.0001, 0.0001, 0.007, 0.004, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.024, 0.018, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.191, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"or\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.414, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.789, 0.001, 0.049, 0.0001, 0.0001, 0.027, 0.0001, 0.016, 0.066, 0.066, 0.001, 0.0001, 0.194, 0.029, 0.065, 0.008, 0.012, 0.014, 0.009, 0.005, 0.004, 0.005, 0.004, 0.004, 0.004, 0.006, 0.014, 0.004, 0.003, 0.001, 0.003, 0.0001, 0.0001, 0.006, 0.004, 0.006, 0.004, 0.003, 0.002, 0.002, 0.003, 0.007, 0.001, 0.002, 0.003, 0.005, 0.003, 0.003, 0.005, 0.0001, 0.003, 0.007, 0.006, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.081, 0.011, 0.027, 0.026, 0.08, 0.013, 0.015, 0.029, 0.067, 0.002, 0.006, 0.037, 0.021, 0.058, 0.062, 0.019, 0.001, 0.059, 0.056, 0.058, 0.033, 0.007, 0.008, 0.003, 0.014, 0.002, 0.0001, 0.009, 0.0001, 0.0001, 0.0001, 0.461, 0.87, 0.209, 0.086, 0.0001, 0.314, 0.231, 1.688, 0.023, 0.138, 0.001, 0.379, 0.073, 2.56, 0.0001, 0.427, 0.002, 0.0001, 0.0001, 0.16, 0.011, 1.385, 0.13, 0.389, 0.041, 0.233, 0.239, 0.105, 0.371, 0.014, 0.058, 0.871, 0.144, 0.225, 0.017, 0.346, 1.495, 0.883, 0.609, 0.353, 1.21, 0.043, 0.827, 0.113, 24.372, 7.183, 0.945, 0.296, 2.678, 0.059, 0.571, 0.283, 0.0001, 0.05, 0.328, 0.264, 0.929, 0.701, 0.0001, 0.0001, 0.094, 0.0001, 2.794, 2.229, 0.0001, 0.0001, 0.045, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.66, 0.0001, 0.069, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"os\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.314, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.198, 0.001, 0.075, 0.0001, 0.0001, 0.009, 0.0001, 0.003, 0.221, 0.221, 0.0001, 0.001, 0.656, 0.268, 0.647, 0.003, 0.149, 0.239, 0.119, 0.07, 0.068, 0.074, 0.065, 0.065, 0.08, 0.138, 0.043, 0.023, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.003, 0.003, 0.004, 0.002, 0.002, 0.002, 0.002, 0.001, 0.024, 0.001, 0.001, 0.002, 0.003, 0.002, 0.002, 0.003, 0.0001, 0.002, 0.005, 0.002, 0.001, 0.007, 0.001, 0.014, 0.001, 0.0001, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 0.032, 0.005, 0.009, 0.008, 0.024, 0.004, 0.005, 0.006, 0.02, 0.001, 0.005, 0.014, 0.008, 0.019, 0.019, 0.004, 0.0001, 0.021, 0.013, 0.014, 0.012, 0.003, 0.002, 0.001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.723, 1.959, 2.433, 1.742, 0.485, 1.045, 0.733, 0.104, 0.039, 0.003, 0.515, 3.703, 0.088, 0.047, 0.033, 0.054, 0.152, 0.11, 0.038, 0.09, 0.183, 0.021, 0.005, 0.03, 0.137, 0.06, 0.132, 0.043, 0.092, 0.063, 0.026, 0.055, 0.062, 0.15, 0.074, 0.109, 0.063, 0.115, 4.882, 0.027, 0.022, 0.001, 0.001, 0.076, 0.0001, 0.018, 0.005, 0.007, 3.663, 0.542, 0.469, 1.346, 2.223, 0.835, 0.243, 0.949, 1.778, 1.262, 0.952, 1.134, 1.447, 2.532, 1.739, 0.347, 0.0001, 0.0001, 0.16, 4.829, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.01, 0.0001, 0.002, 0.001, 23.204, 15.519, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.01, 0.127, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pa\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.424, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.354, 0.003, 0.052, 0.0001, 0.0001, 0.005, 0.0001, 0.026, 0.07, 0.07, 0.0001, 0.001, 0.252, 0.083, 0.057, 0.008, 0.09, 0.134, 0.073, 0.034, 0.034, 0.037, 0.032, 0.033, 0.039, 0.073, 0.023, 0.007, 0.005, 0.003, 0.005, 0.001, 0.0001, 0.004, 0.002, 0.004, 0.002, 0.003, 0.002, 0.002, 0.002, 0.006, 0.001, 0.001, 0.002, 0.003, 0.002, 0.002, 0.003, 0.0001, 0.002, 0.005, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.007, 0.0001, 0.007, 0.0001, 0.002, 0.003, 0.035, 0.006, 0.011, 0.011, 0.037, 0.006, 0.008, 0.011, 0.028, 0.001, 0.004, 0.018, 0.013, 0.026, 0.027, 0.009, 0.001, 0.025, 0.019, 0.024, 0.012, 0.004, 0.004, 0.001, 0.005, 0.001, 0.0001, 0.008, 0.0001, 0.0001, 0.0001, 1.534, 0.423, 1.17, 0.001, 0.001, 0.437, 0.548, 1.695, 0.606, 0.237, 0.024, 0.573, 0.09, 0.23, 0.001, 0.065, 0.035, 0.0001, 0.001, 0.028, 0.011, 1.172, 0.229, 0.428, 0.077, 0.013, 0.442, 0.041, 0.755, 0.032, 0.001, 0.286, 0.066, 0.192, 0.017, 0.267, 1.499, 0.487, 1.308, 0.154, 24.532, 6.371, 0.647, 0.143, 0.447, 0.172, 0.696, 0.068, 2.373, 0.727, 0.943, 0.005, 0.001, 0.891, 0.001, 0.001, 1.461, 1.12, 0.001, 0.001, 0.465, 0.001, 2.611, 1.487, 0.0001, 0.0001, 0.039, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.001, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.006, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 30.092, 0.001, 0.038, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pag\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.03, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.592, 0.019, 0.241, 0.0001, 0.0001, 0.002, 0.0001, 0.022, 0.579, 0.579, 0.001, 0.002, 1.012, 0.179, 1.28, 0.004, 0.607, 0.517, 0.439, 0.246, 0.233, 0.22, 0.206, 0.189, 0.188, 0.203, 0.287, 0.029, 0.2, 0.003, 0.2, 0.007, 0.0001, 0.514, 0.233, 0.299, 0.377, 0.293, 0.091, 0.061, 0.039, 0.141, 0.067, 0.298, 0.143, 0.228, 0.153, 0.107, 0.323, 0.017, 0.066, 0.835, 0.144, 0.158, 0.036, 0.141, 0.001, 0.016, 0.024, 0.044, 0.0001, 0.045, 0.0001, 0.001, 0.0001, 14.553, 2.155, 0.612, 2.213, 4.092, 0.158, 2.144, 0.652, 5.448, 0.026, 2.943, 4.371, 1.62, 6.468, 4.169, 1.489, 0.134, 1.92, 4.171, 4.111, 1.822, 0.133, 0.774, 0.019, 2.966, 0.181, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.082, 0.003, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.005, 0.002, 0.0001, 0.001, 0.001, 0.001, 0.048, 0.0001, 0.001, 0.01, 0.009, 0.0001, 0.0001, 0.004, 0.017, 0.0001, 0.001, 0.002, 0.001, 0.004, 0.002, 0.001, 0.008, 0.001, 0.001, 0.001, 0.005, 0.0001, 0.001, 0.003, 0.012, 0.002, 0.005, 0.002, 0.001, 0.001, 0.001, 0.004, 0.001, 0.005, 0.001, 0.001, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.004, 0.049, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.009, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.003, 0.075, 0.004, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pam\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.69, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.089, 0.008, 0.379, 0.001, 0.002, 0.006, 0.002, 0.032, 0.278, 0.278, 0.002, 0.002, 1.032, 0.272, 0.824, 0.024, 0.328, 0.294, 0.247, 0.119, 0.115, 0.114, 0.097, 0.092, 0.106, 0.173, 0.084, 0.034, 0.037, 0.005, 0.037, 0.004, 0.0001, 0.446, 0.258, 0.283, 0.225, 0.147, 0.167, 0.119, 0.095, 0.427, 0.078, 0.159, 0.17, 0.339, 0.137, 0.085, 0.26, 0.014, 0.119, 0.316, 0.173, 0.073, 0.129, 0.078, 0.01, 0.038, 0.027, 0.056, 0.0001, 0.058, 0.0001, 0.003, 0.0001, 11.717, 1.42, 1.079, 1.934, 5.65, 0.412, 5.535, 0.984, 6.498, 0.051, 2.215, 3.499, 2.546, 9.826, 2.443, 1.882, 0.041, 3.326, 2.846, 3.809, 3.421, 0.311, 0.524, 0.064, 1.429, 0.196, 0.0001, 0.012, 0.001, 0.0001, 0.0001, 0.064, 0.007, 0.004, 0.005, 0.004, 0.002, 0.003, 0.002, 0.002, 0.001, 0.001, 0.002, 0.002, 0.006, 0.001, 0.001, 0.002, 0.001, 0.002, 0.019, 0.004, 0.002, 0.002, 0.002, 0.003, 0.009, 0.002, 0.002, 0.017, 0.008, 0.009, 0.013, 0.012, 0.022, 0.007, 0.002, 0.067, 0.005, 0.005, 0.005, 0.009, 0.033, 0.004, 0.003, 0.003, 0.006, 0.006, 0.004, 0.01, 0.012, 0.01, 0.01, 0.006, 0.003, 0.032, 0.002, 0.004, 0.003, 0.008, 0.007, 0.067, 0.001, 0.005, 0.003, 0.0001, 0.0001, 0.014, 0.263, 0.005, 0.006, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.008, 0.006, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.04, 0.011, 0.063, 0.0001, 0.001, 0.004, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pap\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.577, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 17.041, 0.006, 0.256, 0.002, 0.001, 0.008, 0.005, 0.088, 0.145, 0.144, 0.001, 0.001, 0.829, 0.082, 0.948, 0.014, 0.296, 0.379, 0.247, 0.133, 0.132, 0.125, 0.108, 0.098, 0.111, 0.184, 0.198, 0.046, 0.009, 0.003, 0.009, 0.012, 0.0001, 0.325, 0.157, 0.17, 0.173, 0.346, 0.09, 0.081, 0.124, 0.155, 0.09, 0.129, 0.105, 0.233, 0.162, 0.14, 0.176, 0.005, 0.132, 0.285, 0.134, 0.07, 0.056, 0.051, 0.003, 0.069, 0.012, 0.014, 0.0001, 0.014, 0.0001, 0.003, 0.0001, 10.584, 1.755, 0.96, 3.481, 6.611, 0.57, 0.793, 1.086, 6.922, 0.094, 2.168, 2.197, 2.209, 6.462, 5.124, 1.889, 0.017, 4.387, 3.838, 4.459, 3.468, 0.348, 0.249, 0.043, 0.415, 0.112, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.051, 0.006, 0.003, 0.006, 0.004, 0.001, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.012, 0.002, 0.005, 0.003, 0.001, 0.001, 0.001, 0.006, 0.016, 0.001, 0.001, 0.01, 0.01, 0.001, 0.001, 0.023, 0.288, 0.003, 0.003, 0.003, 0.001, 0.002, 0.007, 0.143, 0.171, 0.002, 0.003, 0.001, 0.133, 0.0001, 0.002, 0.003, 0.162, 0.171, 0.076, 0.001, 0.002, 0.002, 0.001, 0.004, 0.053, 0.027, 0.003, 0.086, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.023, 1.326, 0.004, 0.005, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.007, 0.004, 0.023, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.007, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.007, 0.048, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pcd\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.27, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.657, 0.008, 0.673, 0.0001, 0.0001, 0.003, 0.004, 1.579, 0.485, 0.482, 0.004, 0.002, 0.764, 0.743, 0.844, 0.029, 0.205, 0.439, 0.185, 0.104, 0.105, 0.099, 0.112, 0.109, 0.159, 0.228, 0.141, 0.017, 0.014, 0.103, 0.015, 0.005, 0.0001, 0.322, 0.315, 0.578, 0.163, 0.158, 0.188, 0.128, 0.139, 0.259, 0.095, 0.035, 0.293, 0.23, 0.148, 0.082, 0.416, 0.024, 0.141, 0.362, 0.132, 0.056, 0.121, 0.049, 0.026, 0.011, 0.008, 0.008, 0.0001, 0.009, 0.0001, 0.004, 0.004, 4.164, 0.548, 3.109, 2.876, 7.669, 0.633, 0.742, 2.062, 6.133, 0.168, 0.261, 3.265, 1.584, 5.6, 3.825, 1.718, 0.299, 3.984, 4.345, 3.994, 3.516, 0.729, 0.062, 0.099, 0.331, 0.121, 0.0001, 0.008, 0.001, 0.001, 0.0001, 0.34, 0.006, 0.009, 0.004, 0.003, 0.002, 0.003, 0.003, 0.01, 0.086, 0.002, 0.001, 0.004, 0.005, 0.003, 0.002, 0.003, 0.002, 0.002, 0.018, 0.009, 0.001, 0.001, 0.002, 0.003, 0.283, 0.001, 0.003, 0.003, 0.002, 0.001, 0.001, 0.283, 0.006, 0.038, 0.003, 0.005, 0.006, 0.003, 0.025, 0.548, 2.644, 0.014, 0.04, 0.002, 0.005, 0.009, 0.049, 0.019, 0.006, 0.015, 0.007, 0.051, 0.004, 0.002, 0.022, 0.007, 0.018, 0.005, 0.043, 0.008, 0.005, 0.004, 0.007, 0.0001, 0.0001, 0.109, 3.762, 0.007, 0.017, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.003, 0.0001, 0.0001, 0.018, 0.008, 0.022, 0.007, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.009, 0.005, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.006, 0.329, 0.001, 0.002, 0.009, 0.006, 0.002, 0.003, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pdc\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.347, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.345, 0.014, 0.296, 0.0001, 0.002, 0.001, 0.004, 0.083, 0.252, 0.251, 0.004, 0.0001, 0.899, 0.34, 1.232, 0.016, 0.318, 0.569, 0.233, 0.139, 0.122, 0.133, 0.151, 0.187, 0.197, 0.319, 0.11, 0.028, 0.009, 0.001, 0.009, 0.014, 0.001, 0.435, 0.396, 0.269, 0.612, 0.446, 0.261, 0.34, 0.266, 0.158, 0.126, 0.345, 0.316, 0.408, 0.189, 0.112, 0.446, 0.018, 0.164, 0.922, 0.173, 0.1, 0.127, 0.257, 0.003, 0.091, 0.087, 0.001, 0.0001, 0.002, 0.0001, 0.002, 0.001, 5.822, 0.784, 2.856, 3.127, 10.434, 0.913, 1.544, 3.717, 6.407, 0.033, 0.633, 2.751, 1.821, 6.435, 2.483, 0.545, 0.012, 4.834, 4.912, 3.94, 2.397, 0.665, 1.309, 0.053, 0.442, 0.521, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.13, 0.003, 0.001, 0.002, 0.004, 0.002, 0.002, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.012, 0.001, 0.0001, 0.001, 0.0001, 0.012, 0.052, 0.0001, 0.0001, 0.021, 0.02, 0.013, 0.012, 0.005, 0.005, 0.001, 0.002, 0.087, 0.001, 0.002, 0.004, 0.002, 0.014, 0.002, 0.001, 0.002, 0.005, 0.0001, 0.002, 0.002, 0.007, 0.005, 0.01, 0.013, 0.001, 0.014, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.03, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.019, 0.191, 0.004, 0.004, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.003, 0.003, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.011, 0.007, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.129, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pfl\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.263, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.706, 0.009, 0.364, 0.0001, 0.0001, 0.013, 0.001, 0.091, 0.195, 0.195, 0.003, 0.001, 0.686, 0.342, 1.046, 0.015, 0.201, 0.285, 0.126, 0.084, 0.075, 0.083, 0.073, 0.08, 0.09, 0.133, 0.057, 0.007, 0.004, 0.002, 0.005, 0.003, 0.0001, 0.282, 0.468, 0.129, 0.696, 0.183, 0.211, 0.353, 0.23, 0.145, 0.127, 0.364, 0.254, 0.349, 0.172, 0.165, 0.214, 0.007, 0.306, 0.587, 0.085, 0.112, 0.133, 0.253, 0.004, 0.004, 0.083, 0.004, 0.0001, 0.004, 0.0001, 0.001, 0.001, 5.458, 1.123, 3.147, 5.166, 9.364, 1.012, 2.021, 4.491, 5.715, 0.138, 0.564, 2.856, 2.578, 5.721, 2.982, 0.339, 0.016, 4.316, 5.155, 2.124, 2.997, 0.789, 1.349, 0.04, 0.119, 0.948, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.073, 0.001, 0.001, 0.001, 0.025, 0.007, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.014, 0.001, 0.005, 0.002, 0.0001, 0.001, 0.004, 0.001, 0.001, 0.027, 0.001, 0.026, 0.15, 0.041, 0.008, 0.01, 0.001, 1.309, 0.09, 0.0001, 0.002, 0.017, 0.105, 0.002, 0.002, 0.07, 0.023, 0.0001, 0.002, 0.004, 0.001, 0.016, 0.015, 0.003, 0.033, 0.029, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.044, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.024, 1.987, 0.001, 0.015, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.001, 0.001, 0.0001, 0.002, 0.001, 0.006, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.07, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pi\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.055, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.983, 0.003, 0.019, 0.0001, 0.0001, 0.001, 0.0001, 0.005, 0.015, 0.015, 0.0001, 0.0001, 0.196, 0.05, 0.162, 0.001, 0.012, 0.032, 0.016, 0.014, 0.013, 0.015, 0.012, 0.01, 0.008, 0.011, 0.012, 0.002, 0.001, 0.001, 0.001, 0.003, 0.0001, 0.014, 0.008, 0.005, 0.012, 0.009, 0.003, 0.003, 0.008, 0.003, 0.003, 0.005, 0.005, 0.006, 0.016, 0.004, 0.013, 0.0001, 0.005, 0.023, 0.015, 0.011, 0.002, 0.003, 0.0001, 0.036, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.761, 0.196, 0.276, 0.305, 0.454, 0.017, 0.165, 0.648, 0.799, 0.068, 0.545, 0.11, 0.265, 0.512, 0.167, 0.383, 0.0001, 0.263, 0.542, 0.555, 0.3, 0.254, 0.017, 0.002, 0.396, 0.01, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.585, 1.349, 0.631, 0.309, 0.001, 0.704, 0.099, 0.738, 0.496, 0.135, 0.002, 0.598, 0.015, 3.249, 0.0001, 0.471, 0.004, 0.001, 0.0001, 0.007, 0.008, 1.087, 0.017, 0.945, 0.067, 0.032, 0.055, 0.004, 0.076, 0.002, 0.011, 0.133, 0.012, 0.507, 0.001, 0.315, 18.309, 9.394, 0.355, 1.002, 0.59, 0.225, 0.335, 0.42, 0.344, 0.341, 0.537, 1.408, 2.487, 0.078, 0.724, 0.001, 0.0001, 0.527, 0.043, 0.432, 2.004, 0.558, 0.0001, 0.0001, 0.014, 0.0001, 1.176, 0.438, 0.0001, 0.0001, 0.0001, 0.095, 0.876, 0.019, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 26.149, 0.497, 0.061, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pih\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.022, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.521, 0.009, 0.204, 0.0001, 0.0001, 0.004, 0.001, 2.454, 0.291, 0.293, 0.002, 0.001, 0.888, 0.295, 1.488, 0.018, 0.431, 0.479, 0.302, 0.13, 0.163, 0.176, 0.152, 0.154, 0.17, 0.228, 0.107, 0.024, 0.002, 0.004, 0.002, 0.0001, 0.0001, 0.58, 0.327, 0.222, 0.2, 0.438, 0.166, 0.153, 0.179, 0.225, 0.154, 0.281, 0.167, 0.407, 0.329, 0.234, 0.386, 0.004, 0.196, 0.528, 0.379, 0.134, 0.065, 0.116, 0.0001, 0.083, 0.034, 0.002, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 8.21, 0.769, 1.021, 1.638, 6.843, 0.747, 0.93, 1.82, 7.969, 0.261, 1.7, 3.13, 1.378, 5.431, 3.567, 1.341, 0.022, 3.668, 4.749, 4.715, 2.394, 0.289, 0.906, 0.045, 1.135, 0.094, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.092, 0.039, 0.046, 0.01, 0.019, 0.008, 0.006, 0.003, 0.009, 0.001, 0.001, 0.017, 0.011, 0.007, 0.006, 0.02, 0.015, 0.009, 0.001, 0.019, 0.012, 0.0001, 0.001, 0.006, 0.003, 0.028, 0.004, 0.009, 0.002, 0.007, 0.002, 0.006, 0.077, 0.019, 0.006, 0.004, 0.002, 0.001, 0.001, 0.004, 0.006, 0.035, 0.017, 0.004, 0.004, 0.012, 0.004, 0.0001, 0.082, 0.011, 0.044, 0.037, 0.018, 0.034, 0.007, 0.017, 0.057, 0.008, 0.047, 0.04, 0.021, 0.031, 0.077, 0.012, 0.0001, 0.0001, 0.098, 0.125, 0.02, 0.026, 0.002, 0.0001, 0.0001, 0.016, 0.007, 0.004, 0.0001, 0.001, 0.0001, 0.0001, 0.45, 0.193, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.026, 0.048, 0.0001, 0.0001, 0.003, 0.002, 0.001, 0.0001, 0.001, 0.001, 0.002, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pms\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.299, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.832, 0.001, 0.162, 0.0001, 0.0001, 0.004, 0.001, 1.011, 0.156, 0.156, 0.0001, 0.0001, 1.253, 0.787, 0.992, 0.342, 0.318, 0.594, 0.327, 0.281, 0.214, 0.205, 0.231, 0.17, 0.187, 0.641, 0.121, 0.014, 0.149, 0.035, 0.149, 0.034, 0.0001, 0.513, 0.293, 0.3, 0.101, 0.054, 0.096, 0.128, 0.035, 0.079, 0.03, 0.029, 0.305, 0.216, 0.126, 0.053, 0.205, 0.008, 0.226, 0.291, 0.098, 0.028, 0.104, 0.023, 0.014, 0.012, 0.01, 0.002, 0.0001, 0.002, 0.0001, 0.011, 0.0001, 9.348, 0.753, 2.346, 3.335, 4.488, 0.731, 0.919, 0.76, 4.936, 0.31, 0.385, 3.85, 2.042, 6.393, 3.543, 1.375, 0.084, 3.728, 4.566, 4.041, 1.559, 0.688, 0.145, 0.036, 0.118, 0.134, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.165, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.007, 0.001, 0.059, 0.002, 0.001, 0.004, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.02, 0.115, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.661, 0.007, 0.005, 0.001, 0.004, 0.0001, 0.001, 0.004, 0.295, 0.64, 0.002, 2.121, 0.312, 0.006, 0.001, 0.001, 0.002, 0.006, 0.658, 0.012, 0.019, 0.001, 0.003, 0.0001, 0.001, 0.07, 0.002, 0.002, 0.005, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.286, 4.639, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.002, 0.007, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.137, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pnb\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.056, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.3, 0.001, 0.061, 0.0001, 0.0001, 0.004, 0.0001, 0.019, 0.084, 0.084, 0.0001, 0.001, 0.02, 0.088, 0.041, 0.008, 0.132, 0.201, 0.102, 0.067, 0.062, 0.067, 0.058, 0.058, 0.067, 0.099, 0.044, 0.011, 0.014, 0.019, 0.014, 0.0001, 0.0001, 0.005, 0.015, 0.004, 0.007, 0.004, 0.002, 0.006, 0.002, 0.006, 0.004, 0.001, 0.002, 0.002, 0.003, 0.002, 0.003, 0.0001, 0.004, 0.004, 0.005, 0.001, 0.001, 0.003, 0.0001, 0.0001, 0.0001, 0.016, 0.0001, 0.016, 0.0001, 0.001, 0.001, 0.078, 0.007, 0.025, 0.032, 0.067, 0.025, 0.012, 0.031, 0.075, 0.001, 0.005, 0.047, 0.03, 0.057, 0.061, 0.011, 0.001, 0.056, 0.035, 0.078, 0.015, 0.016, 0.005, 0.001, 0.026, 0.001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.037, 1.78, 0.208, 0.002, 1.668, 1.155, 3.673, 0.01, 3.168, 0.001, 0.005, 0.003, 4.417, 0.001, 0.019, 0.031, 0.026, 0.339, 2.06, 0.022, 0.634, 0.001, 0.0001, 0.0001, 0.015, 0.009, 0.0001, 0.004, 0.002, 0.002, 0.0001, 0.002, 0.01, 0.033, 0.198, 0.001, 0.068, 0.002, 0.419, 5.965, 1.027, 1.695, 1.567, 0.022, 0.752, 0.178, 0.132, 2.73, 0.025, 2.399, 0.214, 1.507, 0.366, 0.211, 0.103, 0.103, 0.037, 0.788, 1.21, 0.001, 0.001, 0.002, 1.61, 0.001, 0.0001, 0.0001, 0.01, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.007, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 17.991, 10.598, 5.545, 8.408, 0.0001, 0.004, 0.0001, 0.0001, 0.008, 0.001, 0.037, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"pnt\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.111, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 8.571, 0.001, 0.271, 0.0001, 0.0001, 0.001, 0.001, 0.535, 0.166, 0.167, 0.003, 0.001, 0.374, 0.066, 0.728, 0.006, 0.22, 0.256, 0.213, 0.123, 0.09, 0.093, 0.091, 0.105, 0.105, 0.136, 0.082, 0.003, 0.006, 0.005, 0.006, 0.0001, 0.0001, 0.027, 0.009, 0.018, 0.007, 0.008, 0.002, 0.007, 0.007, 0.011, 0.005, 0.01, 0.006, 0.014, 0.004, 0.003, 0.01, 0.001, 0.009, 0.009, 0.01, 0.001, 0.002, 0.009, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.102, 0.019, 0.03, 0.031, 0.116, 0.01, 0.025, 0.028, 0.096, 0.002, 0.026, 0.047, 0.026, 0.078, 0.081, 0.022, 0.002, 0.09, 0.05, 0.057, 0.045, 0.011, 0.005, 0.004, 0.012, 0.009, 0.0001, 0.182, 0.0001, 0.0001, 0.0001, 1.086, 2.28, 1.131, 1.649, 3.333, 0.911, 0.24, 0.496, 0.069, 0.319, 0.04, 0.001, 0.823, 0.357, 0.222, 0.002, 0.015, 0.24, 0.073, 0.124, 0.043, 0.159, 0.008, 0.102, 0.032, 0.07, 0.198, 0.037, 0.134, 0.044, 0.005, 0.136, 0.134, 0.042, 0.001, 0.198, 0.245, 0.005, 0.018, 0.079, 0.003, 0.008, 0.001, 0.042, 1.012, 0.786, 0.269, 1.272, 0.017, 4.369, 0.221, 0.746, 0.384, 2.578, 0.144, 1.385, 0.301, 1.937, 1.463, 1.533, 1.22, 4.421, 0.103, 3.268, 0.0001, 0.0001, 0.091, 0.022, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 29.379, 12.776, 0.049, 0.015, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.003, 0.011, 0.009, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.059, 0.041, 0.0001, 0.001, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ps\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.579, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.932, 0.004, 0.044, 0.0001, 0.0001, 0.003, 0.0001, 0.002, 0.118, 0.118, 0.001, 0.001, 0.026, 0.037, 0.443, 0.009, 0.022, 0.03, 0.021, 0.014, 0.011, 0.012, 0.01, 0.009, 0.01, 0.013, 0.062, 0.001, 0.002, 0.005, 0.002, 0.0001, 0.0001, 0.015, 0.007, 0.011, 0.007, 0.006, 0.005, 0.004, 0.007, 0.009, 0.002, 0.003, 0.005, 0.01, 0.006, 0.004, 0.009, 0.001, 0.006, 0.013, 0.009, 0.003, 0.002, 0.003, 0.001, 0.001, 0.001, 0.004, 0.0001, 0.004, 0.0001, 0.003, 0.0001, 0.147, 0.023, 0.055, 0.054, 0.165, 0.027, 0.031, 0.061, 0.131, 0.002, 0.012, 0.073, 0.048, 0.109, 0.113, 0.034, 0.002, 0.103, 0.097, 0.116, 0.047, 0.015, 0.017, 0.005, 0.027, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.103, 0.528, 0.202, 0.231, 2.393, 1.822, 2.655, 3.163, 4.608, 0.307, 2.451, 0.006, 1.513, 0.136, 0.015, 0.009, 1.675, 0.004, 0.009, 0.507, 0.005, 0.0001, 0.154, 0.001, 0.093, 0.002, 0.229, 0.007, 0.005, 0.003, 0.0001, 0.006, 0.024, 0.025, 0.048, 0.014, 0.025, 0.008, 0.038, 4.145, 0.839, 1.375, 1.43, 0.077, 0.25, 0.229, 0.647, 2.983, 0.085, 2.528, 0.449, 1.14, 0.525, 0.146, 0.073, 0.106, 0.064, 0.333, 0.407, 0.02, 0.265, 0.005, 1.278, 0.002, 0.0001, 0.0001, 0.016, 0.003, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.028, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 16.081, 19.012, 3.763, 3.368, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.026, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.038, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"qu\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.204, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.108, 0.002, 0.638, 0.0001, 0.0001, 0.004, 0.002, 0.39, 0.494, 0.494, 0.05, 0.002, 1.04, 0.188, 0.977, 0.036, 0.261, 0.409, 0.214, 0.137, 0.12, 0.137, 0.117, 0.112, 0.136, 0.208, 0.401, 0.061, 0.041, 0.006, 0.041, 0.004, 0.0001, 0.371, 0.173, 0.36, 0.119, 0.076, 0.064, 0.097, 0.189, 0.192, 0.096, 0.255, 0.187, 0.305, 0.078, 0.042, 0.428, 0.148, 0.146, 0.31, 0.198, 0.198, 0.061, 0.174, 0.014, 0.102, 0.014, 0.014, 0.0001, 0.015, 0.0001, 0.002, 0.0001, 14.813, 0.229, 1.579, 0.795, 1.296, 0.084, 0.286, 2.331, 7.773, 0.067, 3.004, 4.09, 3.086, 5.006, 1.165, 2.96, 3.746, 3.293, 3.447, 3.494, 5.678, 0.135, 1.682, 0.024, 2.244, 0.111, 0.0001, 0.003, 0.001, 0.0001, 0.0001, 0.055, 0.016, 0.009, 0.013, 0.008, 0.006, 0.004, 0.006, 0.004, 0.004, 0.003, 0.002, 0.006, 0.007, 0.003, 0.002, 0.012, 0.023, 0.002, 0.011, 0.004, 0.004, 0.003, 0.002, 0.004, 0.015, 0.003, 0.003, 0.005, 0.003, 0.002, 0.003, 0.038, 0.068, 0.004, 0.005, 0.014, 0.005, 0.006, 0.009, 0.007, 0.056, 0.005, 0.005, 0.005, 0.075, 0.004, 0.006, 0.014, 0.34, 0.013, 0.069, 0.007, 0.008, 0.006, 0.005, 0.016, 0.009, 0.022, 0.008, 0.011, 0.009, 0.01, 0.01, 0.0001, 0.0001, 0.026, 0.649, 0.01, 0.009, 0.001, 0.001, 0.0001, 0.002, 0.001, 0.008, 0.002, 0.0001, 0.042, 0.02, 0.051, 0.016, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.006, 0.016, 0.012, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.031, 0.016, 0.047, 0.001, 0.001, 0.006, 0.005, 0.002, 0.002, 0.002, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"rm\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.612, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.511, 0.003, 0.167, 0.0001, 0.0001, 0.015, 0.0001, 0.104, 0.151, 0.151, 0.003, 0.0001, 0.439, 0.065, 0.823, 0.012, 0.199, 0.275, 0.114, 0.07, 0.072, 0.078, 0.067, 0.073, 0.099, 0.138, 0.045, 0.054, 0.005, 0.002, 0.005, 0.002, 0.0001, 0.139, 0.111, 0.151, 0.1, 0.17, 0.069, 0.113, 0.045, 0.217, 0.028, 0.029, 0.208, 0.121, 0.055, 0.036, 0.132, 0.042, 0.086, 0.226, 0.106, 0.051, 0.07, 0.023, 0.003, 0.003, 0.008, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.001, 11.404, 0.601, 3.031, 3.677, 6.353, 0.757, 1.578, 1.412, 6.549, 0.076, 0.068, 4.778, 1.866, 6.046, 2.159, 1.856, 0.265, 4.97, 5.963, 4.375, 3.712, 1.407, 0.03, 0.131, 0.049, 0.802, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.828, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.043, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.675, 0.0001, 0.001, 0.003, 0.001, 0.001, 0.0001, 0.294, 0.003, 0.002, 0.0001, 0.006, 0.001, 0.001, 0.002, 0.333, 0.017, 0.002, 0.02, 0.128, 0.003, 0.0001, 0.001, 0.004, 0.002, 0.027, 0.004, 0.001, 0.001, 0.012, 0.0001, 0.001, 0.054, 0.054, 0.02, 0.042, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.079, 0.841, 0.004, 0.004, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.828, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"rmy\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.439, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.022, 0.009, 0.624, 0.001, 0.0001, 0.014, 0.003, 0.015, 0.443, 0.441, 0.003, 0.003, 1.038, 0.232, 0.983, 0.037, 0.243, 0.24, 0.172, 0.071, 0.075, 0.081, 0.064, 0.056, 0.058, 0.153, 0.179, 0.028, 0.0001, 0.007, 0.0001, 0.007, 0.0001, 0.429, 0.231, 0.183, 0.167, 0.219, 0.086, 0.091, 0.078, 0.193, 0.094, 0.424, 0.255, 0.23, 0.124, 0.22, 0.316, 0.009, 0.404, 0.577, 0.193, 0.066, 0.142, 0.014, 0.008, 0.107, 0.022, 0.003, 0.0001, 0.004, 0.0001, 0.001, 0.001, 10.986, 1.012, 0.769, 2.129, 6.975, 0.334, 0.958, 2.547, 6.287, 0.364, 2.952, 3.16, 1.944, 5.241, 5.031, 1.512, 0.108, 4.343, 3.649, 3.301, 2.127, 1.805, 0.051, 0.119, 2.702, 0.234, 0.0001, 0.003, 0.001, 0.001, 0.0001, 0.065, 0.024, 0.024, 0.136, 0.011, 0.006, 0.01, 0.023, 0.009, 0.001, 0.005, 0.016, 0.01, 0.054, 0.02, 0.004, 0.003, 0.007, 0.001, 0.025, 0.008, 0.009, 0.004, 0.007, 0.004, 0.054, 0.005, 0.044, 0.006, 0.002, 0.003, 0.008, 0.038, 0.056, 0.061, 0.004, 0.069, 0.023, 0.011, 0.016, 0.012, 0.016, 0.02, 0.006, 0.003, 0.014, 0.084, 0.01, 0.051, 0.028, 0.039, 0.03, 0.013, 0.014, 0.009, 0.007, 0.027, 0.007, 0.021, 0.023, 0.024, 0.034, 0.062, 0.014, 0.0001, 0.0001, 0.036, 0.28, 0.165, 0.061, 0.0001, 0.011, 0.089, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.007, 0.003, 0.242, 0.129, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.026, 0.02, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.128, 0.005, 0.037, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.004, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"rn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.466, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.984, 0.029, 0.062, 0.0001, 0.0001, 0.003, 0.0001, 0.574, 0.079, 0.089, 0.011, 0.0001, 1.075, 0.049, 1.054, 0.052, 0.102, 0.296, 0.219, 0.136, 0.097, 0.085, 0.071, 0.069, 0.063, 0.086, 0.222, 0.076, 0.048, 0.0001, 0.048, 0.055, 0.0001, 0.35, 0.153, 0.043, 0.033, 0.087, 0.042, 0.05, 0.106, 0.336, 0.016, 0.136, 0.035, 0.208, 0.24, 0.02, 0.055, 0.001, 0.138, 0.132, 0.082, 0.29, 0.029, 0.019, 0.0001, 0.195, 0.032, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 10.973, 3.104, 0.817, 1.265, 5.117, 0.295, 2.193, 1.806, 7.318, 0.542, 2.677, 0.668, 3.393, 5.179, 4.081, 0.677, 0.004, 4.518, 2.135, 2.161, 5.935, 1.185, 2.07, 0.007, 2.3, 1.432, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.174, 0.023, 0.002, 0.005, 0.007, 0.004, 0.003, 0.003, 0.006, 0.0001, 0.004, 0.0001, 0.015, 0.271, 0.012, 0.002, 0.008, 0.004, 0.004, 0.008, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.284, 0.0001, 0.196, 0.005, 0.002, 0.004, 0.014, 0.082, 0.437, 0.009, 0.0001, 0.0001, 0.006, 0.001, 0.018, 0.002, 0.13, 0.002, 0.17, 0.0001, 0.586, 0.007, 0.108, 0.004, 0.019, 0.001, 0.014, 0.007, 0.001, 0.01, 0.001, 0.001, 0.002, 0.049, 0.153, 0.015, 0.123, 0.121, 0.0001, 0.0001, 0.0001, 0.386, 1.335, 0.539, 0.45, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.015, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.02, 0.053, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.167, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"rue\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.059, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.931, 0.003, 0.134, 0.004, 0.0001, 0.006, 0.0001, 0.004, 0.175, 0.175, 0.004, 0.001, 0.601, 0.077, 0.708, 0.007, 0.16, 0.303, 0.147, 0.097, 0.093, 0.095, 0.084, 0.09, 0.099, 0.137, 0.031, 0.011, 0.003, 0.006, 0.002, 0.001, 0.0001, 0.009, 0.006, 0.011, 0.005, 0.005, 0.004, 0.005, 0.007, 0.017, 0.002, 0.003, 0.006, 0.008, 0.01, 0.004, 0.008, 0.0001, 0.006, 0.012, 0.008, 0.004, 0.005, 0.003, 0.005, 0.001, 0.001, 0.002, 0.011, 0.003, 0.0001, 0.004, 0.0001, 0.091, 0.013, 0.033, 0.032, 0.099, 0.015, 0.014, 0.02, 0.081, 0.006, 0.012, 0.045, 0.028, 0.055, 0.071, 0.018, 0.002, 0.069, 0.058, 0.052, 0.039, 0.016, 0.006, 0.004, 0.014, 0.011, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 2.51, 1.935, 2.11, 1.119, 0.168, 0.507, 0.468, 0.486, 0.356, 0.046, 0.009, 1.422, 0.678, 0.003, 0.239, 0.784, 0.089, 0.301, 0.121, 0.074, 0.371, 0.035, 1.647, 0.529, 0.012, 0.009, 0.09, 0.04, 0.101, 0.07, 0.061, 0.137, 0.108, 0.152, 0.055, 0.234, 0.024, 0.016, 0.017, 0.032, 0.025, 0.005, 0.001, 0.015, 0.002, 0.004, 0.009, 0.015, 3.672, 0.621, 2.19, 0.526, 1.267, 2.143, 0.348, 0.764, 1.53, 0.62, 1.849, 1.595, 1.255, 2.617, 4.166, 1.0, 0.0001, 0.0001, 0.065, 0.026, 0.003, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.006, 0.0001, 0.02, 0.009, 27.547, 15.28, 0.159, 0.0001, 0.0001, 0.001, 0.001, 0.004, 0.006, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.004, 0.115, 0.004, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"rw\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.278, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.768, 0.007, 0.605, 0.0001, 0.0001, 0.01, 0.005, 0.156, 0.48, 0.479, 0.0001, 0.001, 0.554, 0.104, 0.846, 0.03, 0.199, 0.216, 0.155, 0.113, 0.085, 0.082, 0.092, 0.069, 0.073, 0.138, 0.279, 0.158, 0.014, 0.006, 0.014, 0.022, 0.0001, 0.463, 0.203, 0.115, 0.093, 0.067, 0.068, 0.134, 0.078, 0.521, 0.056, 0.247, 0.076, 0.283, 0.278, 0.063, 0.134, 0.006, 0.219, 0.18, 0.135, 0.469, 0.053, 0.038, 0.003, 0.047, 0.033, 0.002, 0.005, 0.003, 0.0001, 0.005, 0.003, 10.187, 2.795, 0.85, 1.072, 4.678, 0.399, 2.704, 1.553, 8.747, 0.332, 2.605, 0.938, 3.443, 4.833, 3.164, 0.42, 0.036, 4.477, 2.07, 2.396, 6.084, 0.329, 1.941, 0.023, 2.813, 1.358, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.976, 0.03, 0.025, 0.03, 0.043, 0.049, 0.025, 0.021, 0.028, 0.009, 0.036, 0.011, 0.018, 0.014, 0.026, 0.013, 0.02, 0.015, 0.01, 0.15, 0.008, 0.015, 0.009, 0.008, 0.011, 0.787, 0.007, 0.008, 0.021, 0.011, 0.011, 0.034, 0.016, 0.016, 0.009, 0.011, 0.025, 0.012, 0.025, 0.083, 0.028, 0.057, 0.013, 0.014, 0.038, 0.019, 0.024, 0.039, 0.055, 0.067, 0.03, 0.036, 0.018, 0.034, 0.017, 0.013, 0.045, 0.024, 0.03, 0.026, 0.034, 0.02, 0.028, 0.012, 0.0001, 0.0001, 0.03, 0.13, 0.052, 0.028, 0.001, 0.001, 0.001, 0.009, 0.002, 0.001, 0.003, 0.0001, 0.02, 0.007, 0.21, 0.107, 0.008, 0.005, 0.001, 0.006, 0.005, 0.025, 0.234, 0.162, 0.005, 0.009, 0.0001, 0.0001, 0.009, 0.0001, 0.108, 0.055, 0.961, 0.001, 0.002, 0.013, 0.008, 0.004, 0.003, 0.002, 0.001, 0.001, 0.003, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sa\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.358, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.441, 0.003, 0.019, 0.0001, 0.0001, 0.003, 0.0001, 0.034, 0.04, 0.042, 0.0001, 0.001, 0.189, 0.124, 0.061, 0.009, 0.003, 0.004, 0.003, 0.002, 0.002, 0.001, 0.001, 0.001, 0.001, 0.002, 0.022, 0.001, 0.005, 0.003, 0.005, 0.005, 0.0001, 0.002, 0.001, 0.007, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.003, 0.002, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.036, 0.006, 0.009, 0.018, 0.033, 0.003, 0.005, 0.01, 0.026, 0.001, 0.003, 0.015, 0.009, 0.022, 0.022, 0.013, 0.0001, 0.02, 0.014, 0.02, 0.007, 0.002, 0.005, 0.001, 0.004, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.485, 0.531, 0.737, 0.892, 0.001, 0.437, 0.14, 1.014, 0.103, 0.083, 0.003, 0.31, 0.053, 4.186, 0.001, 0.123, 0.004, 0.001, 0.0001, 0.016, 0.005, 0.929, 0.077, 0.397, 0.036, 0.098, 0.308, 0.026, 0.311, 0.017, 0.076, 0.146, 0.037, 0.11, 0.008, 0.362, 26.378, 7.803, 0.723, 0.32, 1.5, 0.025, 0.84, 0.05, 0.176, 0.36, 1.148, 1.481, 1.847, 0.0001, 0.431, 0.017, 0.001, 1.168, 0.412, 0.409, 1.347, 0.264, 0.0001, 0.0001, 0.002, 0.029, 2.443, 1.602, 0.0001, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 31.344, 0.0001, 0.071, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sah\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.686, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.508, 0.002, 0.063, 0.0001, 0.0001, 0.006, 0.0001, 0.001, 0.091, 0.092, 0.0001, 0.001, 0.559, 0.22, 0.713, 0.008, 0.141, 0.211, 0.115, 0.061, 0.06, 0.065, 0.051, 0.05, 0.056, 0.124, 0.035, 0.013, 0.006, 0.002, 0.006, 0.002, 0.0001, 0.005, 0.004, 0.005, 0.003, 0.002, 0.002, 0.002, 0.002, 0.017, 0.001, 0.002, 0.002, 0.004, 0.003, 0.002, 0.003, 0.0001, 0.002, 0.006, 0.004, 0.001, 0.004, 0.001, 0.007, 0.002, 0.0001, 0.003, 0.0001, 0.003, 0.0001, 0.004, 0.0001, 0.029, 0.008, 0.012, 0.008, 0.028, 0.005, 0.006, 0.038, 0.023, 0.001, 0.004, 0.015, 0.01, 0.02, 0.025, 0.007, 0.0001, 0.024, 0.015, 0.017, 0.012, 0.003, 0.003, 0.001, 0.005, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.31, 1.601, 3.034, 2.129, 0.054, 0.947, 0.056, 0.303, 0.028, 0.008, 0.002, 2.384, 0.325, 2.811, 0.018, 0.129, 0.129, 0.118, 0.035, 0.051, 0.111, 0.426, 0.011, 0.007, 0.063, 0.002, 0.123, 0.02, 0.075, 0.059, 0.083, 0.041, 0.09, 0.172, 0.066, 0.042, 0.018, 0.342, 0.005, 0.017, 0.028, 0.688, 0.0001, 0.065, 0.003, 0.027, 0.027, 0.911, 6.03, 1.253, 0.256, 0.681, 0.862, 0.464, 0.024, 0.065, 2.76, 0.764, 1.431, 3.082, 0.589, 3.175, 2.114, 0.327, 0.0001, 0.0001, 0.173, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.002, 0.0001, 0.002, 0.001, 24.486, 17.038, 2.234, 0.706, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.102, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sc\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.057, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.585, 0.005, 0.337, 0.0001, 0.0001, 0.01, 0.001, 0.519, 0.179, 0.179, 0.0001, 0.001, 0.997, 0.097, 0.746, 0.025, 0.237, 0.292, 0.15, 0.087, 0.082, 0.09, 0.079, 0.083, 0.093, 0.166, 0.067, 0.032, 0.007, 0.002, 0.008, 0.003, 0.0001, 0.224, 0.132, 0.261, 0.092, 0.103, 0.086, 0.108, 0.028, 0.255, 0.028, 0.024, 0.103, 0.181, 0.091, 0.054, 0.171, 0.006, 0.093, 0.455, 0.116, 0.064, 0.058, 0.024, 0.025, 0.008, 0.012, 0.012, 0.0001, 0.012, 0.0001, 0.0001, 0.0001, 9.363, 0.921, 2.394, 4.179, 7.513, 0.745, 1.075, 0.764, 6.94, 0.075, 0.096, 1.956, 1.851, 5.606, 4.069, 1.688, 0.018, 4.534, 7.393, 5.178, 5.316, 0.445, 0.037, 0.029, 0.067, 0.854, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.107, 0.01, 0.006, 0.008, 0.004, 0.002, 0.002, 0.003, 0.006, 0.002, 0.002, 0.002, 0.006, 0.004, 0.002, 0.001, 0.003, 0.002, 0.003, 0.008, 0.004, 0.001, 0.002, 0.001, 0.003, 0.058, 0.001, 0.002, 0.016, 0.017, 0.001, 0.001, 0.306, 0.009, 0.002, 0.003, 0.003, 0.002, 0.001, 0.004, 0.232, 0.016, 0.003, 0.007, 0.252, 0.007, 0.001, 0.002, 0.008, 0.007, 0.176, 0.008, 0.002, 0.004, 0.003, 0.006, 0.005, 0.096, 0.007, 0.007, 0.006, 0.004, 0.004, 0.004, 0.0001, 0.0001, 0.032, 1.097, 0.007, 0.006, 0.0001, 0.0001, 0.0001, 0.011, 0.003, 0.007, 0.001, 0.0001, 0.018, 0.009, 0.023, 0.009, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.003, 0.006, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.006, 0.096, 0.007, 0.001, 0.002, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"scn\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.769, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.859, 0.005, 0.376, 0.001, 0.0001, 0.006, 0.001, 0.62, 0.187, 0.189, 0.001, 0.001, 0.862, 0.093, 0.813, 0.026, 0.237, 0.31, 0.158, 0.093, 0.09, 0.099, 0.092, 0.087, 0.104, 0.161, 0.085, 0.032, 0.027, 0.008, 0.034, 0.003, 0.0001, 0.211, 0.12, 0.299, 0.095, 0.077, 0.106, 0.122, 0.053, 0.143, 0.031, 0.023, 0.333, 0.207, 0.158, 0.044, 0.198, 0.02, 0.123, 0.32, 0.125, 0.061, 0.101, 0.022, 0.022, 0.008, 0.012, 0.019, 0.0001, 0.019, 0.0001, 0.007, 0.001, 8.698, 0.715, 3.649, 2.751, 2.764, 0.729, 1.223, 0.71, 11.435, 0.098, 0.087, 3.105, 2.009, 5.881, 1.955, 1.977, 0.125, 4.751, 3.262, 4.998, 7.156, 1.012, 0.04, 0.022, 0.077, 0.978, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.123, 0.005, 0.005, 0.005, 0.003, 0.001, 0.002, 0.001, 0.009, 0.002, 0.001, 0.001, 0.004, 0.003, 0.001, 0.001, 0.001, 0.002, 0.002, 0.01, 0.003, 0.001, 0.002, 0.001, 0.012, 0.059, 0.001, 0.002, 0.014, 0.013, 0.001, 0.001, 0.271, 0.006, 0.312, 0.002, 0.001, 0.001, 0.001, 0.004, 0.478, 0.013, 0.046, 0.017, 0.359, 0.007, 0.096, 0.002, 0.008, 0.004, 0.23, 0.006, 0.189, 0.003, 0.002, 0.003, 0.004, 0.145, 0.006, 0.184, 0.003, 0.004, 0.002, 0.003, 0.0001, 0.0001, 0.038, 2.342, 0.007, 0.007, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.018, 0.008, 0.018, 0.009, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.006, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.006, 0.107, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sco\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.424, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.792, 0.003, 0.394, 0.001, 0.002, 0.018, 0.003, 0.122, 0.265, 0.265, 0.001, 0.001, 1.079, 0.194, 0.878, 0.014, 0.365, 0.437, 0.262, 0.125, 0.119, 0.131, 0.116, 0.116, 0.135, 0.238, 0.065, 0.052, 0.004, 0.002, 0.004, 0.001, 0.0001, 0.38, 0.213, 0.323, 0.162, 0.132, 0.149, 0.164, 0.147, 0.281, 0.103, 0.117, 0.162, 0.281, 0.134, 0.098, 0.228, 0.015, 0.18, 0.416, 0.389, 0.071, 0.084, 0.096, 0.009, 0.031, 0.022, 0.004, 0.0001, 0.004, 0.0001, 0.0001, 0.001, 7.525, 0.973, 2.32, 1.915, 9.145, 0.867, 0.966, 3.175, 6.858, 0.086, 0.556, 2.986, 1.72, 5.779, 4.854, 1.317, 0.066, 4.925, 4.607, 6.432, 2.109, 0.676, 1.003, 0.125, 1.018, 0.144, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.052, 0.009, 0.005, 0.004, 0.004, 0.003, 0.002, 0.004, 0.002, 0.003, 0.002, 0.001, 0.002, 0.005, 0.001, 0.002, 0.001, 0.002, 0.001, 0.033, 0.006, 0.001, 0.002, 0.002, 0.002, 0.009, 0.001, 0.002, 0.003, 0.003, 0.001, 0.005, 0.039, 0.026, 0.003, 0.006, 0.015, 0.004, 0.002, 0.007, 0.005, 0.025, 0.002, 0.006, 0.002, 0.019, 0.001, 0.002, 0.01, 0.012, 0.013, 0.016, 0.004, 0.005, 0.011, 0.002, 0.007, 0.003, 0.007, 0.003, 0.009, 0.004, 0.004, 0.002, 0.0001, 0.0001, 0.051, 0.152, 0.018, 0.014, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.011, 0.005, 0.019, 0.007, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.009, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.005, 0.05, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sd\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.527, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.452, 0.004, 0.026, 0.001, 0.0001, 0.002, 0.0001, 0.004, 0.108, 0.108, 0.004, 0.001, 0.009, 0.252, 0.565, 0.015, 0.09, 0.18, 0.083, 0.051, 0.047, 0.052, 0.047, 0.048, 0.055, 0.112, 0.038, 0.003, 0.004, 0.004, 0.004, 0.0001, 0.0001, 0.01, 0.007, 0.009, 0.005, 0.004, 0.003, 0.004, 0.004, 0.005, 0.002, 0.003, 0.004, 0.007, 0.004, 0.003, 0.007, 0.001, 0.004, 0.011, 0.007, 0.002, 0.002, 0.003, 0.0001, 0.001, 0.0001, 0.005, 0.0001, 0.005, 0.001, 0.003, 0.0001, 0.093, 0.018, 0.025, 0.03, 0.086, 0.014, 0.018, 0.031, 0.075, 0.002, 0.009, 0.041, 0.026, 0.061, 0.064, 0.018, 0.001, 0.061, 0.048, 0.062, 0.025, 0.01, 0.009, 0.005, 0.016, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.358, 0.355, 0.367, 0.044, 1.492, 1.56, 3.771, 2.24, 3.043, 0.003, 5.411, 0.006, 0.747, 0.045, 0.229, 0.346, 0.139, 0.006, 0.013, 0.002, 0.041, 0.001, 0.001, 0.003, 0.036, 0.237, 0.0001, 0.007, 0.063, 0.063, 0.001, 0.009, 0.066, 0.346, 0.511, 0.005, 0.013, 0.004, 0.6, 4.552, 0.858, 0.458, 2.417, 0.053, 1.486, 0.357, 0.248, 1.426, 0.07, 2.412, 0.238, 1.475, 0.403, 0.209, 0.078, 0.141, 0.068, 0.553, 0.222, 0.578, 0.001, 0.647, 1.291, 0.291, 0.0001, 0.0001, 0.065, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 17.302, 19.772, 4.118, 0.84, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.198, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.104, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"se\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.476, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.534, 0.002, 0.233, 0.001, 0.001, 0.008, 0.001, 0.011, 0.255, 0.258, 0.0001, 0.001, 1.05, 0.297, 1.247, 0.015, 0.373, 0.513, 0.317, 0.174, 0.148, 0.157, 0.145, 0.145, 0.159, 0.258, 0.106, 0.008, 0.018, 0.002, 0.018, 0.002, 0.0001, 0.188, 0.166, 0.071, 0.251, 0.09, 0.093, 0.235, 0.162, 0.082, 0.134, 0.245, 0.18, 0.186, 0.183, 0.112, 0.147, 0.004, 0.206, 0.487, 0.123, 0.053, 0.171, 0.019, 0.003, 0.015, 0.006, 0.005, 0.0001, 0.005, 0.0001, 0.001, 0.0001, 10.038, 0.915, 0.217, 3.327, 5.602, 0.262, 2.678, 1.589, 6.671, 1.512, 2.136, 5.043, 2.364, 3.492, 4.102, 0.745, 0.01, 2.956, 3.613, 3.601, 3.337, 2.349, 0.035, 0.018, 0.282, 0.057, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.058, 0.044, 0.003, 0.004, 0.008, 0.009, 0.002, 0.003, 0.002, 0.002, 0.001, 0.119, 0.035, 0.369, 0.001, 0.002, 0.002, 0.302, 0.004, 0.024, 0.002, 0.002, 0.006, 0.001, 0.007, 0.006, 0.003, 0.005, 0.009, 0.024, 0.001, 0.003, 0.073, 3.336, 0.013, 0.002, 0.299, 0.044, 0.019, 0.052, 0.004, 0.017, 0.001, 0.011, 0.001, 0.006, 0.001, 0.004, 0.019, 0.004, 0.042, 0.006, 0.01, 0.008, 0.063, 0.001, 0.053, 0.001, 0.006, 0.009, 0.008, 0.006, 0.098, 0.001, 0.0001, 0.0001, 0.118, 3.226, 0.711, 1.0, 0.0001, 0.004, 0.002, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.008, 0.003, 0.027, 0.008, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.003, 0.004, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.008, 0.055, 0.002, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sg\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.098, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.879, 0.044, 0.115, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.198, 0.211, 0.0001, 0.0001, 0.85, 0.464, 1.499, 0.007, 0.524, 0.5, 0.381, 0.203, 0.246, 0.244, 0.244, 0.191, 0.262, 0.229, 0.082, 0.038, 0.0001, 0.0001, 0.0001, 0.029, 0.0001, 0.282, 0.315, 0.126, 0.106, 0.101, 0.06, 0.092, 0.086, 0.092, 0.093, 0.251, 0.348, 0.227, 0.266, 0.057, 0.128, 0.0001, 0.081, 0.346, 0.343, 0.013, 0.053, 0.416, 0.005, 0.029, 0.062, 0.002, 0.0001, 0.002, 0.0001, 0.015, 0.0001, 5.706, 1.515, 0.218, 1.433, 4.537, 0.262, 2.208, 0.762, 2.069, 0.092, 2.86, 1.7, 1.242, 5.365, 3.328, 0.599, 0.022, 1.81, 1.981, 3.619, 0.984, 0.189, 0.434, 0.086, 1.565, 0.929, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.004, 0.029, 0.0001, 0.015, 0.0001, 0.0001, 0.007, 0.004, 0.002, 0.007, 0.016, 0.004, 0.004, 0.022, 0.015, 0.018, 0.0001, 0.004, 0.007, 0.022, 0.0001, 0.009, 0.004, 0.0001, 0.005, 0.0001, 0.016, 0.002, 0.0001, 0.002, 0.059, 0.007, 0.009, 1.785, 0.013, 1.12, 0.007, 0.002, 0.044, 0.027, 0.112, 1.609, 0.647, 0.002, 0.026, 3.804, 0.577, 0.007, 0.004, 0.0001, 0.022, 0.564, 0.0001, 1.689, 0.002, 0.005, 0.046, 0.002, 0.403, 0.176, 0.0001, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 12.759, 0.005, 0.07, 0.0001, 0.007, 0.0001, 0.007, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.024, 0.011, 0.007, 0.0001, 0.0001, 0.015, 0.004, 0.002, 0.0001, 0.002, 0.002, 0.004, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sh\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.387, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.061, 0.002, 0.275, 0.0001, 0.001, 0.015, 0.001, 0.014, 0.187, 0.187, 0.0001, 0.002, 0.95, 0.147, 1.223, 0.02, 0.376, 0.495, 0.321, 0.161, 0.177, 0.142, 0.127, 0.123, 0.133, 0.217, 0.047, 0.016, 0.003, 0.003, 0.003, 0.002, 0.0001, 0.169, 0.152, 0.152, 0.124, 0.074, 0.068, 0.115, 0.09, 0.132, 0.082, 0.135, 0.104, 0.207, 0.208, 0.116, 0.297, 0.005, 0.117, 0.252, 0.137, 0.082, 0.102, 0.023, 0.008, 0.009, 0.058, 0.012, 0.0001, 0.012, 0.0001, 0.003, 0.0001, 8.404, 0.811, 0.782, 2.282, 6.596, 0.226, 1.221, 0.511, 7.186, 3.593, 2.512, 2.698, 2.143, 5.119, 6.434, 1.798, 0.011, 3.759, 3.618, 2.964, 3.066, 2.297, 0.032, 0.02, 0.068, 1.245, 0.001, 0.003, 0.001, 0.0001, 0.0001, 0.106, 0.072, 0.05, 0.059, 0.003, 0.006, 0.013, 0.266, 0.014, 0.001, 0.001, 0.001, 0.012, 0.537, 0.001, 0.001, 0.005, 0.13, 0.005, 0.01, 0.006, 0.001, 0.001, 0.002, 0.039, 0.019, 0.011, 0.012, 0.016, 0.006, 0.015, 0.006, 0.037, 0.532, 0.005, 0.005, 0.003, 0.001, 0.001, 0.002, 0.003, 0.013, 0.001, 0.004, 0.001, 0.013, 0.001, 0.001, 0.155, 0.023, 0.063, 0.029, 0.039, 0.108, 0.009, 0.019, 0.119, 0.002, 0.045, 0.036, 0.04, 0.092, 0.525, 0.041, 0.0001, 0.0001, 0.038, 0.074, 0.943, 0.945, 0.0001, 0.0001, 0.009, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.013, 0.006, 0.916, 0.332, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.045, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"si\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.314, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.928, 0.001, 0.061, 0.001, 0.001, 0.005, 0.0001, 0.009, 0.059, 0.059, 0.0001, 0.001, 0.185, 0.034, 0.404, 0.009, 0.086, 0.094, 0.056, 0.028, 0.026, 0.03, 0.026, 0.025, 0.029, 0.049, 0.012, 0.004, 0.002, 0.002, 0.002, 0.002, 0.0001, 0.015, 0.008, 0.015, 0.008, 0.007, 0.006, 0.005, 0.007, 0.015, 0.002, 0.003, 0.006, 0.01, 0.006, 0.006, 0.01, 0.001, 0.006, 0.015, 0.013, 0.004, 0.004, 0.005, 0.001, 0.001, 0.001, 0.005, 0.0001, 0.005, 0.0001, 0.003, 0.0001, 0.173, 0.028, 0.067, 0.071, 0.23, 0.039, 0.038, 0.081, 0.157, 0.003, 0.016, 0.086, 0.051, 0.142, 0.142, 0.042, 0.002, 0.132, 0.124, 0.159, 0.054, 0.019, 0.027, 0.007, 0.032, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 2.252, 0.201, 0.275, 1.149, 0.707, 0.497, 0.106, 0.13, 0.003, 0.08, 2.719, 0.062, 0.013, 0.472, 0.0001, 1.466, 0.446, 0.166, 2.231, 0.489, 1.129, 0.007, 0.144, 0.0001, 0.039, 0.612, 2.039, 0.034, 0.759, 0.19, 0.015, 0.026, 0.094, 0.008, 0.192, 0.001, 0.005, 0.01, 0.001, 0.537, 0.012, 0.162, 0.001, 0.275, 0.003, 1.26, 0.067, 0.843, 0.165, 1.921, 0.001, 0.089, 0.809, 0.008, 15.577, 14.437, 1.305, 0.017, 1.681, 1.481, 0.0001, 0.796, 0.0001, 0.001, 0.0001, 0.0001, 0.014, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 29.588, 0.0001, 0.504, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sm\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.213, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 19.463, 0.008, 0.168, 0.0001, 0.003, 0.014, 0.002, 0.885, 0.148, 0.148, 0.0001, 0.001, 1.0, 0.173, 0.914, 0.009, 0.254, 0.312, 0.179, 0.14, 0.095, 0.115, 0.095, 0.086, 0.112, 0.168, 0.033, 0.027, 0.006, 0.002, 0.006, 0.005, 0.0001, 0.462, 0.087, 0.119, 0.039, 0.23, 0.233, 0.074, 0.06, 0.345, 0.031, 0.147, 0.149, 0.348, 0.135, 0.431, 0.236, 0.003, 0.115, 0.459, 0.28, 0.072, 0.088, 0.128, 0.02, 0.007, 0.009, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 15.436, 0.147, 0.251, 0.268, 7.552, 1.798, 1.939, 0.261, 7.65, 0.014, 0.507, 6.117, 2.84, 3.141, 6.14, 1.094, 0.011, 1.189, 2.656, 4.384, 4.707, 0.608, 0.084, 0.018, 0.145, 0.038, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.05, 0.151, 0.0001, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.003, 0.002, 0.017, 0.002, 0.001, 0.003, 0.0001, 0.0001, 0.02, 0.001, 0.001, 0.0001, 0.001, 0.039, 0.002, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.005, 0.004, 0.0001, 0.011, 0.001, 0.001, 0.001, 0.001, 0.001, 0.006, 0.001, 0.028, 0.001, 0.004, 0.001, 0.001, 0.003, 0.003, 0.001, 0.003, 0.002, 0.002, 0.0001, 0.001, 0.002, 0.001, 0.002, 0.086, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.005, 0.033, 0.176, 0.042, 0.0001, 0.0001, 0.0001, 0.001, 0.085, 0.001, 0.0001, 0.0001, 0.007, 0.0001, 0.006, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.046, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.427, 0.001, 0.194, 0.0001, 0.001, 0.007, 0.003, 0.024, 0.281, 0.281, 0.0001, 0.005, 0.597, 0.124, 0.956, 0.038, 0.114, 0.113, 0.073, 0.04, 0.036, 0.036, 0.026, 0.025, 0.034, 0.053, 0.08, 0.124, 0.002, 0.009, 0.002, 0.006, 0.0001, 0.169, 0.097, 0.234, 0.083, 0.107, 0.043, 0.1, 0.097, 0.095, 0.037, 0.196, 0.037, 0.454, 0.178, 0.024, 0.119, 0.003, 0.094, 0.231, 0.097, 0.036, 0.089, 0.031, 0.003, 0.009, 0.113, 0.039, 0.0001, 0.038, 0.0001, 0.002, 0.0001, 12.237, 1.335, 1.505, 2.374, 5.54, 0.412, 1.524, 3.199, 8.126, 0.115, 3.86, 0.667, 3.205, 6.578, 4.667, 1.202, 0.019, 4.537, 2.41, 2.721, 5.562, 2.325, 2.211, 0.043, 1.41, 2.325, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.012, 0.003, 0.001, 0.003, 0.001, 0.001, 0.001, 0.0001, 0.017, 0.001, 0.004, 0.001, 0.004, 0.0001, 0.001, 0.001, 0.01, 0.005, 0.003, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.016, 0.001, 0.004, 0.001, 0.001, 0.0001, 0.0001, 0.009, 0.004, 0.001, 0.001, 0.001, 0.001, 0.003, 0.003, 0.003, 0.004, 0.008, 0.001, 0.0001, 0.002, 0.0001, 0.001, 0.004, 0.002, 0.002, 0.002, 0.001, 0.001, 0.002, 0.002, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.011, 0.016, 0.003, 0.002, 0.0001, 0.0001, 0.0001, 0.037, 0.008, 0.027, 0.001, 0.001, 0.002, 0.001, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"so\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.235, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.454, 0.003, 0.106, 0.0001, 0.001, 0.01, 0.006, 0.044, 0.175, 0.179, 0.001, 0.006, 0.698, 0.181, 0.663, 0.023, 0.173, 0.237, 0.118, 0.074, 0.068, 0.076, 0.069, 0.062, 0.061, 0.116, 0.103, 0.039, 0.006, 0.095, 0.008, 0.006, 0.001, 0.277, 0.176, 0.197, 0.21, 0.058, 0.067, 0.135, 0.123, 0.156, 0.069, 0.122, 0.08, 0.279, 0.092, 0.046, 0.025, 0.078, 0.077, 0.341, 0.096, 0.053, 0.009, 0.145, 0.085, 0.037, 0.009, 0.058, 0.001, 0.058, 0.0001, 0.009, 0.001, 20.28, 1.752, 0.781, 4.408, 3.807, 0.467, 1.801, 2.804, 6.156, 0.344, 2.692, 2.981, 1.937, 3.517, 5.007, 0.065, 0.666, 2.59, 2.645, 1.488, 3.47, 0.033, 1.517, 1.277, 3.257, 0.024, 0.006, 0.007, 0.006, 0.0001, 0.0001, 0.044, 0.021, 0.016, 0.015, 0.092, 0.046, 0.041, 0.026, 0.037, 0.007, 0.048, 0.005, 0.002, 0.004, 0.027, 0.011, 0.01, 0.009, 0.012, 0.004, 0.002, 0.001, 0.001, 0.002, 0.003, 0.016, 0.0001, 0.0001, 0.009, 0.011, 0.002, 0.005, 0.026, 0.005, 0.004, 0.02, 0.008, 0.009, 0.004, 0.102, 0.029, 0.015, 0.023, 0.008, 0.009, 0.018, 0.009, 0.021, 0.011, 0.034, 0.006, 0.02, 0.009, 0.011, 0.006, 0.006, 0.005, 0.024, 0.019, 0.018, 0.004, 0.003, 0.001, 0.004, 0.0001, 0.0001, 0.03, 0.015, 0.007, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.36, 0.404, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.045, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.034, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sq\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.871, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.83, 0.005, 0.212, 0.0001, 0.001, 0.008, 0.002, 0.025, 0.142, 0.143, 0.001, 0.002, 0.876, 0.197, 0.817, 0.021, 0.247, 0.322, 0.187, 0.096, 0.094, 0.095, 0.084, 0.083, 0.096, 0.185, 0.067, 0.019, 0.003, 0.004, 0.003, 0.004, 0.0001, 0.232, 0.164, 0.084, 0.121, 0.088, 0.104, 0.113, 0.084, 0.118, 0.051, 0.274, 0.113, 0.216, 0.178, 0.042, 0.229, 0.027, 0.103, 0.291, 0.126, 0.044, 0.092, 0.017, 0.024, 0.009, 0.037, 0.024, 0.0001, 0.024, 0.0001, 0.005, 0.001, 5.42, 0.732, 0.432, 2.174, 7.144, 0.635, 1.01, 2.972, 6.09, 2.066, 2.05, 2.101, 2.386, 4.875, 2.895, 1.724, 0.557, 5.177, 3.826, 5.956, 2.462, 1.012, 0.037, 0.057, 0.423, 0.487, 0.0001, 0.007, 0.0001, 0.0001, 0.0001, 0.107, 0.006, 0.004, 0.005, 0.003, 0.002, 0.002, 0.017, 0.002, 0.002, 0.001, 0.01, 0.001, 0.002, 0.002, 0.001, 0.001, 0.008, 0.001, 0.019, 0.002, 0.001, 0.001, 0.001, 0.003, 0.015, 0.001, 0.001, 0.032, 0.031, 0.002, 0.003, 0.048, 0.005, 0.005, 0.002, 0.005, 0.002, 0.002, 0.098, 0.005, 0.011, 0.001, 5.762, 0.002, 0.004, 0.002, 0.002, 0.006, 0.006, 0.012, 0.004, 0.005, 0.003, 0.003, 0.002, 0.003, 0.003, 0.003, 0.004, 0.006, 0.003, 0.003, 0.003, 0.0001, 0.0001, 0.063, 5.926, 0.008, 0.006, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.023, 0.009, 0.015, 0.012, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.007, 0.008, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.106, 0.002, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"srn\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.777, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 18.537, 0.004, 0.236, 0.0001, 0.0001, 0.009, 0.0001, 0.081, 0.222, 0.175, 0.0001, 0.0001, 0.673, 0.268, 1.397, 0.005, 0.412, 0.368, 0.15, 0.085, 0.102, 0.103, 0.102, 0.071, 0.07, 0.14, 0.041, 0.016, 0.015, 0.002, 0.015, 0.0001, 0.0001, 0.384, 0.184, 0.068, 0.478, 0.061, 0.057, 0.098, 0.039, 0.172, 0.08, 0.05, 0.052, 0.288, 0.1, 0.075, 0.116, 0.004, 0.117, 0.271, 0.146, 0.008, 0.023, 0.047, 0.004, 0.014, 0.007, 0.005, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 8.95, 2.176, 0.221, 2.431, 7.818, 1.651, 1.874, 0.226, 8.782, 0.064, 2.479, 1.698, 2.095, 8.318, 4.117, 1.376, 0.003, 4.52, 3.577, 2.919, 3.347, 0.156, 1.329, 0.018, 1.038, 0.054, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.042, 0.007, 0.007, 0.002, 0.003, 0.002, 0.003, 0.006, 0.003, 0.001, 0.002, 0.001, 0.006, 0.003, 0.002, 0.005, 0.004, 0.002, 0.001, 0.035, 0.002, 0.002, 0.002, 0.006, 0.002, 0.002, 0.002, 0.002, 0.002, 0.007, 0.002, 0.002, 0.024, 0.012, 0.002, 0.005, 0.004, 0.007, 0.002, 0.002, 0.012, 0.012, 0.006, 0.009, 0.002, 0.021, 0.005, 0.003, 0.003, 0.003, 0.034, 0.007, 0.002, 0.002, 0.002, 0.0001, 0.005, 0.007, 0.019, 0.009, 0.005, 0.003, 0.004, 0.012, 0.0001, 0.0001, 0.029, 0.098, 0.021, 0.025, 0.002, 0.002, 0.002, 0.005, 0.001, 0.003, 0.0001, 0.0001, 0.01, 0.004, 0.009, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.053, 0.0001, 0.0001, 0.016, 0.016, 0.0001, 0.01, 0.0001, 0.0001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ss\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.873, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.454, 0.015, 0.301, 0.001, 0.003, 0.01, 0.003, 0.035, 0.203, 0.202, 0.001, 0.0001, 0.685, 0.328, 0.962, 0.019, 0.22, 0.221, 0.137, 0.048, 0.066, 0.07, 0.054, 0.061, 0.082, 0.144, 0.105, 0.052, 0.007, 0.003, 0.008, 0.003, 0.0001, 0.231, 0.18, 0.097, 0.094, 0.111, 0.055, 0.072, 0.058, 0.259, 0.082, 0.196, 0.342, 0.348, 0.356, 0.028, 0.088, 0.003, 0.097, 0.319, 0.164, 0.113, 0.024, 0.061, 0.025, 0.043, 0.044, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 10.793, 2.656, 0.706, 1.31, 8.505, 1.004, 2.081, 2.919, 7.091, 0.258, 4.271, 5.701, 2.568, 6.606, 3.595, 0.825, 0.028, 0.782, 3.437, 3.569, 4.546, 0.696, 2.323, 0.017, 1.567, 0.734, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.037, 0.016, 0.007, 0.01, 0.014, 0.008, 0.007, 0.004, 0.009, 0.007, 0.013, 0.004, 0.003, 0.014, 0.015, 0.004, 0.003, 0.006, 0.003, 0.008, 0.006, 0.002, 0.007, 0.004, 0.002, 0.004, 0.007, 0.002, 0.01, 0.003, 0.007, 0.003, 0.09, 0.039, 0.013, 0.006, 0.01, 0.005, 0.005, 0.023, 0.007, 0.024, 0.007, 0.009, 0.005, 0.109, 0.006, 0.007, 0.018, 0.014, 0.009, 0.035, 0.024, 0.01, 0.007, 0.005, 0.015, 0.006, 0.031, 0.01, 0.005, 0.01, 0.008, 0.005, 0.0001, 0.0001, 0.085, 0.273, 0.013, 0.008, 0.0001, 0.0001, 0.0001, 0.005, 0.001, 0.002, 0.002, 0.0001, 0.003, 0.002, 0.061, 0.022, 0.001, 0.0001, 0.0001, 0.003, 0.002, 0.003, 0.059, 0.053, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.042, 0.021, 0.034, 0.0001, 0.001, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.003, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"st\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.411, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.42, 0.002, 0.079, 0.0001, 0.001, 0.016, 0.003, 0.083, 0.165, 0.167, 0.001, 0.001, 0.789, 0.143, 0.973, 0.021, 0.355, 0.325, 0.221, 0.104, 0.116, 0.113, 0.108, 0.098, 0.108, 0.15, 0.061, 0.016, 0.007, 0.005, 0.006, 0.001, 0.0001, 0.408, 0.587, 0.149, 0.148, 0.115, 0.088, 0.067, 0.172, 0.071, 0.055, 0.339, 0.212, 0.509, 0.175, 0.046, 0.141, 0.01, 0.115, 0.317, 0.165, 0.126, 0.071, 0.047, 0.0001, 0.019, 0.026, 0.011, 0.0001, 0.01, 0.0001, 0.005, 0.0001, 12.26, 2.144, 0.403, 1.165, 9.234, 0.827, 1.837, 3.801, 3.704, 0.349, 2.878, 4.66, 2.188, 4.177, 7.024, 1.54, 0.085, 2.344, 4.067, 4.22, 1.114, 0.282, 1.372, 0.049, 0.996, 0.173, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.013, 0.01, 0.009, 0.0001, 0.005, 0.007, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.013, 0.003, 0.001, 0.009, 0.001, 0.001, 0.004, 0.005, 0.0001, 0.0001, 0.002, 0.001, 0.01, 0.006, 0.004, 0.004, 0.003, 0.0001, 0.0001, 0.049, 0.052, 0.003, 0.003, 0.006, 0.002, 0.001, 0.006, 0.002, 0.022, 0.037, 0.001, 0.003, 0.01, 0.0001, 0.001, 0.004, 0.002, 0.001, 0.03, 0.056, 0.001, 0.001, 0.001, 0.004, 0.001, 0.002, 0.007, 0.001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.046, 0.167, 0.019, 0.086, 0.0001, 0.003, 0.001, 0.01, 0.001, 0.003, 0.0001, 0.001, 0.0001, 0.0001, 0.01, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.008, 0.013, 0.0001, 0.001, 0.004, 0.002, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"stq\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.516, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.229, 0.003, 0.416, 0.007, 0.001, 0.015, 0.001, 0.047, 0.208, 0.207, 0.008, 0.003, 0.814, 0.425, 1.065, 0.021, 0.376, 0.623, 0.234, 0.148, 0.183, 0.183, 0.241, 0.167, 0.231, 0.214, 0.089, 0.019, 0.072, 0.007, 0.069, 0.006, 0.0001, 0.293, 0.408, 0.089, 0.454, 0.155, 0.334, 0.214, 0.273, 0.205, 0.248, 0.241, 0.264, 0.372, 0.199, 0.14, 0.214, 0.005, 0.245, 0.798, 0.226, 0.158, 0.049, 0.246, 0.003, 0.006, 0.016, 0.02, 0.0001, 0.02, 0.0001, 0.001, 0.0001, 3.929, 0.935, 0.799, 3.858, 10.176, 1.298, 1.131, 1.308, 4.615, 0.883, 2.156, 2.674, 1.358, 6.685, 4.841, 0.816, 0.012, 4.246, 3.53, 4.621, 4.666, 0.159, 1.055, 0.042, 0.141, 0.124, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.049, 0.004, 0.003, 0.001, 0.07, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.004, 0.002, 0.001, 0.003, 0.001, 0.009, 0.014, 0.001, 0.002, 0.008, 0.004, 0.005, 0.004, 0.021, 0.009, 0.015, 0.001, 2.394, 0.001, 0.001, 0.002, 0.004, 0.014, 0.003, 0.002, 0.001, 0.007, 0.002, 0.002, 0.004, 0.002, 0.026, 0.006, 0.003, 0.001, 0.134, 0.001, 0.003, 0.002, 0.004, 0.004, 0.245, 0.003, 0.002, 0.003, 0.0001, 0.0001, 0.038, 2.918, 0.006, 0.011, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.002, 0.001, 0.0001, 0.013, 0.006, 0.008, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.048, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"su\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.293, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.195, 0.001, 0.272, 0.0001, 0.001, 0.006, 0.001, 0.02, 0.129, 0.129, 0.0001, 0.002, 1.05, 0.168, 1.046, 0.037, 0.48, 0.412, 0.411, 0.202, 0.173, 0.175, 0.161, 0.145, 0.144, 0.197, 0.036, 0.015, 0.003, 0.003, 0.003, 0.001, 0.0001, 0.394, 0.22, 0.151, 0.149, 0.042, 0.047, 0.094, 0.073, 0.227, 0.16, 0.402, 0.071, 0.278, 0.12, 0.097, 0.305, 0.014, 0.09, 0.368, 0.175, 0.05, 0.031, 0.057, 0.016, 0.027, 0.009, 0.008, 0.0001, 0.008, 0.0001, 0.005, 0.0001, 13.373, 1.612, 0.819, 2.725, 4.093, 0.314, 2.685, 1.583, 5.788, 0.997, 2.729, 2.341, 2.09, 7.706, 2.801, 1.889, 0.016, 3.889, 3.272, 4.14, 4.781, 0.134, 0.635, 0.029, 0.708, 0.032, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.025, 0.005, 0.003, 0.004, 0.006, 0.004, 0.004, 0.002, 0.004, 0.073, 0.003, 0.001, 0.001, 0.004, 0.007, 0.003, 0.003, 0.002, 0.003, 0.007, 0.008, 0.001, 0.001, 0.001, 0.001, 0.004, 0.001, 0.001, 0.004, 0.004, 0.001, 0.001, 0.047, 0.002, 0.001, 0.002, 0.004, 0.002, 0.002, 0.006, 0.003, 2.276, 0.003, 0.002, 0.001, 0.002, 0.007, 0.002, 0.004, 0.005, 0.002, 0.002, 0.001, 0.001, 0.002, 0.001, 0.002, 0.003, 0.002, 0.001, 0.002, 0.033, 0.001, 0.033, 0.0001, 0.0001, 0.051, 2.355, 0.003, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.02, 0.037, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.007, 0.025, 0.004, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.032, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"sw\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.454, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.93, 0.002, 0.217, 0.002, 0.001, 0.01, 0.003, 0.027, 0.171, 0.171, 0.001, 0.001, 0.703, 0.109, 0.942, 0.015, 0.41, 0.383, 0.266, 0.126, 0.108, 0.126, 0.108, 0.107, 0.119, 0.201, 0.062, 0.024, 0.003, 0.004, 0.003, 0.003, 0.0001, 0.226, 0.167, 0.122, 0.086, 0.058, 0.057, 0.065, 0.116, 0.13, 0.09, 0.638, 0.08, 0.504, 0.137, 0.044, 0.113, 0.006, 0.074, 0.173, 0.147, 0.165, 0.059, 0.218, 0.013, 0.04, 0.023, 0.04, 0.0001, 0.04, 0.001, 0.001, 0.001, 16.478, 1.326, 0.611, 1.343, 3.374, 0.678, 1.131, 2.383, 9.629, 0.827, 4.598, 2.609, 3.253, 5.284, 3.187, 0.805, 0.008, 1.616, 2.094, 2.468, 4.443, 0.427, 3.161, 0.026, 2.095, 1.273, 0.001, 0.006, 0.001, 0.0001, 0.0001, 0.04, 0.005, 0.004, 0.002, 0.004, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.001, 0.002, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.013, 0.002, 0.001, 0.001, 0.001, 0.002, 0.006, 0.001, 0.001, 0.009, 0.008, 0.001, 0.004, 0.009, 0.003, 0.002, 0.002, 0.003, 0.001, 0.001, 0.005, 0.003, 0.009, 0.001, 0.002, 0.001, 0.002, 0.001, 0.002, 0.005, 0.009, 0.009, 0.004, 0.002, 0.003, 0.004, 0.001, 0.004, 0.003, 0.003, 0.003, 0.006, 0.003, 0.002, 0.003, 0.0001, 0.0001, 0.018, 0.029, 0.009, 0.005, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.014, 0.007, 0.011, 0.004, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.005, 0.012, 0.01, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.038, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"szl\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.884, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.63, 0.002, 0.452, 0.0001, 0.0001, 0.012, 0.001, 0.026, 0.296, 0.296, 0.001, 0.001, 1.094, 0.318, 1.181, 0.015, 0.332, 0.469, 0.289, 0.138, 0.131, 0.151, 0.118, 0.131, 0.157, 0.273, 0.087, 0.014, 0.006, 0.003, 0.006, 0.0001, 0.0001, 0.207, 0.209, 0.155, 0.118, 0.048, 0.111, 0.139, 0.08, 0.122, 0.125, 0.213, 0.123, 0.287, 0.122, 0.062, 0.309, 0.005, 0.156, 0.329, 0.126, 0.154, 0.05, 0.233, 0.034, 0.017, 0.083, 0.004, 0.0001, 0.004, 0.0001, 0.006, 0.001, 5.741, 0.894, 2.016, 2.128, 5.35, 0.327, 1.279, 0.968, 3.438, 2.841, 2.633, 2.099, 2.293, 3.364, 5.857, 1.423, 0.012, 3.389, 2.85, 2.58, 2.277, 0.102, 3.144, 0.017, 3.623, 2.205, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.191, 0.035, 0.624, 0.044, 0.945, 0.014, 0.009, 0.333, 0.008, 0.003, 0.006, 0.005, 0.012, 0.221, 0.005, 0.196, 0.006, 0.005, 0.003, 0.168, 0.01, 0.003, 0.005, 0.005, 0.005, 0.109, 0.059, 0.562, 0.005, 0.005, 0.004, 0.006, 0.062, 0.111, 0.006, 0.016, 0.01, 0.004, 0.004, 0.012, 0.011, 0.03, 0.005, 0.012, 0.003, 0.012, 0.008, 1.67, 0.032, 0.015, 0.058, 0.035, 0.048, 0.018, 0.012, 0.004, 0.02, 0.013, 0.335, 0.026, 0.282, 0.022, 0.098, 0.006, 0.0001, 0.0001, 0.109, 0.208, 0.455, 5.073, 0.0001, 0.001, 0.0001, 0.008, 0.003, 0.003, 0.004, 0.0001, 0.015, 0.008, 0.161, 0.06, 0.003, 0.002, 0.0001, 0.003, 0.001, 0.009, 0.025, 0.019, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.011, 0.01, 0.176, 0.006, 0.001, 0.005, 0.003, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ta\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.357, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.862, 0.001, 0.077, 0.0001, 0.001, 0.006, 0.001, 0.007, 0.055, 0.056, 0.0001, 0.001, 0.234, 0.03, 0.384, 0.005, 0.084, 0.106, 0.063, 0.029, 0.028, 0.034, 0.027, 0.032, 0.031, 0.052, 0.017, 0.006, 0.002, 0.002, 0.002, 0.001, 0.0001, 0.008, 0.004, 0.008, 0.004, 0.004, 0.003, 0.005, 0.004, 0.006, 0.002, 0.003, 0.003, 0.005, 0.004, 0.003, 0.008, 0.0001, 0.004, 0.008, 0.005, 0.002, 0.002, 0.002, 0.001, 0.001, 0.0001, 0.006, 0.0001, 0.006, 0.0001, 0.002, 0.0001, 0.062, 0.006, 0.017, 0.014, 0.042, 0.007, 0.009, 0.018, 0.038, 0.001, 0.006, 0.024, 0.018, 0.035, 0.032, 0.011, 0.001, 0.036, 0.022, 0.032, 0.017, 0.005, 0.004, 0.002, 0.01, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.122, 2.149, 0.144, 0.01, 0.0001, 0.297, 0.436, 0.597, 0.764, 0.136, 0.24, 0.226, 0.005, 5.298, 0.158, 0.027, 0.013, 0.0001, 0.078, 0.014, 0.0001, 2.36, 0.0001, 0.0001, 0.001, 0.171, 0.627, 0.0001, 0.037, 0.002, 0.021, 1.319, 0.014, 0.0001, 0.001, 0.32, 2.012, 0.001, 0.001, 0.0001, 0.539, 0.989, 1.521, 0.0001, 0.0001, 0.001, 23.215, 10.185, 1.322, 0.801, 1.028, 0.757, 0.189, 0.942, 0.0001, 0.015, 0.06, 0.015, 0.0001, 0.0001, 0.0001, 0.0001, 1.18, 2.177, 0.0001, 0.0001, 0.016, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 31.245, 0.0001, 0.013, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tcy\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.391, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.751, 0.001, 0.026, 0.0001, 0.0001, 0.002, 0.0001, 0.028, 0.048, 0.047, 0.0001, 0.001, 0.244, 0.028, 0.533, 0.012, 0.014, 0.02, 0.01, 0.005, 0.005, 0.007, 0.006, 0.004, 0.008, 0.009, 0.009, 0.003, 0.002, 0.003, 0.002, 0.002, 0.0001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.003, 0.0001, 0.001, 0.001, 0.02, 0.002, 0.008, 0.006, 0.018, 0.002, 0.005, 0.006, 0.017, 0.0001, 0.003, 0.009, 0.008, 0.014, 0.015, 0.008, 0.0001, 0.013, 0.012, 0.015, 0.006, 0.002, 0.005, 0.0001, 0.003, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.354, 1.789, 1.221, 0.031, 0.0001, 0.268, 1.686, 0.484, 0.152, 0.21, 0.745, 0.196, 0.087, 4.125, 0.064, 0.014, 0.014, 0.0001, 0.109, 0.011, 0.001, 1.28, 0.033, 0.613, 0.012, 0.007, 0.23, 0.003, 0.404, 0.002, 0.011, 0.433, 0.058, 1.007, 0.002, 0.198, 1.312, 0.064, 1.397, 0.124, 1.439, 0.012, 1.248, 0.035, 0.624, 0.105, 0.769, 0.62, 1.755, 0.0001, 22.872, 9.408, 0.0001, 0.629, 0.164, 0.121, 0.665, 0.124, 0.0001, 0.0001, 0.003, 0.0001, 1.377, 1.63, 0.0001, 0.0001, 0.05, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.955, 0.0001, 0.194, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"te\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.34, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.746, 0.003, 0.051, 0.0001, 0.001, 0.003, 0.002, 0.007, 0.042, 0.043, 0.0001, 0.001, 0.336, 0.028, 0.611, 0.018, 0.129, 0.152, 0.069, 0.038, 0.034, 0.073, 0.03, 0.032, 0.034, 0.047, 0.02, 0.007, 0.002, 0.004, 0.002, 0.002, 0.0001, 0.008, 0.004, 0.006, 0.005, 0.003, 0.003, 0.002, 0.002, 0.006, 0.001, 0.002, 0.003, 0.005, 0.003, 0.002, 0.005, 0.0001, 0.003, 0.008, 0.005, 0.003, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.006, 0.0001, 0.007, 0.0001, 0.005, 0.0001, 0.053, 0.008, 0.019, 0.022, 0.056, 0.009, 0.01, 0.021, 0.046, 0.001, 0.004, 0.022, 0.015, 0.038, 0.038, 0.014, 0.0001, 0.036, 0.036, 0.045, 0.017, 0.006, 0.006, 0.002, 0.007, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.485, 1.801, 1.898, 0.051, 0.0001, 0.236, 0.427, 0.575, 0.238, 0.222, 0.152, 0.685, 0.105, 2.799, 0.055, 0.027, 0.006, 0.0001, 0.047, 0.007, 0.005, 1.329, 0.049, 0.668, 0.014, 0.002, 0.428, 0.004, 0.25, 0.001, 0.004, 0.537, 0.039, 0.598, 0.002, 0.137, 0.864, 0.099, 0.843, 0.149, 1.628, 0.0001, 0.909, 0.085, 0.267, 0.128, 0.942, 0.804, 25.531, 7.165, 1.487, 0.074, 0.0001, 0.877, 0.211, 0.153, 0.855, 0.145, 0.0001, 0.001, 0.0001, 0.0001, 2.169, 2.359, 0.0001, 0.0001, 0.014, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 30.736, 0.0001, 0.069, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tet\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.506, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.056, 0.014, 0.345, 0.0001, 0.004, 0.018, 0.001, 0.455, 0.383, 0.382, 0.001, 0.004, 1.067, 0.53, 0.968, 0.029, 0.443, 0.39, 0.316, 0.132, 0.112, 0.137, 0.105, 0.106, 0.119, 0.181, 0.186, 0.018, 0.015, 0.005, 0.015, 0.003, 0.0001, 0.338, 0.226, 0.145, 0.169, 0.132, 0.156, 0.098, 0.111, 0.215, 0.061, 0.136, 0.43, 0.301, 0.181, 0.101, 0.266, 0.01, 0.137, 0.345, 0.37, 0.107, 0.065, 0.041, 0.021, 0.008, 0.014, 0.01, 0.0001, 0.01, 0.0001, 0.0001, 0.0001, 11.569, 1.502, 0.408, 2.068, 6.067, 0.587, 0.66, 2.225, 7.509, 0.16, 2.246, 2.814, 2.311, 6.307, 4.401, 1.282, 0.035, 4.022, 4.063, 3.545, 4.826, 0.518, 0.1, 0.064, 0.126, 0.341, 0.0001, 0.009, 0.0001, 0.0001, 0.0001, 0.318, 0.081, 0.003, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.002, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.015, 0.001, 0.0001, 0.0001, 0.001, 0.004, 0.275, 0.001, 0.0001, 0.014, 0.013, 0.001, 0.002, 0.021, 0.254, 0.002, 0.025, 0.0001, 0.0001, 0.003, 0.02, 0.002, 0.389, 0.006, 0.001, 0.001, 0.167, 0.001, 0.001, 0.002, 0.048, 0.071, 0.284, 0.01, 0.003, 0.001, 0.0001, 0.001, 0.001, 0.076, 0.003, 0.014, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.1, 1.362, 0.004, 0.006, 0.0001, 0.0001, 0.0001, 0.009, 0.011, 0.0001, 0.0001, 0.0001, 0.007, 0.003, 0.006, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.316, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tg\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.272, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.893, 0.001, 0.026, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.324, 0.326, 0.0001, 0.001, 0.765, 0.105, 0.581, 0.006, 0.139, 0.257, 0.13, 0.073, 0.063, 0.072, 0.065, 0.068, 0.082, 0.185, 0.026, 0.048, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.026, 0.01, 0.018, 0.007, 0.005, 0.01, 0.006, 0.007, 0.018, 0.002, 0.005, 0.008, 0.009, 0.006, 0.004, 0.009, 0.001, 0.007, 0.015, 0.007, 0.003, 0.006, 0.004, 0.006, 0.002, 0.002, 0.004, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.081, 0.01, 0.03, 0.023, 0.086, 0.012, 0.015, 0.021, 0.065, 0.002, 0.009, 0.037, 0.017, 0.055, 0.061, 0.017, 0.001, 0.07, 0.039, 0.054, 0.023, 0.01, 0.007, 0.003, 0.013, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.968, 1.483, 1.764, 1.455, 0.398, 0.384, 0.008, 0.116, 0.704, 0.002, 0.17, 0.01, 0.024, 0.035, 0.045, 0.663, 0.178, 0.263, 0.119, 0.126, 0.303, 0.007, 0.009, 0.022, 0.136, 0.003, 0.143, 0.343, 0.148, 0.063, 0.071, 0.071, 0.134, 0.159, 0.101, 0.347, 0.121, 0.05, 0.002, 0.026, 0.059, 0.003, 0.003, 0.057, 0.003, 0.035, 0.012, 0.164, 5.899, 1.075, 1.071, 1.816, 2.336, 1.339, 0.082, 0.882, 4.885, 0.258, 1.014, 1.438, 1.445, 2.22, 3.885, 0.208, 0.0001, 0.0001, 0.132, 0.006, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.002, 0.001, 30.166, 10.131, 1.965, 0.481, 0.0001, 0.0001, 0.0001, 0.0001, 0.024, 0.016, 0.001, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.209, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ti\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.164, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.862, 0.026, 0.05, 0.0001, 0.0001, 0.012, 0.0001, 0.044, 0.1, 0.1, 0.0001, 0.0001, 0.075, 0.114, 0.14, 0.02, 0.098, 0.121, 0.073, 0.033, 0.026, 0.04, 0.027, 0.03, 0.029, 0.042, 0.024, 0.004, 0.001, 0.013, 0.001, 0.007, 0.0001, 0.018, 0.013, 0.015, 0.007, 0.006, 0.007, 0.011, 0.013, 0.022, 0.004, 0.004, 0.024, 0.018, 0.012, 0.005, 0.015, 0.004, 0.01, 0.013, 0.022, 0.007, 0.009, 0.006, 0.002, 0.004, 0.002, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.329, 0.063, 0.099, 0.16, 0.451, 0.14, 0.111, 0.211, 0.297, 0.027, 0.053, 0.155, 0.097, 0.283, 0.275, 0.071, 0.007, 0.228, 0.261, 0.255, 0.122, 0.059, 0.08, 0.007, 0.069, 0.014, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.358, 0.069, 0.074, 0.236, 0.007, 0.331, 0.023, 0.001, 9.303, 5.576, 6.47, 5.805, 1.549, 3.066, 0.251, 0.003, 0.505, 0.172, 0.135, 1.034, 0.015, 2.293, 0.054, 0.001, 0.75, 0.233, 0.32, 0.51, 0.12, 1.725, 0.08, 0.002, 0.83, 0.546, 0.753, 1.425, 0.111, 2.053, 0.138, 0.011, 0.764, 0.373, 0.244, 0.731, 0.034, 1.854, 0.258, 0.004, 1.053, 0.166, 0.551, 0.69, 0.031, 2.007, 0.179, 0.005, 0.189, 0.048, 0.045, 0.156, 0.011, 0.447, 0.067, 0.002, 0.0001, 0.0001, 0.386, 0.04, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.027, 0.012, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.008, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 27.967, 0.209, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tk\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.842, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.847, 0.005, 0.052, 0.0001, 0.001, 0.008, 0.001, 0.006, 0.121, 0.125, 0.004, 0.002, 0.691, 0.455, 1.024, 0.011, 0.191, 0.306, 0.153, 0.096, 0.091, 0.095, 0.077, 0.079, 0.095, 0.155, 0.055, 0.012, 0.028, 0.003, 0.028, 0.005, 0.0001, 0.227, 0.204, 0.012, 0.086, 0.083, 0.04, 0.177, 0.112, 0.174, 0.027, 0.109, 0.037, 0.173, 0.054, 0.141, 0.071, 0.001, 0.074, 0.173, 0.153, 0.029, 0.028, 0.04, 0.045, 0.029, 0.016, 0.01, 0.0001, 0.01, 0.001, 0.003, 0.0001, 8.711, 1.574, 0.069, 3.499, 5.666, 0.119, 2.22, 0.895, 5.266, 0.476, 2.165, 5.087, 2.1, 4.83, 1.754, 1.161, 0.002, 5.326, 1.953, 2.216, 1.612, 0.014, 0.863, 0.003, 4.905, 0.889, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.208, 0.022, 0.019, 0.011, 0.017, 0.007, 0.003, 0.027, 1.247, 0.001, 0.0001, 0.008, 0.005, 0.003, 0.002, 0.006, 0.003, 0.005, 0.002, 0.04, 0.02, 0.001, 0.017, 0.002, 0.001, 0.002, 0.001, 0.001, 0.068, 0.139, 0.083, 1.114, 0.015, 0.004, 0.009, 0.002, 0.694, 0.003, 0.003, 0.67, 0.001, 0.002, 0.0001, 0.027, 0.0001, 0.192, 0.001, 0.002, 0.056, 0.114, 0.02, 0.061, 0.013, 0.043, 0.813, 0.006, 0.038, 0.007, 0.016, 0.096, 0.984, 2.385, 0.053, 0.019, 0.0001, 0.0001, 0.268, 5.753, 0.012, 2.464, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.324, 0.111, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.054, 0.182, 0.0001, 0.005, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tl\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.527, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.015, 0.006, 0.416, 0.001, 0.001, 0.006, 0.002, 0.043, 0.2, 0.202, 0.001, 0.002, 0.702, 0.264, 0.789, 0.017, 0.219, 0.272, 0.17, 0.08, 0.075, 0.082, 0.072, 0.075, 0.087, 0.155, 0.061, 0.022, 0.066, 0.004, 0.066, 0.002, 0.0001, 0.555, 0.199, 0.186, 0.134, 0.118, 0.059, 0.112, 0.181, 0.214, 0.066, 0.204, 0.127, 0.268, 0.176, 0.063, 0.292, 0.011, 0.11, 0.398, 0.188, 0.06, 0.045, 0.055, 0.008, 0.035, 0.014, 0.016, 0.0001, 0.015, 0.001, 0.003, 0.0001, 16.44, 1.457, 0.382, 1.246, 2.379, 0.123, 6.741, 1.192, 6.121, 0.033, 2.118, 3.173, 2.569, 9.845, 3.868, 2.142, 0.019, 2.313, 4.125, 3.402, 2.226, 0.121, 0.559, 0.032, 2.131, 0.078, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.038, 0.008, 0.005, 0.004, 0.004, 0.003, 0.002, 0.002, 0.004, 0.002, 0.002, 0.002, 0.003, 0.007, 0.003, 0.002, 0.004, 0.004, 0.002, 0.014, 0.006, 0.001, 0.002, 0.001, 0.002, 0.008, 0.001, 0.002, 0.013, 0.007, 0.002, 0.002, 0.028, 0.01, 0.003, 0.002, 0.004, 0.002, 0.002, 0.004, 0.003, 0.01, 0.002, 0.004, 0.002, 0.008, 0.002, 0.002, 0.005, 0.01, 0.003, 0.007, 0.003, 0.003, 0.002, 0.002, 0.006, 0.003, 0.004, 0.003, 0.005, 0.003, 0.003, 0.003, 0.0001, 0.0001, 0.029, 0.045, 0.007, 0.011, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.012, 0.006, 0.01, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.008, 0.007, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 0.012, 0.037, 0.005, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.004, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tn\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.716, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 17.981, 0.003, 0.08, 0.013, 0.001, 0.009, 0.002, 0.01, 0.075, 0.075, 0.0001, 0.0001, 0.66, 0.106, 0.757, 0.034, 0.2, 0.226, 0.113, 0.036, 0.039, 0.039, 0.04, 0.035, 0.043, 0.09, 0.021, 0.015, 0.01, 0.005, 0.011, 0.004, 0.0001, 0.148, 0.357, 0.071, 0.097, 0.07, 0.054, 0.125, 0.028, 0.051, 0.019, 0.166, 0.104, 0.374, 0.087, 0.085, 0.102, 0.001, 0.088, 0.173, 0.113, 0.019, 0.017, 0.023, 0.006, 0.007, 0.021, 0.023, 0.0001, 0.022, 0.0001, 0.004, 0.0001, 12.488, 2.445, 0.191, 1.643, 9.389, 0.795, 4.171, 1.899, 3.702, 0.312, 2.67, 5.097, 2.631, 4.499, 8.158, 1.075, 0.008, 1.917, 4.118, 4.684, 0.837, 0.048, 2.161, 0.014, 0.955, 0.029, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.014, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.0001, 0.0001, 0.003, 0.003, 0.0001, 0.0001, 0.034, 0.011, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.036, 0.008, 0.0001, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"to\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.293, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.821, 0.001, 0.44, 0.0001, 0.0001, 0.001, 0.005, 0.111, 0.238, 0.237, 0.002, 0.0001, 0.847, 0.076, 1.066, 0.045, 0.084, 0.141, 0.063, 0.039, 0.037, 0.032, 0.036, 0.05, 0.065, 0.067, 0.09, 0.023, 0.003, 0.011, 0.005, 0.027, 0.0001, 0.126, 0.034, 0.039, 0.011, 0.049, 0.193, 0.01, 0.178, 0.123, 0.01, 0.599, 0.145, 0.204, 0.188, 0.245, 0.136, 0.001, 0.012, 0.185, 0.547, 0.059, 0.124, 0.026, 0.001, 0.005, 0.001, 0.004, 0.0001, 0.005, 0.0001, 0.002, 0.001, 10.579, 0.223, 0.423, 0.627, 6.707, 1.724, 1.525, 3.199, 6.545, 0.014, 3.573, 2.547, 1.814, 3.859, 6.712, 1.277, 0.01, 0.909, 1.504, 3.555, 4.441, 0.529, 0.312, 0.02, 0.255, 0.009, 0.0001, 0.0001, 0.0001, 0.004, 0.0001, 0.028, 0.432, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.082, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.183, 0.003, 0.0001, 0.0001, 0.001, 0.011, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.057, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.002, 0.001, 0.078, 0.0001, 0.015, 0.0001, 0.0001, 0.013, 0.0001, 0.001, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 4.517, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.022, 0.094, 0.659, 0.119, 0.0001, 0.0001, 0.0001, 0.0001, 4.513, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.024, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tpi\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.506, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.422, 0.004, 0.225, 0.0001, 0.0001, 0.006, 0.0001, 0.033, 0.226, 0.227, 0.001, 0.0001, 0.976, 0.07, 1.357, 0.011, 0.339, 0.409, 0.202, 0.102, 0.113, 0.106, 0.09, 0.101, 0.134, 0.258, 0.112, 0.01, 0.016, 0.001, 0.016, 0.001, 0.0001, 0.28, 0.281, 0.358, 0.108, 0.184, 0.096, 0.132, 0.102, 0.251, 0.103, 0.247, 0.515, 0.27, 0.273, 0.17, 0.405, 0.016, 0.129, 0.696, 0.311, 0.02, 0.133, 0.076, 0.006, 0.097, 0.011, 0.006, 0.0001, 0.006, 0.0001, 0.003, 0.0001, 9.267, 1.534, 0.295, 1.028, 5.418, 0.186, 3.091, 0.44, 8.286, 0.1, 1.968, 5.697, 3.075, 7.815, 5.428, 2.623, 0.013, 2.618, 3.22, 3.51, 1.911, 0.537, 0.798, 0.013, 0.388, 0.104, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.026, 0.016, 0.007, 0.003, 0.007, 0.001, 0.002, 0.003, 0.001, 0.001, 0.001, 0.002, 0.006, 0.002, 0.001, 0.001, 0.004, 0.002, 0.001, 0.01, 0.002, 0.002, 0.002, 0.003, 0.001, 0.004, 0.001, 0.005, 0.009, 0.009, 0.003, 0.002, 0.021, 0.037, 0.001, 0.006, 0.0001, 0.001, 0.001, 0.002, 0.002, 0.013, 0.005, 0.003, 0.004, 0.024, 0.002, 0.002, 0.006, 0.026, 0.007, 0.298, 0.002, 0.005, 0.003, 0.003, 0.01, 0.004, 0.011, 0.015, 0.005, 0.005, 0.003, 0.004, 0.0001, 0.0001, 0.019, 0.408, 0.007, 0.009, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.02, 0.011, 0.021, 0.008, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.012, 0.021, 0.009, 0.003, 0.009, 0.003, 0.001, 0.001, 0.002, 0.004, 0.003, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ts\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.117, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.445, 0.004, 0.183, 0.0001, 0.0001, 0.006, 0.001, 0.136, 0.107, 0.107, 0.0001, 0.0001, 0.868, 0.158, 0.838, 0.021, 0.152, 0.161, 0.081, 0.037, 0.038, 0.052, 0.045, 0.043, 0.056, 0.092, 0.041, 0.025, 0.03, 0.001, 0.03, 0.006, 0.0001, 0.18, 0.088, 0.068, 0.084, 0.075, 0.029, 0.061, 0.137, 0.055, 0.032, 0.132, 0.116, 0.387, 0.232, 0.02, 0.062, 0.002, 0.075, 0.171, 0.121, 0.04, 0.219, 0.021, 0.119, 0.045, 0.021, 0.003, 0.0001, 0.003, 0.0001, 0.002, 0.005, 13.463, 1.384, 0.275, 1.092, 4.958, 0.572, 1.347, 3.614, 7.958, 0.047, 4.285, 4.291, 2.768, 5.921, 3.615, 0.489, 0.025, 2.056, 2.585, 2.874, 4.929, 1.994, 3.082, 0.68, 2.172, 0.64, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.055, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.0001, 0.005, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.031, 0.0001, 0.001, 0.008, 0.008, 0.0001, 0.0001, 0.05, 0.004, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.001, 0.003, 0.0001, 0.002, 0.001, 0.005, 0.002, 0.011, 0.002, 0.0001, 0.0001, 0.001, 0.002, 0.002, 0.001, 0.002, 0.002, 0.002, 0.0001, 0.002, 0.0001, 0.0001, 0.051, 0.023, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.018, 0.006, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.054, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tt\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.086, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.219, 0.001, 0.085, 0.0001, 0.0001, 0.04, 0.0001, 0.002, 0.22, 0.221, 0.0001, 0.008, 0.529, 0.164, 0.713, 0.007, 0.223, 0.276, 0.185, 0.093, 0.09, 0.084, 0.067, 0.069, 0.089, 0.159, 0.097, 0.008, 0.002, 0.001, 0.002, 0.003, 0.0001, 0.01, 0.009, 0.017, 0.009, 0.006, 0.003, 0.002, 0.003, 0.017, 0.001, 0.009, 0.003, 0.013, 0.003, 0.003, 0.004, 0.005, 0.005, 0.013, 0.017, 0.009, 0.006, 0.002, 0.01, 0.003, 0.001, 0.002, 0.0001, 0.002, 0.0001, 0.002, 0.0001, 0.245, 0.051, 0.015, 0.059, 0.152, 0.017, 0.027, 0.019, 0.108, 0.002, 0.051, 0.14, 0.059, 0.158, 0.057, 0.025, 0.035, 0.149, 0.073, 0.108, 0.056, 0.01, 0.015, 0.014, 0.048, 0.025, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.852, 1.726, 1.824, 1.398, 0.151, 0.194, 0.076, 0.605, 0.638, 0.004, 0.1, 2.623, 0.236, 0.061, 0.057, 0.479, 0.123, 0.129, 0.053, 0.062, 0.279, 0.075, 0.02, 0.174, 0.096, 1.916, 0.222, 0.025, 0.1, 0.049, 0.069, 0.128, 0.159, 0.146, 0.119, 0.43, 0.164, 0.055, 0.003, 0.065, 0.036, 0.325, 0.0001, 0.038, 0.001, 0.013, 0.042, 0.429, 4.958, 1.044, 0.394, 1.429, 0.959, 3.011, 0.048, 0.384, 1.557, 0.433, 1.901, 3.01, 1.056, 3.108, 1.043, 0.407, 0.0001, 0.0001, 0.106, 0.225, 0.139, 0.034, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.004, 0.0001, 0.003, 0.001, 26.093, 12.748, 1.127, 2.265, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.275, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tum\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.34, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.852, 0.004, 0.573, 0.003, 0.004, 0.004, 0.017, 0.083, 0.308, 0.306, 0.001, 0.0001, 1.303, 0.412, 1.2, 0.024, 0.557, 0.476, 0.366, 0.176, 0.172, 0.213, 0.206, 0.176, 0.165, 0.191, 0.118, 0.025, 0.012, 0.007, 0.012, 0.0001, 0.001, 0.268, 0.377, 0.217, 0.158, 0.11, 0.095, 0.125, 0.123, 0.134, 0.277, 0.29, 0.111, 0.727, 0.21, 0.076, 0.143, 0.01, 0.116, 0.269, 0.294, 0.069, 0.067, 0.069, 0.003, 0.068, 0.042, 0.008, 0.0001, 0.008, 0.0001, 0.0001, 0.0001, 10.116, 1.728, 1.817, 1.937, 5.125, 1.225, 1.488, 3.251, 6.548, 0.159, 2.454, 2.854, 2.514, 5.282, 4.292, 2.074, 0.028, 2.715, 2.7, 3.62, 4.127, 0.602, 1.862, 0.051, 1.299, 0.758, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.102, 0.017, 0.014, 0.014, 0.01, 0.006, 0.008, 0.005, 0.003, 0.001, 0.007, 0.006, 0.02, 0.058, 0.017, 0.003, 0.008, 0.005, 0.001, 0.016, 0.005, 0.005, 0.003, 0.004, 0.009, 0.043, 0.004, 0.001, 0.008, 0.005, 0.006, 0.002, 0.103, 0.006, 0.008, 0.007, 0.001, 0.005, 0.009, 0.025, 0.006, 0.01, 0.003, 0.011, 0.006, 0.004, 0.0001, 0.003, 0.016, 0.015, 0.003, 0.014, 0.008, 0.112, 0.003, 0.014, 0.012, 0.008, 0.012, 0.012, 0.008, 0.009, 0.01, 0.003, 0.0001, 0.0001, 0.101, 0.045, 0.006, 0.195, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.003, 0.063, 0.038, 0.001, 0.001, 0.001, 0.006, 0.003, 0.007, 0.053, 0.034, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.016, 0.022, 0.093, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.012, 0.008, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tw\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.984, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.303, 0.001, 0.389, 0.0001, 0.001, 0.0001, 0.004, 0.077, 0.488, 0.486, 0.0001, 0.0001, 0.756, 0.118, 1.791, 0.025, 0.73, 0.614, 0.579, 0.248, 0.221, 0.18, 0.206, 0.176, 0.192, 0.286, 0.065, 0.035, 0.004, 0.0001, 0.004, 0.01, 0.0001, 0.602, 0.283, 0.296, 0.116, 0.311, 0.173, 0.2, 0.1, 0.303, 0.048, 0.367, 0.187, 0.399, 0.306, 0.149, 0.189, 0.019, 0.18, 0.508, 0.305, 0.203, 0.099, 0.096, 0.049, 0.077, 0.01, 0.003, 0.0001, 0.019, 0.0001, 0.0001, 0.0001, 8.315, 0.995, 0.605, 1.602, 5.365, 0.628, 0.659, 0.955, 4.58, 0.091, 2.249, 1.426, 1.892, 5.378, 5.608, 0.884, 0.03, 3.156, 2.583, 1.888, 2.004, 0.328, 1.708, 0.075, 2.441, 0.168, 0.0001, 0.0001, 0.0001, 0.01, 0.0001, 0.083, 0.035, 0.035, 0.017, 0.032, 0.015, 0.093, 0.059, 0.023, 0.016, 0.025, 0.022, 0.019, 0.022, 0.029, 0.012, 0.046, 0.013, 0.009, 0.017, 0.855, 0.004, 0.017, 0.017, 0.006, 0.004, 0.012, 1.236, 0.017, 0.012, 0.01, 0.004, 0.081, 0.046, 0.012, 0.012, 0.086, 0.028, 0.017, 0.054, 0.03, 0.075, 0.019, 0.012, 0.016, 0.036, 0.009, 0.019, 0.074, 0.048, 0.057, 0.049, 0.013, 2.039, 0.016, 0.03, 0.109, 0.023, 0.064, 0.039, 0.051, 0.048, 0.068, 0.015, 0.0001, 0.0001, 0.075, 0.196, 0.058, 0.036, 0.106, 0.0001, 0.001, 1.812, 0.004, 0.0001, 0.001, 0.0001, 2.053, 0.006, 0.306, 0.086, 0.0001, 0.0001, 0.0001, 0.012, 0.003, 0.267, 0.158, 0.09, 0.007, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.209, 0.016, 0.044, 0.0001, 0.016, 0.052, 0.016, 0.023, 0.012, 0.003, 0.001, 0.0001, 0.003, 0.0001, 0.0001, 0.019, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ty\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 5.596, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.482, 0.002, 0.148, 0.0001, 0.0001, 0.0001, 0.001, 0.103, 0.185, 0.187, 0.0001, 0.0001, 0.459, 0.229, 1.457, 0.013, 0.217, 0.354, 0.181, 0.099, 0.109, 0.09, 0.093, 0.094, 0.097, 0.295, 0.032, 0.014, 0.002, 0.001, 0.023, 0.0001, 0.0001, 0.336, 0.259, 0.191, 0.056, 0.549, 0.206, 0.061, 0.142, 0.109, 0.062, 0.031, 0.131, 0.411, 0.099, 0.644, 0.477, 0.008, 0.194, 0.401, 0.951, 0.146, 0.18, 0.019, 0.004, 0.015, 0.007, 0.008, 0.0001, 0.01, 0.0001, 0.003, 0.0001, 9.536, 0.253, 0.42, 0.705, 6.452, 0.803, 0.335, 1.722, 7.016, 0.092, 0.277, 1.311, 1.613, 3.693, 4.012, 0.994, 0.04, 4.455, 1.038, 5.804, 2.543, 0.371, 0.019, 0.027, 0.146, 0.201, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.052, 0.908, 0.007, 0.002, 0.007, 0.001, 0.0001, 0.003, 0.006, 0.001, 0.003, 0.002, 0.002, 1.282, 0.0001, 0.001, 0.007, 0.0001, 0.043, 0.549, 0.01, 0.0001, 0.0001, 0.003, 0.114, 1.916, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.19, 0.144, 0.074, 0.002, 0.002, 0.003, 0.003, 0.022, 0.06, 0.039, 0.051, 0.598, 0.116, 0.035, 0.003, 0.018, 0.003, 0.029, 0.506, 0.059, 0.005, 0.003, 0.0001, 0.001, 0.002, 0.008, 0.013, 0.037, 0.005, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.033, 1.417, 1.711, 1.627, 0.0001, 0.0001, 0.0001, 0.008, 0.01, 0.005, 0.002, 0.001, 0.0001, 0.0001, 0.009, 0.006, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.012, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.01, 2.037, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"tyv\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.67, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.942, 0.005, 0.141, 0.0001, 0.0001, 0.004, 0.001, 0.003, 0.097, 0.1, 0.0001, 0.001, 0.649, 0.583, 0.64, 0.009, 0.087, 0.151, 0.08, 0.042, 0.04, 0.04, 0.033, 0.032, 0.035, 0.099, 0.046, 0.011, 0.008, 0.002, 0.008, 0.008, 0.0001, 0.007, 0.002, 0.003, 0.002, 0.002, 0.002, 0.001, 0.002, 0.022, 0.0001, 0.001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.001, 0.003, 0.003, 0.001, 0.006, 0.002, 0.011, 0.001, 0.0001, 0.005, 0.0001, 0.005, 0.0001, 0.005, 0.0001, 0.081, 0.005, 0.006, 0.008, 0.025, 0.002, 0.005, 0.006, 0.02, 0.002, 0.007, 0.012, 0.016, 0.015, 0.021, 0.013, 0.003, 0.017, 0.01, 0.014, 0.01, 0.002, 0.004, 0.007, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.263, 0.883, 1.755, 1.893, 0.056, 0.377, 0.045, 1.004, 0.604, 0.005, 0.051, 2.643, 0.086, 0.75, 0.036, 0.173, 0.125, 0.135, 0.03, 0.065, 0.108, 0.011, 0.018, 0.005, 0.038, 0.005, 0.129, 0.036, 0.079, 0.041, 0.11, 0.022, 0.066, 0.107, 0.147, 0.782, 0.015, 0.082, 0.008, 0.088, 0.054, 0.476, 0.001, 0.089, 0.001, 0.039, 0.018, 0.892, 5.51, 0.98, 0.415, 1.888, 1.904, 2.436, 0.478, 0.679, 2.249, 0.486, 1.593, 2.459, 0.684, 3.034, 1.582, 0.744, 0.0001, 0.0001, 0.143, 0.011, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.002, 0.004, 0.003, 0.01, 0.001, 0.011, 0.002, 28.453, 13.514, 1.663, 0.515, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.001, 0.094, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"udm\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.306, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.09, 0.004, 0.114, 0.0001, 0.0001, 0.008, 0.0001, 0.002, 0.237, 0.238, 0.002, 0.001, 0.557, 0.317, 0.775, 0.018, 0.183, 0.302, 0.16, 0.086, 0.075, 0.092, 0.071, 0.074, 0.085, 0.189, 0.048, 0.012, 0.017, 0.014, 0.016, 0.001, 0.0001, 0.018, 0.008, 0.012, 0.004, 0.003, 0.003, 0.003, 0.003, 0.016, 0.004, 0.004, 0.006, 0.014, 0.003, 0.019, 0.021, 0.0001, 0.006, 0.011, 0.006, 0.003, 0.006, 0.001, 0.009, 0.001, 0.001, 0.003, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.242, 0.027, 0.103, 0.053, 0.195, 0.007, 0.026, 0.039, 0.148, 0.005, 0.015, 0.074, 0.03, 0.111, 0.083, 0.028, 0.002, 0.108, 0.083, 0.059, 0.078, 0.015, 0.004, 0.004, 0.02, 0.008, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 2.622, 2.823, 2.068, 1.727, 0.105, 0.092, 0.121, 0.28, 0.404, 0.054, 0.451, 2.424, 1.272, 0.932, 0.131, 0.626, 0.166, 0.634, 0.123, 0.164, 0.252, 0.027, 0.006, 0.023, 0.083, 0.009, 0.22, 0.069, 0.124, 0.088, 0.082, 0.223, 0.15, 0.209, 0.107, 0.132, 0.033, 0.405, 0.01, 0.179, 0.05, 0.004, 0.001, 0.088, 0.001, 0.03, 0.018, 0.022, 2.886, 0.44, 0.8, 0.564, 1.075, 2.236, 0.315, 1.165, 1.904, 0.34, 1.795, 2.214, 1.337, 2.854, 2.759, 0.664, 0.0001, 0.0001, 0.24, 0.028, 0.005, 0.005, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.023, 0.0001, 0.001, 0.0001, 25.262, 16.34, 0.005, 0.714, 0.0001, 0.005, 0.001, 0.002, 0.005, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.006, 0.277, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ug\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.4, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 6.843, 0.005, 0.045, 0.0001, 0.0001, 0.006, 0.0001, 0.05, 0.059, 0.061, 0.001, 0.001, 0.064, 0.182, 0.431, 0.006, 0.116, 0.137, 0.086, 0.058, 0.051, 0.055, 0.044, 0.042, 0.045, 0.072, 0.055, 0.007, 0.018, 0.009, 0.017, 0.0001, 0.0001, 0.014, 0.005, 0.004, 0.003, 0.002, 0.001, 0.002, 0.002, 0.011, 0.008, 0.009, 0.003, 0.013, 0.002, 0.002, 0.005, 0.001, 0.002, 0.015, 0.014, 0.019, 0.001, 0.002, 0.002, 0.003, 0.0001, 0.003, 0.001, 0.003, 0.0001, 0.008, 0.0001, 0.198, 0.04, 0.041, 0.081, 0.144, 0.022, 0.07, 0.096, 0.317, 0.009, 0.06, 0.138, 0.069, 0.164, 0.09, 0.038, 0.044, 0.138, 0.091, 0.118, 0.088, 0.011, 0.018, 0.015, 0.072, 0.022, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.146, 0.075, 1.421, 1.142, 2.553, 1.322, 3.07, 1.622, 1.224, 6.252, 1.181, 0.454, 0.501, 0.027, 0.124, 0.02, 0.545, 0.041, 0.008, 0.046, 0.025, 2.705, 0.02, 0.099, 0.121, 0.09, 0.015, 0.082, 0.041, 0.012, 0.015, 0.06, 0.068, 0.006, 0.005, 0.06, 0.019, 0.028, 1.456, 3.601, 1.011, 0.28, 1.856, 0.056, 0.228, 0.623, 0.346, 2.099, 0.163, 2.119, 0.524, 1.075, 0.873, 0.045, 0.014, 0.035, 0.226, 0.052, 1.208, 0.825, 0.077, 0.089, 1.1, 0.024, 0.0001, 0.0001, 0.118, 0.051, 0.009, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.765, 0.262, 0.112, 0.09, 0.0001, 0.0001, 0.0001, 0.001, 14.938, 17.649, 1.694, 5.905, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.067, 0.002, 0.002, 0.006, 0.003, 0.003, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.007, 1.731, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ur\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.979, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.161, 0.002, 0.04, 0.0001, 0.0001, 0.001, 0.0001, 0.006, 0.157, 0.157, 0.0001, 0.001, 0.081, 0.085, 0.055, 0.007, 0.121, 0.179, 0.119, 0.082, 0.072, 0.073, 0.068, 0.065, 0.07, 0.096, 0.098, 0.002, 0.004, 0.003, 0.004, 0.0001, 0.0001, 0.02, 0.016, 0.035, 0.016, 0.006, 0.007, 0.013, 0.009, 0.011, 0.009, 0.012, 0.015, 0.025, 0.011, 0.007, 0.016, 0.003, 0.012, 0.029, 0.016, 0.005, 0.006, 0.007, 0.001, 0.005, 0.003, 0.004, 0.0001, 0.004, 0.0001, 0.004, 0.0001, 0.265, 0.03, 0.059, 0.059, 0.181, 0.032, 0.039, 0.075, 0.194, 0.006, 0.027, 0.102, 0.048, 0.197, 0.175, 0.037, 0.004, 0.142, 0.109, 0.147, 0.083, 0.021, 0.026, 0.005, 0.049, 0.011, 0.0001, 0.014, 0.0001, 0.0001, 0.0001, 0.055, 2.387, 0.534, 0.013, 1.581, 2.193, 2.297, 0.009, 2.712, 0.004, 0.024, 0.012, 4.725, 0.004, 0.025, 0.025, 0.036, 0.091, 1.735, 0.008, 0.507, 0.001, 0.001, 0.002, 0.02, 0.012, 0.0001, 0.005, 0.005, 0.004, 0.001, 0.005, 0.009, 0.069, 0.224, 0.005, 0.08, 0.002, 0.401, 5.353, 1.186, 2.395, 1.412, 0.054, 0.699, 0.376, 0.232, 1.576, 0.068, 2.734, 0.325, 1.531, 0.466, 0.218, 0.1, 0.222, 0.073, 1.112, 0.88, 0.012, 0.002, 0.002, 1.074, 0.003, 0.0001, 0.0001, 0.008, 0.011, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.005, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 18.028, 10.547, 4.494, 8.618, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.001, 0.049, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.043, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"uz\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.321, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.468, 0.001, 0.189, 0.0001, 0.0001, 0.012, 0.0001, 0.019, 0.383, 0.392, 0.002, 0.002, 1.018, 0.346, 1.56, 0.012, 0.451, 0.539, 0.363, 0.217, 0.199, 0.207, 0.182, 0.168, 0.187, 0.31, 0.029, 0.042, 0.003, 0.005, 0.003, 0.002, 0.0001, 0.288, 0.177, 0.127, 0.096, 0.051, 0.092, 0.103, 0.072, 0.123, 0.042, 0.115, 0.075, 0.277, 0.092, 0.158, 0.088, 0.099, 0.095, 0.293, 0.135, 0.08, 0.063, 0.021, 0.043, 0.077, 0.019, 0.006, 0.0001, 0.006, 0.001, 0.001, 0.005, 11.395, 1.621, 0.663, 2.97, 1.946, 0.469, 2.488, 2.791, 9.732, 0.446, 2.32, 4.562, 2.354, 4.897, 4.652, 0.487, 1.34, 4.598, 3.575, 3.341, 2.208, 1.083, 0.027, 0.322, 2.128, 0.799, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 0.456, 0.006, 0.008, 0.004, 0.002, 0.001, 0.001, 0.001, 0.003, 0.002, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.001, 0.001, 0.165, 0.164, 0.0001, 0.001, 0.001, 0.064, 0.017, 0.001, 0.002, 0.019, 0.002, 0.019, 0.002, 0.169, 0.003, 0.003, 0.0001, 0.002, 0.0001, 0.0001, 0.002, 0.007, 0.014, 0.0001, 0.005, 0.001, 0.001, 0.0001, 0.0001, 0.04, 0.006, 0.006, 0.01, 0.015, 0.009, 0.006, 0.002, 0.016, 0.002, 0.006, 0.916, 0.127, 0.009, 0.012, 0.002, 0.0001, 0.0001, 0.192, 0.06, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 1.018, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.124, 0.036, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.449, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"ve\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.731, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.522, 0.012, 0.078, 0.0001, 0.0001, 0.001, 0.0001, 0.009, 0.159, 0.16, 0.0001, 0.001, 0.539, 0.225, 1.016, 0.019, 0.145, 0.2, 0.126, 0.043, 0.046, 0.05, 0.05, 0.043, 0.035, 0.051, 0.043, 0.011, 0.01, 0.003, 0.01, 0.007, 0.001, 0.246, 0.066, 0.041, 0.13, 0.054, 0.04, 0.046, 0.163, 0.081, 0.023, 0.129, 0.141, 0.422, 0.243, 0.021, 0.074, 0.002, 0.073, 0.154, 0.414, 0.061, 0.436, 0.032, 0.007, 0.055, 0.059, 0.001, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 13.088, 1.237, 0.128, 2.934, 4.075, 0.966, 1.256, 7.989, 6.478, 0.01, 1.611, 2.964, 2.428, 5.855, 4.328, 0.793, 0.003, 1.372, 2.898, 2.532, 4.835, 2.93, 2.215, 0.021, 0.876, 1.698, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.04, 0.003, 0.001, 0.0001, 0.002, 0.021, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.005, 0.137, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.006, 0.001, 0.001, 0.006, 0.005, 0.0001, 0.0001, 0.002, 0.001, 0.008, 0.001, 0.0001, 0.0001, 0.007, 0.001, 0.0001, 0.0001, 0.0001, 0.004, 0.002, 0.0001, 0.0001, 0.001, 0.008, 0.049, 0.003, 0.004, 0.0001, 0.0001, 0.001, 0.0001, 0.157, 0.074, 0.001, 0.002, 0.0001, 0.026, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.017, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.014, 0.002, 0.006, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.006, 0.231, 0.039, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"vec\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.253, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.683, 0.003, 0.435, 0.0001, 0.0001, 0.011, 0.001, 0.612, 0.188, 0.187, 0.0001, 0.002, 0.962, 0.099, 0.799, 0.015, 0.255, 0.324, 0.176, 0.103, 0.099, 0.11, 0.096, 0.095, 0.113, 0.179, 0.07, 0.031, 0.016, 0.004, 0.016, 0.001, 0.0001, 0.22, 0.135, 0.257, 0.11, 0.212, 0.092, 0.123, 0.029, 0.211, 0.028, 0.03, 0.164, 0.197, 0.115, 0.055, 0.192, 0.012, 0.112, 0.28, 0.113, 0.043, 0.127, 0.024, 0.034, 0.008, 0.022, 0.006, 0.0001, 0.006, 0.0001, 0.006, 0.0001, 9.014, 0.584, 2.527, 3.084, 9.08, 0.695, 1.267, 0.67, 6.478, 0.14, 0.121, 3.361, 1.486, 5.29, 5.96, 1.776, 0.156, 4.436, 3.403, 4.054, 1.601, 1.042, 0.044, 0.834, 0.071, 0.222, 0.0001, 0.006, 0.0001, 0.0001, 0.0001, 0.081, 0.084, 1.282, 0.004, 0.002, 0.002, 0.001, 0.002, 0.002, 0.001, 0.001, 0.002, 0.003, 0.004, 0.001, 0.001, 0.002, 0.013, 0.001, 0.01, 0.002, 0.001, 0.001, 0.008, 0.004, 0.058, 0.055, 0.001, 0.003, 0.003, 0.0001, 0.001, 0.74, 0.012, 0.002, 0.002, 0.005, 0.001, 0.002, 0.041, 0.204, 0.163, 0.002, 0.004, 0.188, 0.007, 0.001, 0.002, 0.019, 0.005, 0.113, 0.084, 0.004, 0.003, 0.003, 0.001, 0.003, 0.085, 0.013, 0.006, 0.006, 0.01, 0.027, 0.003, 0.0001, 0.0001, 0.074, 1.6, 0.013, 1.389, 0.061, 0.0001, 0.005, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.014, 0.007, 0.012, 0.005, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.013, 0.075, 0.002, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"vep\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.78, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.379, 0.003, 0.471, 0.0001, 0.001, 0.103, 0.0001, 0.568, 0.495, 0.495, 0.0001, 0.017, 1.052, 0.379, 1.489, 0.012, 0.568, 0.707, 0.478, 0.223, 0.214, 0.232, 0.198, 0.192, 0.203, 0.325, 0.211, 0.045, 0.002, 0.001, 0.002, 0.001, 0.0001, 0.203, 0.112, 0.053, 0.077, 0.109, 0.05, 0.072, 0.067, 0.085, 0.066, 0.318, 0.157, 0.187, 0.127, 0.087, 0.197, 0.001, 0.106, 0.305, 0.17, 0.046, 0.359, 0.008, 0.005, 0.004, 0.023, 0.011, 0.0001, 0.011, 0.0001, 0.0001, 0.0001, 7.907, 0.771, 0.299, 4.189, 5.699, 0.182, 1.123, 1.305, 7.031, 1.198, 2.907, 3.562, 2.965, 5.97, 3.852, 1.33, 0.003, 2.724, 3.29, 3.069, 2.779, 1.746, 0.01, 0.004, 0.024, 0.95, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.243, 0.042, 0.031, 0.026, 0.016, 0.009, 0.007, 0.007, 0.018, 0.003, 0.008, 0.014, 0.04, 0.228, 0.004, 0.014, 0.011, 0.008, 0.007, 0.006, 0.198, 0.004, 0.004, 0.004, 0.004, 0.01, 0.011, 0.006, 0.059, 0.006, 0.007, 0.007, 0.049, 0.512, 0.005, 0.004, 1.459, 0.005, 0.005, 0.012, 0.007, 0.009, 0.006, 0.076, 0.003, 0.005, 0.006, 0.008, 0.087, 0.02, 0.049, 0.021, 0.019, 0.048, 0.155, 0.011, 0.041, 0.019, 0.037, 0.102, 0.539, 0.049, 0.808, 0.016, 0.0001, 0.0001, 0.208, 2.197, 0.255, 1.283, 0.0001, 0.0001, 0.0001, 0.018, 0.007, 0.013, 0.002, 0.001, 0.025, 0.012, 0.469, 0.173, 0.003, 0.003, 0.001, 0.006, 0.006, 0.011, 0.026, 0.019, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.026, 0.012, 0.203, 0.002, 0.001, 0.003, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"vls\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.228, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.739, 0.003, 0.38, 0.0001, 0.0001, 0.005, 0.003, 0.744, 0.196, 0.195, 0.001, 0.001, 0.727, 0.325, 0.943, 0.009, 0.279, 0.491, 0.2, 0.128, 0.127, 0.144, 0.128, 0.137, 0.161, 0.217, 0.083, 0.01, 0.008, 0.003, 0.008, 0.002, 0.0001, 0.184, 0.236, 0.118, 0.332, 0.112, 0.115, 0.126, 0.103, 0.261, 0.107, 0.122, 0.141, 0.163, 0.108, 0.113, 0.118, 0.004, 0.127, 0.191, 0.088, 0.03, 0.223, 0.122, 0.006, 0.022, 0.104, 0.001, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 4.751, 0.962, 1.1, 3.988, 12.635, 0.533, 2.162, 1.118, 4.159, 0.386, 1.909, 2.864, 1.62, 7.645, 4.865, 1.022, 0.013, 4.762, 3.511, 4.63, 2.292, 1.812, 1.033, 0.041, 0.74, 0.83, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.13, 0.003, 0.003, 0.001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.001, 0.008, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.008, 0.015, 0.0001, 0.0001, 0.0001, 0.025, 0.093, 0.0001, 0.0001, 0.002, 0.002, 0.001, 0.001, 0.016, 0.003, 0.001, 0.001, 0.002, 0.001, 0.001, 0.004, 0.09, 0.034, 0.493, 0.075, 0.001, 0.002, 0.001, 0.006, 0.006, 0.003, 0.004, 0.004, 0.299, 0.002, 0.003, 0.001, 0.002, 0.001, 0.002, 0.002, 0.005, 0.002, 0.001, 0.002, 0.0001, 0.0001, 0.02, 1.045, 0.002, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.007, 0.004, 0.008, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.002, 0.13, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"vo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.865, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 14.101, 0.0001, 0.089, 0.0001, 0.177, 0.768, 0.0001, 0.013, 0.471, 0.471, 0.0001, 0.0001, 1.958, 0.301, 1.263, 0.002, 1.009, 1.484, 1.145, 0.885, 0.977, 0.988, 0.827, 0.571, 0.867, 0.731, 0.368, 0.112, 0.003, 0.0001, 0.003, 0.0001, 0.0001, 0.099, 0.202, 0.186, 0.179, 0.034, 0.122, 0.068, 0.069, 0.028, 0.029, 0.035, 0.486, 0.223, 0.193, 0.038, 0.198, 0.004, 0.074, 0.506, 0.089, 0.221, 0.126, 0.048, 0.001, 0.008, 0.039, 0.004, 0.001, 0.005, 0.0001, 0.0001, 0.0001, 5.558, 2.077, 0.284, 2.834, 4.622, 1.332, 0.379, 0.28, 4.679, 0.128, 1.147, 4.377, 2.51, 5.854, 4.077, 1.175, 0.015, 1.237, 3.788, 2.427, 1.276, 0.657, 0.06, 0.029, 0.621, 0.304, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.73, 0.001, 0.0001, 0.005, 0.073, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.0001, 0.033, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.623, 0.0001, 0.0001, 0.045, 0.0001, 0.038, 0.009, 0.001, 0.006, 0.006, 0.01, 2.184, 0.0001, 0.0001, 0.003, 0.022, 0.052, 0.002, 0.001, 0.0001, 0.004, 0.0001, 0.0001, 0.27, 0.001, 0.247, 0.003, 0.014, 0.006, 1.503, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 1.216, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.516, 5.121, 0.006, 0.01, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.73, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"wa\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.065, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 16.234, 0.018, 0.387, 0.0001, 0.0001, 0.001, 0.005, 1.403, 0.391, 0.397, 0.0001, 0.006, 1.323, 0.328, 1.748, 0.02, 0.212, 0.344, 0.172, 0.086, 0.07, 0.086, 0.076, 0.078, 0.098, 0.148, 0.393, 0.059, 0.003, 0.003, 0.006, 0.012, 0.0001, 0.126, 0.117, 0.176, 0.177, 0.152, 0.211, 0.071, 0.055, 0.154, 0.041, 0.016, 0.325, 0.219, 0.065, 0.097, 0.121, 0.003, 0.069, 0.125, 0.076, 0.016, 0.053, 0.079, 0.005, 0.007, 0.005, 0.103, 0.0001, 0.103, 0.0001, 0.0001, 0.0001, 4.343, 0.71, 2.121, 3.465, 9.326, 0.692, 0.491, 0.929, 5.047, 0.968, 0.844, 3.108, 1.647, 4.913, 4.614, 1.529, 0.028, 3.303, 5.504, 4.286, 1.947, 1.135, 0.682, 0.179, 1.059, 0.366, 0.0001, 0.075, 0.0001, 0.0001, 0.0001, 0.076, 0.002, 0.002, 0.001, 0.001, 0.022, 0.001, 0.008, 0.005, 0.003, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.002, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.065, 0.0001, 0.001, 0.004, 0.003, 0.0001, 0.001, 0.371, 0.002, 0.017, 0.001, 0.001, 0.706, 0.003, 0.089, 0.451, 0.662, 0.205, 0.03, 0.001, 0.001, 0.639, 0.002, 0.006, 0.002, 0.002, 0.001, 0.243, 0.004, 0.001, 0.001, 0.001, 0.001, 0.002, 0.257, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.478, 3.239, 0.002, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.006, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.08, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"war\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.118, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 12.933, 0.0001, 1.377, 0.0001, 0.0001, 0.0001, 0.003, 0.004, 0.008, 0.008, 0.0001, 0.0001, 0.432, 0.073, 1.214, 0.001, 0.079, 0.266, 0.062, 0.046, 0.041, 0.046, 0.05, 0.055, 0.111, 0.217, 0.037, 0.004, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 1.082, 0.154, 0.38, 0.175, 0.141, 0.098, 0.127, 0.173, 0.102, 0.057, 0.046, 0.208, 0.316, 0.091, 0.096, 0.293, 0.004, 0.105, 0.232, 0.146, 0.033, 0.038, 0.367, 0.012, 0.008, 0.019, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.129, 0.835, 2.123, 1.488, 5.092, 0.584, 3.71, 3.47, 8.491, 0.033, 1.376, 3.841, 1.504, 9.228, 3.14, 2.313, 0.025, 2.807, 5.239, 2.428, 2.957, 0.216, 0.413, 0.116, 1.506, 0.106, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.006, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.002, 0.004, 0.019, 0.0001, 0.001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.004, 0.003, 0.0001, 0.004, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.006, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.06, 0.002, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"wo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.906, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 18.371, 0.007, 0.083, 0.0001, 0.0001, 0.002, 0.0001, 0.048, 0.243, 0.244, 0.0001, 0.001, 1.526, 0.299, 0.6, 0.011, 0.077, 0.162, 0.075, 0.048, 0.048, 0.043, 0.038, 0.041, 0.05, 0.065, 0.149, 0.021, 0.001, 0.005, 0.001, 0.009, 0.0001, 0.248, 0.196, 0.082, 0.079, 0.037, 0.083, 0.06, 0.026, 0.08, 0.079, 0.082, 0.102, 0.179, 0.109, 0.049, 0.052, 0.005, 0.054, 0.208, 0.113, 0.015, 0.012, 0.059, 0.037, 0.106, 0.002, 0.002, 0.0001, 0.002, 0.0001, 0.001, 0.0001, 10.502, 2.142, 1.408, 2.296, 5.004, 0.815, 2.647, 0.171, 6.017, 1.265, 2.73, 3.516, 3.296, 5.064, 5.377, 0.616, 0.08, 2.151, 1.518, 2.39, 4.356, 0.021, 1.494, 1.066, 2.37, 0.019, 0.002, 0.0001, 0.004, 0.0001, 0.0001, 0.102, 0.006, 0.003, 0.003, 0.01, 0.005, 0.004, 0.003, 0.005, 0.001, 0.005, 0.02, 0.001, 0.001, 0.008, 0.002, 0.005, 0.039, 0.003, 0.026, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.041, 0.001, 0.0001, 0.016, 0.015, 0.0001, 0.0001, 0.641, 0.001, 0.002, 0.004, 0.002, 0.001, 0.001, 0.012, 0.011, 0.402, 0.004, 0.775, 0.001, 0.002, 0.001, 0.002, 0.004, 0.912, 0.013, 0.056, 0.002, 0.002, 0.001, 0.002, 0.003, 0.003, 0.002, 0.013, 0.001, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.028, 2.826, 0.002, 0.019, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.002, 0.0001, 0.004, 0.002, 0.016, 0.018, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.007, 0.033, 0.053, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.096, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"wuu\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.208, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.344, 0.001, 0.064, 0.0001, 0.0001, 0.012, 0.001, 0.005, 0.037, 0.032, 0.001, 0.002, 0.029, 0.05, 0.042, 0.019, 0.267, 0.364, 0.21, 0.108, 0.106, 0.12, 0.107, 0.1, 0.123, 0.181, 0.013, 0.001, 0.013, 0.002, 0.013, 0.001, 0.0001, 0.027, 0.021, 0.029, 0.015, 0.013, 0.01, 0.014, 0.013, 0.018, 0.007, 0.011, 0.017, 0.022, 0.016, 0.01, 0.023, 0.002, 0.017, 0.03, 0.019, 0.009, 0.006, 0.008, 0.002, 0.003, 0.002, 0.03, 0.0001, 0.03, 0.0001, 0.003, 0.0001, 0.184, 0.024, 0.041, 0.051, 0.161, 0.019, 0.037, 0.056, 0.143, 0.005, 0.024, 0.082, 0.047, 0.138, 0.118, 0.028, 0.006, 0.111, 0.081, 0.088, 0.07, 0.016, 0.015, 0.01, 0.024, 0.008, 0.001, 0.002, 0.001, 0.001, 0.0001, 2.843, 1.238, 1.324, 0.655, 0.418, 1.022, 0.586, 0.937, 1.267, 1.305, 0.731, 1.421, 2.335, 0.988, 0.859, 1.016, 1.143, 0.568, 0.436, 0.439, 0.836, 0.673, 0.873, 1.003, 0.932, 0.655, 0.691, 1.033, 1.591, 0.82, 0.469, 0.875, 0.536, 0.577, 0.431, 0.453, 0.911, 0.859, 0.578, 0.722, 0.777, 0.496, 1.371, 0.496, 0.553, 1.219, 0.891, 1.125, 1.185, 0.888, 0.563, 0.66, 0.876, 0.472, 0.61, 0.726, 3.021, 1.231, 1.855, 1.189, 2.708, 1.052, 0.869, 1.001, 0.0001, 0.0001, 0.059, 0.019, 0.003, 0.003, 0.001, 0.0001, 0.0001, 0.005, 0.002, 0.002, 0.002, 0.0001, 0.011, 0.004, 0.02, 0.007, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.003, 0.011, 0.009, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.0001, 0.068, 0.005, 0.208, 1.565, 4.388, 9.361, 5.679, 3.099, 2.882, 2.131, 0.002, 0.004, 0.008, 0.002, 0.0001, 1.953, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"xal\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 2.016, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 7.801, 0.002, 0.076, 0.0001, 0.0001, 0.005, 0.0001, 0.002, 0.134, 0.134, 0.001, 0.003, 0.529, 0.574, 0.918, 0.006, 0.214, 0.423, 0.268, 0.17, 0.177, 0.128, 0.129, 0.128, 0.121, 0.185, 0.028, 0.007, 0.006, 0.001, 0.006, 0.006, 0.0001, 0.005, 0.004, 0.004, 0.001, 0.002, 0.002, 0.002, 0.002, 0.006, 0.001, 0.002, 0.002, 0.003, 0.001, 0.001, 0.002, 0.0001, 0.002, 0.005, 0.003, 0.001, 0.002, 0.003, 0.004, 0.001, 0.001, 0.005, 0.0001, 0.006, 0.0001, 0.005, 0.0001, 0.064, 0.016, 0.035, 0.026, 0.079, 0.017, 0.024, 0.144, 0.059, 0.003, 0.012, 0.04, 0.028, 0.059, 0.05, 0.015, 0.002, 0.048, 0.045, 0.048, 0.035, 0.008, 0.009, 0.006, 0.012, 0.006, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 2.512, 1.678, 1.585, 1.178, 0.036, 0.859, 0.336, 0.487, 0.211, 0.008, 0.012, 0.272, 0.319, 0.492, 0.054, 0.135, 0.09, 0.152, 0.041, 0.073, 0.19, 0.017, 0.022, 0.69, 0.054, 1.446, 0.115, 0.043, 0.168, 0.153, 0.159, 0.053, 0.055, 0.105, 0.151, 0.242, 0.028, 0.118, 0.031, 0.02, 0.093, 0.554, 0.004, 0.02, 0.002, 0.072, 0.031, 0.849, 3.75, 1.252, 0.825, 1.816, 2.139, 1.256, 0.115, 0.387, 2.666, 0.446, 0.987, 3.364, 1.079, 4.101, 2.147, 0.166, 0.0001, 0.0001, 0.041, 0.006, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.038, 0.0001, 0.0001, 0.004, 0.0001, 0.007, 0.004, 27.749, 10.017, 2.264, 1.98, 0.0001, 0.003, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.024, 0.035, 0.127, 0.0001, 0.0001, 0.004, 0.002, 0.001, 0.001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"xh\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.827, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 11.133, 0.013, 0.259, 0.004, 0.001, 0.009, 0.002, 0.046, 0.125, 0.123, 0.001, 0.005, 0.846, 0.831, 0.912, 0.026, 0.163, 0.218, 0.112, 0.059, 0.052, 0.058, 0.048, 0.051, 0.067, 0.118, 0.048, 0.023, 0.018, 0.006, 0.018, 0.006, 0.0001, 0.218, 0.122, 0.114, 0.05, 0.111, 0.054, 0.063, 0.043, 0.32, 0.057, 0.15, 0.086, 0.186, 0.216, 0.074, 0.101, 0.011, 0.057, 0.136, 0.094, 0.198, 0.022, 0.071, 0.041, 0.042, 0.046, 0.076, 0.001, 0.076, 0.0001, 0.013, 0.0001, 10.703, 2.404, 0.805, 1.231, 8.068, 0.529, 2.029, 3.142, 7.484, 0.244, 4.325, 4.529, 2.518, 6.863, 5.226, 0.943, 0.434, 1.064, 2.867, 2.574, 4.687, 0.307, 2.513, 0.353, 2.341, 2.213, 0.002, 0.028, 0.002, 0.0001, 0.0001, 0.043, 0.003, 0.001, 0.001, 0.002, 0.0001, 0.004, 0.012, 0.003, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.002, 0.001, 0.004, 0.01, 0.003, 0.0001, 0.0001, 0.001, 0.003, 0.018, 0.0001, 0.0001, 0.005, 0.005, 0.0001, 0.001, 0.1, 0.005, 0.001, 0.004, 0.001, 0.0001, 0.0001, 0.003, 0.001, 0.007, 0.001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.001, 0.004, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.001, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.101, 0.03, 0.014, 0.003, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.004, 0.003, 0.004, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.049, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"xmf\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.601, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 4.701, 0.001, 0.058, 0.0001, 0.0001, 0.01, 0.0001, 0.002, 0.121, 0.121, 0.0001, 0.001, 0.458, 0.166, 0.464, 0.005, 0.164, 0.192, 0.121, 0.06, 0.056, 0.064, 0.055, 0.055, 0.065, 0.102, 0.028, 0.018, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.008, 0.006, 0.008, 0.007, 0.003, 0.004, 0.003, 0.003, 0.027, 0.001, 0.002, 0.006, 0.007, 0.003, 0.003, 0.005, 0.0001, 0.004, 0.007, 0.009, 0.002, 0.008, 0.003, 0.01, 0.001, 0.0001, 0.006, 0.0001, 0.006, 0.0001, 0.001, 0.0001, 0.041, 0.006, 0.016, 0.012, 0.042, 0.004, 0.007, 0.012, 0.032, 0.001, 0.006, 0.021, 0.011, 0.029, 0.03, 0.007, 0.001, 0.029, 0.023, 0.023, 0.015, 0.005, 0.003, 0.002, 0.006, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.172, 0.003, 0.002, 30.333, 0.002, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 4.083, 0.506, 0.555, 0.957, 2.283, 0.421, 0.156, 0.803, 3.59, 0.653, 1.19, 1.236, 1.788, 2.02, 0.312, 0.098, 2.097, 1.217, 0.638, 1.469, 0.698, 0.389, 0.172, 0.093, 1.339, 0.152, 0.183, 0.083, 0.259, 0.102, 0.41, 0.184, 0.054, 0.009, 0.013, 0.002, 0.001, 0.003, 0.001, 0.323, 0.062, 0.002, 0.002, 0.002, 0.002, 0.003, 0.004, 0.002, 0.0001, 0.0001, 0.043, 0.004, 0.001, 0.001, 0.007, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.001, 0.0001, 0.011, 0.002, 0.023, 0.008, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.001, 0.004, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.009, 30.332, 0.17, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"yi\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.709, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.298, 0.002, 0.186, 0.0001, 0.001, 0.004, 0.006, 0.121, 0.075, 0.076, 0.0001, 0.0001, 0.466, 0.059, 0.46, 0.006, 0.099, 0.114, 0.062, 0.037, 0.035, 0.037, 0.03, 0.03, 0.038, 0.064, 0.034, 0.015, 0.001, 0.001, 0.001, 0.002, 0.0001, 0.003, 0.003, 0.004, 0.003, 0.002, 0.002, 0.002, 0.002, 0.002, 0.001, 0.001, 0.002, 0.003, 0.002, 0.002, 0.002, 0.0001, 0.002, 0.004, 0.003, 0.001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.003, 0.0001, 0.003, 0.0001, 0.001, 0.0001, 0.02, 0.003, 0.006, 0.007, 0.022, 0.003, 0.004, 0.006, 0.017, 0.0001, 0.003, 0.01, 0.006, 0.015, 0.021, 0.004, 0.006, 0.015, 0.011, 0.018, 0.013, 0.002, 0.003, 0.001, 0.003, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.013, 0.004, 0.003, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 0.001, 0.0001, 0.0001, 0.001, 5.002, 1.068, 1.228, 1.611, 0.814, 3.904, 1.071, 0.178, 2.364, 5.673, 0.275, 0.347, 1.459, 0.389, 1.018, 2.472, 1.73, 1.057, 4.356, 0.098, 1.356, 0.06, 0.547, 0.832, 3.227, 0.975, 0.239, 0.001, 0.001, 0.001, 0.0001, 0.001, 0.02, 0.006, 0.026, 0.005, 0.016, 0.005, 0.002, 0.163, 0.104, 0.003, 0.002, 0.002, 0.041, 0.002, 0.022, 0.034, 0.0001, 0.0001, 0.015, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.003, 0.001, 0.029, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.372, 43.367, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.0001, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"yo\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 3.162, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 13.013, 0.002, 0.102, 0.0001, 0.001, 0.004, 0.001, 0.025, 0.251, 0.249, 0.0001, 0.001, 0.499, 0.259, 1.047, 0.013, 0.386, 0.744, 0.471, 0.336, 0.305, 0.301, 0.323, 0.299, 0.314, 0.417, 0.09, 0.08, 0.008, 0.009, 0.008, 0.006, 0.0001, 0.462, 0.171, 0.128, 0.102, 0.134, 0.101, 0.156, 0.11, 0.251, 0.116, 0.194, 0.108, 0.188, 0.187, 0.263, 0.133, 0.007, 0.102, 0.27, 0.148, 0.037, 0.042, 0.07, 0.006, 0.044, 0.016, 0.007, 0.0001, 0.008, 0.0001, 0.001, 0.001, 4.068, 1.959, 0.507, 1.515, 3.958, 0.547, 1.326, 0.747, 4.508, 1.331, 1.562, 2.445, 1.011, 4.469, 3.265, 1.008, 0.02, 3.063, 1.958, 2.732, 1.408, 0.219, 0.852, 0.039, 0.732, 0.092, 0.0001, 0.013, 0.0001, 0.0001, 0.0001, 0.678, 1.441, 0.002, 0.002, 0.064, 0.002, 0.001, 0.002, 0.025, 0.003, 0.001, 0.001, 0.172, 1.046, 0.0001, 0.0001, 0.001, 0.001, 0.032, 0.052, 0.002, 0.0001, 0.0001, 0.0001, 0.018, 0.066, 0.002, 0.001, 0.007, 0.006, 0.0001, 0.001, 1.085, 1.316, 0.01, 0.17, 0.004, 0.001, 0.001, 0.003, 0.307, 0.812, 0.001, 0.003, 1.559, 1.199, 0.001, 0.002, 0.003, 0.004, 0.287, 0.374, 0.003, 0.002, 0.006, 0.001, 0.038, 1.787, 1.887, 1.09, 0.005, 0.003, 0.003, 0.001, 0.0001, 0.0001, 0.021, 7.862, 0.009, 0.075, 0.0001, 0.008, 0.0001, 0.001, 0.001, 0.001, 1.898, 0.0001, 0.005, 0.002, 0.012, 0.004, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.005, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.004, 2.718, 0.12, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"za\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.779, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 9.452, 0.003, 0.07, 0.0001, 0.001, 0.016, 0.002, 0.054, 0.186, 0.179, 0.001, 0.0001, 0.82, 0.089, 0.74, 0.012, 0.236, 0.344, 0.171, 0.097, 0.104, 0.109, 0.078, 0.094, 0.113, 0.172, 0.091, 0.029, 0.001, 0.001, 0.002, 0.003, 0.0001, 0.117, 0.253, 0.245, 0.236, 0.047, 0.096, 0.232, 0.128, 0.101, 0.031, 0.049, 0.109, 0.142, 0.114, 0.031, 0.114, 0.005, 0.051, 0.316, 0.07, 0.028, 0.136, 0.041, 0.012, 0.157, 0.02, 0.003, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 4.452, 1.127, 1.557, 2.16, 5.66, 0.54, 3.525, 2.807, 4.357, 1.245, 0.519, 1.215, 1.057, 5.149, 2.46, 0.332, 0.75, 1.554, 1.842, 1.639, 2.859, 0.55, 1.169, 0.375, 1.034, 2.115, 0.002, 0.0001, 0.002, 0.001, 0.0001, 1.059, 0.53, 0.446, 0.215, 0.472, 0.297, 0.257, 0.268, 0.372, 0.375, 0.213, 0.338, 0.751, 0.361, 0.284, 0.332, 0.27, 0.144, 0.117, 0.272, 0.266, 0.278, 0.305, 0.293, 0.26, 0.335, 0.49, 0.247, 0.537, 0.19, 0.142, 0.27, 0.209, 0.19, 0.122, 0.13, 0.301, 0.259, 0.231, 0.235, 0.283, 0.134, 0.154, 0.156, 0.162, 0.375, 0.302, 0.377, 0.293, 0.227, 0.124, 0.201, 0.231, 0.092, 0.229, 0.184, 0.748, 0.296, 0.646, 0.455, 0.756, 0.262, 0.268, 0.277, 0.0001, 0.0001, 0.072, 0.167, 0.018, 0.011, 0.0001, 0.002, 0.0001, 0.002, 0.001, 0.001, 0.001, 0.0001, 0.006, 0.002, 0.014, 0.004, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.012, 0.01, 0.002, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.022, 0.008, 0.114, 0.555, 1.309, 2.559, 1.698, 1.364, 0.916, 0.712, 0.001, 0.001, 0.001, 0.0001, 0.0001, 0.518, 0.001, 0.0001, 0.0001, 0.003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"zea\": [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.532, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 15.056, 0.008, 0.162, 0.0001, 0.0001, 0.007, 0.001, 1.415, 0.162, 0.162, 0.0001, 0.0001, 1.0, 0.532, 1.127, 0.004, 0.395, 0.563, 0.389, 0.394, 0.405, 0.319, 0.329, 0.24, 0.265, 0.382, 0.062, 0.076, 0.002, 0.003, 0.001, 0.008, 0.0001, 0.28, 0.228, 0.122, 0.346, 0.208, 0.185, 0.11, 0.117, 0.317, 0.071, 0.084, 0.154, 0.152, 0.245, 0.188, 0.145, 0.004, 0.099, 0.307, 0.104, 0.026, 0.15, 0.089, 0.002, 0.005, 0.114, 0.003, 0.0001, 0.003, 0.0001, 0.0001, 0.001, 4.665, 0.916, 0.779, 3.731, 13.123, 0.39, 1.695, 1.202, 4.867, 0.455, 1.861, 2.604, 1.621, 7.033, 3.935, 1.063, 0.011, 4.601, 3.105, 3.908, 1.82, 1.832, 0.958, 0.04, 0.135, 0.573, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.482, 0.005, 0.003, 0.002, 0.003, 0.002, 0.001, 0.001, 0.002, 0.005, 0.002, 0.001, 0.001, 0.002, 0.0001, 0.001, 0.001, 0.001, 0.001, 0.005, 0.003, 0.001, 0.0001, 0.001, 0.021, 0.432, 0.001, 0.001, 0.01, 0.009, 0.003, 0.005, 0.009, 0.008, 0.021, 0.001, 0.002, 0.001, 0.003, 0.005, 0.09, 0.052, 0.453, 0.056, 0.009, 0.006, 0.003, 0.006, 0.115, 0.002, 0.14, 0.027, 0.252, 0.001, 0.064, 0.0001, 0.002, 0.002, 0.002, 0.006, 0.004, 0.002, 0.002, 0.001, 0.0001, 0.0001, 0.239, 1.084, 0.009, 0.01, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.005, 0.002, 0.008, 0.003, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.002, 0.007, 0.005, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.003, 0.481, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n    \"zu\":  [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 1.261, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 10.94, 0.004, 0.267, 0.001, 0.002, 0.016, 0.003, 0.041, 0.181, 0.181, 0.001, 0.001, 0.907, 0.49, 0.797, 0.099, 0.343, 0.379, 0.279, 0.134, 0.118, 0.116, 0.102, 0.097, 0.11, 0.223, 0.065, 0.035, 0.134, 0.003, 0.135, 0.005, 0.0001, 0.296, 0.147, 0.142, 0.093, 0.11, 0.08, 0.077, 0.065, 0.3, 0.114, 0.141, 0.151, 0.361, 0.387, 0.067, 0.128, 0.012, 0.082, 0.239, 0.152, 0.188, 0.039, 0.182, 0.012, 0.045, 0.07, 0.138, 0.0001, 0.139, 0.0001, 0.001, 0.0001, 10.325, 2.215, 0.829, 1.627, 7.521, 0.687, 2.042, 3.525, 7.719, 0.199, 3.874, 4.421, 2.406, 6.494, 4.881, 0.951, 0.342, 1.361, 3.011, 2.552, 4.691, 0.394, 2.227, 0.134, 1.688, 1.779, 0.0001, 0.002, 0.0001, 0.0001, 0.0001, 0.08, 0.007, 0.001, 0.001, 0.002, 0.0001, 0.014, 0.002, 0.002, 0.001, 0.0001, 0.001, 0.003, 0.005, 0.001, 0.002, 0.002, 0.014, 0.001, 0.01, 0.003, 0.0001, 0.001, 0.001, 0.002, 0.06, 0.0001, 0.001, 0.003, 0.003, 0.001, 0.0001, 0.084, 0.004, 0.0001, 0.001, 0.001, 0.0001, 0.002, 0.004, 0.002, 0.005, 0.003, 0.001, 0.001, 0.002, 0.001, 0.0001, 0.004, 0.003, 0.003, 0.005, 0.002, 0.002, 0.001, 0.006, 0.005, 0.002, 0.003, 0.005, 0.002, 0.003, 0.003, 0.0001, 0.0001, 0.0001, 0.089, 0.024, 0.004, 0.007, 0.0001, 0.0001, 0.0001, 0.002, 0.001, 0.001, 0.0001, 0.0001, 0.002, 0.001, 0.029, 0.011, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.005, 0.002, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.002, 0.002, 0.091, 0.001, 0.0001, 0.001, 0.002, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001],\n});\n\n/**\n * Determines whether two ArrayBuffers contain the same values.\n *\n * @param {ArrayBuffer} a\n * @param {ArrayBuffer} b\n * @returns {boolean}\n */\nfunction _buffersEqual(a, b) {\n    if (a === b) {\n        return true;\n    }\n\n    if (a.byteLength !== b.byteLength) {\n        return false;\n    }\n\n    const ai = new Uint8Array(a),\n        bi = new Uint8Array(b);\n\n    let i = a.byteLength;\n    while (i--) {\n        if (ai[i] !== bi[i]) {\n            return false;\n        }\n    }\n\n    return true;\n}\n\nexport default Magic;\n"
  },
  {
    "path": "src/core/lib/Modhex.mjs",
    "content": "/**\n * @author linuxgemini [ilteris@asenkron.com.tr]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { fromHex, toHex } from \"./Hex.mjs\";\n\n/**\n * Modhex alphabet.\n */\nconst MODHEX_ALPHABET = \"cbdefghijklnrtuv\";\n\n\n/**\n * Modhex alphabet map.\n */\nconst MODHEX_ALPHABET_MAP = MODHEX_ALPHABET.split(\"\");\n\n\n/**\n * Hex alphabet to substitute Modhex.\n */\nconst HEX_ALPHABET = \"0123456789abcdef\";\n\n\n/**\n * Hex alphabet map to substitute Modhex.\n */\nconst HEX_ALPHABET_MAP = HEX_ALPHABET.split(\"\");\n\n\n/**\n * Convert a byte array into a modhex string.\n *\n * @param {byteArray|Uint8Array|ArrayBuffer} data\n * @param {string} [delim=\" \"]\n * @param {number} [padding=2]\n * @returns {string}\n *\n * @example\n * // returns \"cl bf bu\"\n * toModhex([10,20,30]);\n *\n * // returns \"cl:bf:bu\"\n * toModhex([10,20,30], \":\");\n */\nexport function toModhex(data, delim=\" \", padding=2, extraDelim=\"\", lineSize=0) {\n    if (!data) return \"\";\n    if (data instanceof ArrayBuffer) data = new Uint8Array(data);\n\n    const regularHexString = toHex(data, \"\", padding, \"\", 0);\n\n    let modhexString = \"\";\n    for (const letter of regularHexString.split(\"\")) {\n        modhexString += MODHEX_ALPHABET_MAP[HEX_ALPHABET_MAP.indexOf(letter)];\n    }\n\n    let output = \"\";\n    const groupingRegexp = new RegExp(`.{1,${padding}}`, \"g\");\n    const groupedModhex = modhexString.match(groupingRegexp);\n\n    for (let i = 0; i < groupedModhex.length; i++) {\n        const group = groupedModhex[i];\n        output += group + delim;\n\n        if (extraDelim) {\n            output += extraDelim;\n        }\n        // Add LF after each lineSize amount of bytes but not at the end\n        if ((i !== groupedModhex.length - 1) && ((i + 1) % lineSize === 0)) {\n            output += \"\\n\";\n        }\n    }\n\n    // Remove the extraDelim at the end (if there is one)\n    // and remove the delim at the end, but if it's prepended there's nothing to remove\n    const rTruncLen = extraDelim.length + delim.length;\n    if (rTruncLen) {\n        // If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing\n        return output.slice(0, -rTruncLen);\n    } else {\n        return output;\n    }\n}\n\n\n/**\n * Convert a byte array into a modhex string as efficiently as possible with no options.\n *\n * @param {byteArray|Uint8Array|ArrayBuffer} data\n * @returns {string}\n *\n * @example\n * // returns \"clbfbu\"\n * toModhexFast([10,20,30]);\n */\nexport function toModhexFast(data) {\n    if (!data) return \"\";\n    if (data instanceof ArrayBuffer) data = new Uint8Array(data);\n\n    const output = [];\n\n    for (let i = 0; i < data.length; i++) {\n        output.push(MODHEX_ALPHABET_MAP[(data[i] >> 4) & 0xf]);\n        output.push(MODHEX_ALPHABET_MAP[data[i] & 0xf]);\n    }\n    return output.join(\"\");\n}\n\n\n/**\n * Convert a modhex string into a byte array.\n *\n * @param {string} data\n * @param {string} [delim]\n * @param {number} [byteLen=2]\n * @returns {byteArray}\n *\n * @example\n * // returns [10,20,30]\n * fromModhex(\"cl bf bu\");\n *\n * // returns [10,20,30]\n * fromModhex(\"cl:bf:bu\", \"Colon\");\n */\nexport function fromModhex(data, delim=\"Auto\", byteLen=2) {\n    if (byteLen < 1 || Math.round(byteLen) !== byteLen)\n        throw new OperationError(\"Byte length must be a positive integer\");\n\n    // The `.replace(/\\s/g, \"\")` an interesting workaround: Hex \"multiline\" tests aren't actually\n    // multiline. Tests for Modhex fixes that, thus exposing the issue.\n    data = data.toLowerCase().replace(/\\s/g, \"\");\n\n    if (delim !== \"None\") {\n        const delimRegex = delim === \"Auto\" ? /[^cbdefghijklnrtuv]/gi : Utils.regexRep(delim);\n        data = data.split(delimRegex);\n    } else {\n        data = [data];\n    }\n\n    let regularHexString = \"\";\n    for (let i = 0; i < data.length; i++) {\n        for (const letter of data[i].split(\"\")) {\n            regularHexString += HEX_ALPHABET_MAP[MODHEX_ALPHABET_MAP.indexOf(letter)];\n        }\n    }\n\n    const output = fromHex(regularHexString, \"None\", byteLen);\n    return output;\n}\n\n\n/**\n * To Modhex delimiters.\n */\nexport const TO_MODHEX_DELIM_OPTIONS = [\"Space\", \"Percent\", \"Comma\", \"Semi-colon\", \"Colon\", \"Line feed\", \"CRLF\", \"None\"];\n\n\n/**\n * From Modhex delimiters.\n */\nexport const FROM_MODHEX_DELIM_OPTIONS = [\"Auto\"].concat(TO_MODHEX_DELIM_OPTIONS);\n"
  },
  {
    "path": "src/core/lib/PGP.mjs",
    "content": "/**\n * PGP functions.\n *\n * @author tlwr [toby@toby.codes]\n * @author Matt C [matt@artemisbot.uk]\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n *\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport kbpgp from \"kbpgp\";\nimport * as es6promisify from \"es6-promisify\";\nconst promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;\n\n/**\n * Progress callback\n */\nexport const ASP = kbpgp.ASP({\n    \"progress_hook\": info => {\n        let msg = \"\";\n\n        switch (info.what) {\n            case \"guess\":\n                msg = \"Guessing a prime\";\n                break;\n            case \"fermat\":\n                msg = \"Factoring prime using Fermat's factorization method\";\n                break;\n            case \"mr\":\n                msg = \"Performing Miller-Rabin primality test\";\n                break;\n            case \"passed_mr\":\n                msg = \"Passed Miller-Rabin primality test\";\n                break;\n            case \"failed_mr\":\n                msg = \"Failed Miller-Rabin primality test\";\n                break;\n            case \"found\":\n                msg = \"Prime found\";\n                break;\n            default:\n                msg = `Stage: ${info.what}`;\n        }\n\n        if (isWorkerEnvironment())\n            self.sendStatusMessage(msg);\n    }\n});\n\n/**\n * Get size of subkey\n *\n * @param {number} keySize\n * @returns {number}\n */\nexport function getSubkeySize(keySize) {\n    return {\n        1024: 1024,\n        2048: 1024,\n        4096: 2048,\n        256:   256,\n        384:   256,\n    }[keySize];\n}\n\n/**\n* Import private key and unlock if necessary\n*\n* @param {string} privateKey\n* @param {string} [passphrase]\n* @returns {Object}\n*/\nexport async function importPrivateKey(privateKey, passphrase) {\n    try {\n        const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({\n            armored: privateKey,\n            opts: {\n                \"no_check_keys\": true\n            }\n        });\n        if (key.is_pgp_locked()) {\n            if (passphrase) {\n                await promisify(key.unlock_pgp.bind(key))({\n                    passphrase\n                });\n            } else {\n                throw new OperationError(\"Did not provide passphrase with locked private key.\");\n            }\n        }\n        return key;\n    } catch (err) {\n        throw new OperationError(`Could not import private key: ${err}`);\n    }\n}\n\n/**\n * Import public key\n *\n * @param {string} publicKey\n * @returns {Object}\n */\nexport async function importPublicKey (publicKey) {\n    try {\n        const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({\n            armored: publicKey,\n            opts: {\n                \"no_check_keys\": true\n            }\n        });\n        return key;\n    } catch (err) {\n        throw new OperationError(`Could not import public key: ${err}`);\n    }\n}\n"
  },
  {
    "path": "src/core/lib/Protobuf.mjs",
    "content": "import Utils from \"../Utils.mjs\";\nimport protobuf from \"protobufjs\";\n\n/**\n * Protobuf lib. Contains functions to decode protobuf serialised\n * data without a schema or .proto file.\n *\n * Provides utility functions to encode and decode variable length\n * integers (varint).\n *\n * @author GCHQ Contributor [3]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nclass Protobuf {\n\n    /**\n     * Protobuf constructor\n     *\n     * @param {byteArray|Uint8Array} data\n     */\n    constructor(data) {\n        // Check we have a byteArray or Uint8Array\n        if (data instanceof Array || data instanceof Uint8Array) {\n            this.data = data;\n        } else {\n            throw new Error(\"Protobuf input must be a byteArray or Uint8Array\");\n        }\n\n        // Set up masks\n        this.TYPE = 0x07;\n        this.NUMBER = 0x78;\n        this.MSB = 0x80;\n        this.VALUE = 0x7f;\n\n        // Declare offset, length, and field type object\n        this.offset = 0;\n        this.LENGTH = data.length;\n        this.fieldTypes = {};\n    }\n\n    // Public Functions\n\n    /**\n     * Encode a varint from a number\n     *\n     * @param {number} number\n     * @returns {byteArray}\n     */\n    static varIntEncode(number) {\n        const MSB = 0x80,\n            VALUE = 0x7f,\n            MSBALL = ~VALUE,\n            INT = Math.pow(2, 31);\n        const out = [];\n        let offset = 0;\n\n        while (number >= INT) {\n            out[offset++] = (number & 0xff) | MSB;\n            number /= 128;\n        }\n        while (number & MSBALL) {\n            out[offset++] = (number & 0xff) | MSB;\n            number >>>= 7;\n        }\n        out[offset] = number | 0;\n        return out;\n    }\n\n    /**\n     * Decode a varint from the byteArray\n     *\n     * @param {byteArray} input\n     * @returns {number}\n     */\n    static varIntDecode(input) {\n        const pb = new Protobuf(input);\n        return pb._varInt();\n    }\n\n    /**\n     * Encode input JSON according to the given schema\n     *\n     * @param {Object} input\n     * @param {Object []} args\n     * @returns {Object}\n     */\n    static encode(input, args) {\n        this.updateProtoRoot(args[0]);\n        if (!this.mainMessageName) {\n            throw new Error(\"Schema Error: Schema not defined\");\n        }\n        const message = this.parsedProto.root.nested[this.mainMessageName];\n\n        // Convert input into instance of message, and verify instance\n        input = message.fromObject(input);\n        const error = message.verify(input);\n        if (error) {\n            throw new Error(\"Input Error: \" + error);\n        }\n        // Encode input\n        const output = message.encode(input).finish();\n        return new Uint8Array(output).buffer;\n    }\n\n    /**\n     * Parse Protobuf data\n     *\n     * @param {byteArray} input\n     * @returns {Object}\n     */\n    static decode(input, args) {\n        this.updateProtoRoot(args[0]);\n        this.showUnknownFields = args[1];\n        this.showTypes = args[2];\n        return this.mergeDecodes(input);\n    }\n\n    /**\n     * Update the parsedProto, throw parsing errors\n     *\n     * @param {string} protoText\n     */\n    static updateProtoRoot(protoText) {\n        try {\n            this.parsedProto = protobuf.parse(protoText);\n            if (this.parsedProto.package) {\n                this.parsedProto.root = this.parsedProto.root.nested[this.parsedProto.package];\n            }\n            this.updateMainMessageName();\n        } catch (error) {\n            throw new Error(\"Schema \" + error);\n        }\n    }\n\n    /**\n     * Set mainMessageName to the first instance of a message defined in the schema that is not a submessage\n     *\n     */\n    static updateMainMessageName() {\n        const messageNames = [];\n        const fieldTypes = [];\n        this.parsedProto.root.nestedArray.forEach(block => {\n            if (block instanceof protobuf.Type) {\n                messageNames.push(block.name);\n                this.parsedProto.root.nested[block.name].fieldsArray.forEach(field => {\n                    fieldTypes.push(field.type);\n                });\n            }\n        });\n\n        if (messageNames.length === 0) {\n            this.mainMessageName = null;\n        } else {\n            // for (const name of messageNames) {\n            //     if (!fieldTypes.includes(name)) {\n            //         this.mainMessageName = name;\n            //         break;\n            //     }\n            // }\n            this.mainMessageName = messageNames[0];\n        }\n    }\n\n    /**\n     * Decode input using Protobufjs package and raw methods, compare, and merge results\n     *\n     * @param {byteArray} input\n     * @returns {Object}\n     */\n    static mergeDecodes(input) {\n        const pb = new Protobuf(input);\n        let rawDecode = pb._parse();\n        let message;\n\n        if (this.showTypes) {\n            rawDecode = this.showRawTypes(rawDecode, pb.fieldTypes);\n            this.parsedProto.root = this.appendTypesToFieldNames(this.parsedProto.root);\n        }\n\n        try {\n            message = this.parsedProto.root.nested[this.mainMessageName];\n            const packageDecode = message.toObject(message.decode(input), {\n                bytes: String,\n                longs: Number,\n                enums: String,\n                defaults: true\n            });\n            const output = {};\n\n            if (this.showUnknownFields) {\n                output[message.name] = packageDecode;\n                output[\"Unknown Fields\"] = this.compareFields(rawDecode, message);\n                return output;\n            } else {\n                return packageDecode;\n            }\n\n        } catch (error) {\n            if (message) {\n                throw new Error(\"Input \" + error);\n            } else {\n                return rawDecode;\n            }\n        }\n    }\n\n    /**\n     * Replace fieldnames with fieldname and type\n     *\n     * @param {Object} schemaRoot\n     * @returns {Object}\n     */\n    static appendTypesToFieldNames(schemaRoot) {\n        for (const block of schemaRoot.nestedArray) {\n            if (block instanceof protobuf.Type) {\n                for (const [fieldName, fieldData] of Object.entries(block.fields)) {\n                    schemaRoot.nested[block.name].remove(block.fields[fieldName]);\n                    schemaRoot.nested[block.name].add(new protobuf.Field(`${fieldName} (${fieldData.type})`, fieldData.id, fieldData.type, fieldData.rule));\n                }\n            }\n        }\n        return schemaRoot;\n    }\n\n    /**\n     * Add field type to field name for fields in the raw decoded output\n     *\n     * @param {Object} rawDecode\n     * @param {Object} fieldTypes\n     * @returns {Object}\n     */\n    static showRawTypes(rawDecode, fieldTypes) {\n        for (const [fieldNum, value] of Object.entries(rawDecode)) {\n            const fieldType = fieldTypes[fieldNum];\n            let outputFieldValue;\n            let outputFieldType;\n\n            // Submessages\n            if (isNaN(fieldType)) {\n                outputFieldType = 2;\n\n                // Repeated submessages\n                if (Array.isArray(value)) {\n                    const fieldInstances = [];\n                    for (const instance of Object.keys(value)) {\n                        if (typeof(value[instance]) !== \"string\") {\n                            fieldInstances.push(this.showRawTypes(value[instance], fieldType));\n                        } else {\n                            fieldInstances.push(value[instance]);\n                        }\n                    }\n                    outputFieldValue = fieldInstances;\n\n                // Single submessage\n                } else {\n                    outputFieldValue = this.showRawTypes(value, fieldType);\n                }\n\n            // Non-submessage field\n            } else {\n                outputFieldType = fieldType;\n                outputFieldValue = value;\n            }\n\n            // Substitute fieldNum with field number and type\n            rawDecode[`field #${fieldNum}: ${this.getTypeInfo(outputFieldType)}`] = outputFieldValue;\n            delete rawDecode[fieldNum];\n        }\n        return rawDecode;\n    }\n\n    /**\n     * Compare raw decode to package decode and return discrepancies\n     *\n     * @param rawDecodedMessage\n     * @param schemaMessage\n     * @returns {Object}\n     */\n    static compareFields(rawDecodedMessage, schemaMessage) {\n        // Define message data using raw decode output and schema\n        const schemaFieldProperties = {};\n        const schemaFieldNames = Object.keys(schemaMessage.fields);\n        schemaFieldNames.forEach(field => schemaFieldProperties[schemaMessage.fields[field].id] = field);\n\n        // Loop over each field present in the raw decode output\n        for (const fieldName in rawDecodedMessage) {\n            let fieldId;\n            if (isNaN(fieldName)) {\n                fieldId = fieldName.match(/^field #(\\d+)/)[1];\n            } else {\n                fieldId = fieldName;\n            }\n\n            // Check if this field is defined in the schema\n            if (fieldId in schemaFieldProperties) {\n                const schemaFieldName = schemaFieldProperties[fieldId];\n\n                // Extract the current field data from the raw decode and schema\n                const rawFieldData = rawDecodedMessage[fieldName];\n                const schemaField = schemaMessage.fields[schemaFieldName];\n\n                // Check for repeated fields\n                if (Array.isArray(rawFieldData) && !schemaField.repeated) {\n                    rawDecodedMessage[`(${schemaMessage.name}) ${schemaFieldName} is a repeated field`] = rawFieldData;\n                }\n\n                // Check for submessage fields\n                if (schemaField.resolvedType instanceof protobuf.Type) {\n                    const subMessageType = schemaMessage.fields[schemaFieldName].type;\n                    const schemaSubMessage = this.parsedProto.root.nested[subMessageType];\n                    const rawSubMessages = rawDecodedMessage[fieldName];\n                    let rawDecodedSubMessage = {};\n\n                    // Squash multiple submessage instances into one submessage\n                    if (Array.isArray(rawSubMessages)) {\n                        rawSubMessages.forEach(subMessageInstance => {\n                            const instanceFields = Object.entries(subMessageInstance);\n                            instanceFields.forEach(subField => {\n                                rawDecodedSubMessage[subField[0]] = subField[1];\n                            });\n                        });\n                    } else {\n                        rawDecodedSubMessage = rawSubMessages;\n                    }\n\n                    // Treat submessage as own message and compare its fields\n                    rawDecodedSubMessage = Protobuf.compareFields(rawDecodedSubMessage, schemaSubMessage);\n                    if (Object.entries(rawDecodedSubMessage).length !== 0) {\n                        rawDecodedMessage[`${schemaFieldName} (${subMessageType}) has missing fields`] = rawDecodedSubMessage;\n                    }\n                }\n                delete rawDecodedMessage[fieldName];\n            }\n        }\n        return rawDecodedMessage;\n    }\n\n    /**\n     * Returns wiretype information for input wiretype number\n     *\n     * @param {number} wireType\n     * @returns {string}\n     */\n    static getTypeInfo(wireType) {\n        switch (wireType) {\n            case 0:\n                return \"VarInt (e.g. int32, bool)\";\n            case 1:\n                return \"64-Bit (e.g. fixed64, double)\";\n            case 2:\n                return \"L-delim (e.g. string, message)\";\n            case 5:\n                return \"32-Bit (e.g. fixed32, float)\";\n        }\n    }\n\n    // Private Class Functions\n\n    /**\n     * Main private parsing function\n     *\n     * @private\n     * @returns {Object}\n     */\n    _parse() {\n        let object = {};\n        // Continue reading whilst we still have data\n        while (this.offset < this.LENGTH) {\n            const field = this._parseField();\n            object = this._addField(field, object);\n        }\n        // Throw an error if we have gone beyond the end of the data\n        if (this.offset > this.LENGTH) {\n            throw new Error(\"Exhausted Buffer\");\n        }\n        return object;\n    }\n\n    /**\n     * Add a field read from the protobuf data into the Object. As\n     * protobuf fields can appear multiple times, if the field already\n     * exists we need to add the new field into an array of fields\n     * for that key.\n     *\n     * @private\n     * @param {Object} field\n     * @param {Object} object\n     * @returns {Object}\n     */\n    _addField(field, object) {\n        // Get the field key/values\n        const key = field.key;\n        const value = field.value;\n        object[key] = Object.prototype.hasOwnProperty.call(object, key) ?\n            object[key] instanceof Array ?\n                object[key].concat([value]) :\n                [object[key], value] :\n            value;\n        return object;\n    }\n\n    /**\n     * Parse a field and return the Object read from the record\n     *\n     * @private\n     * @returns {Object}\n     */\n    _parseField() {\n        // Get the field headers\n        const header = this._fieldHeader();\n        const type = header.type;\n        const key = header.key;\n\n        if (typeof(this.fieldTypes[key]) !== \"object\") {\n            this.fieldTypes[key] = type;\n        }\n\n        switch (type) {\n            // varint\n            case 0:\n                return { \"key\": key, \"value\": this._varInt() };\n            // fixed 64\n            case 1:\n                return { \"key\": key, \"value\": this._uint64() };\n            // length delimited\n            case 2:\n                return { \"key\": key, \"value\": this._lenDelim(key) };\n            // fixed 32\n            case 5:\n                return { \"key\": key, \"value\": this._uint32() };\n            // unknown type\n            default:\n                throw new Error(\"Unknown type 0x\" + type.toString(16));\n        }\n    }\n\n    /**\n     * Parse the field header and return the type and key\n     *\n     * @private\n     * @returns {Object}\n     */\n    _fieldHeader() {\n        // Make sure we call type then number to preserve offset\n        return { \"type\": this._fieldType(), \"key\": this._fieldNumber() };\n    }\n\n    /**\n     * Parse the field type from the field header. Type is stored in the\n     * lower 3 bits of the tag byte. This does not move the offset on as\n     * we need to read the field number from the tag byte too.\n     *\n     * @private\n     * @returns {number}\n     */\n    _fieldType() {\n        // Field type stored in lower 3 bits of tag byte\n        return this.data[this.offset] & this.TYPE;\n    }\n\n    /**\n     * Parse the field number (i.e. the key) from the field header. The\n     * field number is stored in the upper 5 bits of the tag byte - but\n     * is also varint encoded so the follow on bytes may need to be read\n     * when field numbers are > 15.\n     *\n     * @private\n     * @returns {number}\n     */\n    _fieldNumber() {\n        let shift = -3;\n        let fieldNumber = 0;\n        do {\n            fieldNumber += shift < 28 ?\n                shift === -3 ?\n                    (this.data[this.offset] & this.NUMBER) >> -shift :\n                    (this.data[this.offset] & this.VALUE) << shift :\n                (this.data[this.offset] & this.VALUE) * Math.pow(2, shift);\n            shift += 7;\n        } while ((this.data[this.offset++] & this.MSB) === this.MSB);\n        return fieldNumber;\n    }\n\n    // Field Parsing Functions\n\n    /**\n     * Read off a varint from the data\n     *\n     * @private\n     * @returns {number}\n     */\n    _varInt() {\n        let value = 0;\n        let shift = 0;\n        // Keep reading while upper bit set\n        do {\n            value += shift < 28 ?\n                (this.data[this.offset] & this.VALUE) << shift :\n                (this.data[this.offset] & this.VALUE) * Math.pow(2, shift);\n            shift += 7;\n        } while ((this.data[this.offset++] & this.MSB) === this.MSB);\n        return value;\n    }\n\n    /**\n     * Read off a 64 bit unsigned integer from the data\n     *\n     * @private\n     * @returns {number}\n     */\n    _uint64() {\n        // Read off a Uint64 with little-endian\n        const lowerHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;\n        const upperHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;\n        return upperHalf * 0x100000000 + lowerHalf;\n    }\n\n    /**\n     * Read off a length delimited field from the data\n     *\n     * @private\n     * @returns {Object|string}\n     */\n    _lenDelim(fieldNum) {\n        // Read off the field length\n        const length = this._varInt();\n        const fieldBytes = this.data.slice(this.offset, this.offset + length);\n        let field;\n        try {\n            // Attempt to parse as a new Protobuf Object\n            const pbObject = new Protobuf(fieldBytes);\n            field = pbObject._parse();\n\n            // Set field types object\n            this.fieldTypes[fieldNum] = {...this.fieldTypes[fieldNum], ...pbObject.fieldTypes};\n\n        } catch (err) {\n            // Otherwise treat as bytes\n            field = Utils.byteArrayToChars(fieldBytes);\n        }\n        // Move the offset and return the field\n        this.offset += length;\n        return field;\n    }\n\n    /**\n     * Read a 32 bit unsigned integer from the data\n     *\n     * @private\n     * @returns {number}\n     */\n    _uint32() {\n        // Use a dataview to read off the integer\n        const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);\n        const value = dataview.getUint32(0, true);\n        this.offset += 4;\n        return value;\n    }\n}\n\nexport default Protobuf;\n"
  },
  {
    "path": "src/core/lib/Protocol.mjs",
    "content": "/**\n * Protocol parsing functions.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport BigNumber from \"bignumber.js\";\nimport {toHexFast} from \"../lib/Hex.mjs\";\n\n/**\n * Recursively displays a JSON object as an HTML table\n *\n * @param {Object} obj\n * @returns string\n */\nexport function objToTable(obj, nested=false) {\n    let html = `<table\n        class='table table-sm table-nonfluid ${nested ? \"mb-0 table-borderless\" : \"table-bordered\"}'\n        style='table-layout: fixed; ${nested ? \"margin: -1px !important;\" : \"\"}'>`;\n    if (!nested)\n        html += `<tr>\n            <th>Field</th>\n            <th>Value</th>\n        </tr>`;\n\n    for (const key in obj) {\n        if (typeof obj[key] === \"function\")\n            continue;\n\n        html += `<tr><td style='word-wrap: break-word'>${key}</td>`;\n        if (typeof obj[key] === \"object\")\n            html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;\n        else\n            html += `<td>${obj[key]}</td>`;\n        html += \"</tr>\";\n    }\n    html += \"</table>\";\n    return html;\n}\n\n/**\n * Converts bytes into a BigNumber string\n * @param {Uint8Array} bs\n * @returns {string}\n */\nexport function bytesToLargeNumber(bs) {\n    return BigNumber(toHexFast(bs), 16).toString();\n}\n"
  },
  {
    "path": "src/core/lib/PublicKey.mjs",
    "content": "/**\n * Public key resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport { toHex, fromHex } from \"./Hex.mjs\";\n\n/**\n * Formats Distinguished Name (DN) objects to strings.\n *\n * @param {Object} dnObj\n * @param {number} indent\n * @returns {string}\n */\nexport function formatDnObj(dnObj, indent) {\n    let output = \"\";\n\n    const maxKeyLen = dnObj.array.reduce((max, item) => {\n        return item[0].type.length > max ? item[0].type.length : max;\n    }, 0);\n\n    for (let i = 0; i < dnObj.array.length; i++) {\n        if (!dnObj.array[i].length) continue;\n\n        const key = dnObj.array[i][0].type;\n        const value = dnObj.array[i][0].value;\n        const str = `${key.padEnd(maxKeyLen, \" \")} = ${value}\\n`;\n\n        output += str.padStart(indent + str.length, \" \");\n    }\n\n    return output.slice(0, -1);\n}\n\n\n/**\n * Formats byte strings by adding line breaks and delimiters.\n *\n * @param {string} byteStr\n * @param {number} length - Line width\n * @param {number} indent\n * @returns {string}\n */\nexport function formatByteStr(byteStr, length, indent) {\n    byteStr = toHex(fromHex(byteStr), \":\");\n    length = length * 3;\n    let output = \"\";\n\n    for (let i = 0; i < byteStr.length; i += length) {\n        const str = byteStr.slice(i, i + length) + \"\\n\";\n        if (i === 0) {\n            output += str;\n        } else {\n            output += str.padStart(indent + str.length, \" \");\n        }\n    }\n\n    return output.slice(0, output.length-1);\n}\n"
  },
  {
    "path": "src/core/lib/QRCode.mjs",
    "content": "/**\n * QR code resources\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport jsQR from \"jsqr\";\nimport qr from \"qr-image\";\nimport Utils from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Parses a QR code image from an image\n *\n * @param {ArrayBuffer} input\n * @param {boolean} normalise\n * @returns {string}\n */\nexport async function parseQrCode(input, normalise) {\n    let image;\n    try {\n        image = await Jimp.read(input);\n    } catch (err) {\n        throw new OperationError(`Error opening image. (${err})`);\n    }\n\n    try {\n        if (normalise) {\n            image.greyscale();\n            image.normalize();\n        }\n    } catch (err) {\n        throw new OperationError(`Error normalising image. (${err})`);\n    }\n\n    // Remove transparency which jsQR cannot handle\n    image.scan((x, y, idx) => {\n        // If pixel is fully transparent, make it opaque white\n        if (image.bitmap.data[idx + 3] === 0x00) {\n            image.bitmap.data[idx + 0] = 0xff;\n            image.bitmap.data[idx + 1] = 0xff;\n            image.bitmap.data[idx + 2] = 0xff;\n        }\n        // Otherwise, make it fully opaque at its existing colour\n        image.bitmap.data[idx + 3] = 0xff;\n    });\n    image = await Jimp.read(await image.getBuffer(JimpMime.jpeg));\n\n    const qrData = jsQR(\n        new Uint8ClampedArray(image.bitmap.data),\n        image.width,\n        image.height,\n    );\n    if (qrData) {\n        return qrData.data;\n    } else {\n        throw new OperationError(\"Could not read a QR code from the image.\");\n    }\n}\n\n/**\n * Generates a QR code from the input string\n *\n * @param {string} input\n * @param {string} format\n * @param {number} moduleSize\n * @param {number} margin\n * @param {string} errorCorrection\n * @returns {ArrayBuffer}\n */\nexport function generateQrCode(\n    input,\n    format,\n    moduleSize,\n    margin,\n    errorCorrection,\n) {\n    const formats = [\"SVG\", \"EPS\", \"PDF\", \"PNG\"];\n    if (!formats.includes(format.toUpperCase())) {\n        throw new OperationError(\"Unsupported QR code format.\");\n    }\n\n    let qrImage;\n    try {\n        qrImage = qr.imageSync(input, {\n            type: format,\n            size: moduleSize,\n            margin: margin,\n            // eslint-disable-next-line camelcase\n            ec_level: errorCorrection.charAt(0).toUpperCase(),\n        });\n    } catch (err) {\n        throw new OperationError(`Error generating QR code. (${err})`);\n    }\n\n    if (!qrImage) {\n        throw new OperationError(\"Error generating QR code.\");\n    }\n\n    switch (format) {\n        case \"SVG\":\n        case \"EPS\":\n        case \"PDF\":\n            return Utils.strToArrayBuffer(qrImage);\n        case \"PNG\":\n            return qrImage.buffer.slice(qrImage.byteOffset, qrImage.byteLength + qrImage.byteOffset);\n        default:\n            throw new OperationError(\"Unsupported QR code format.\");\n    }\n}\n"
  },
  {
    "path": "src/core/lib/RC6.mjs",
    "content": "/**\n * Complete implementation of RC6 block cipher encryption/decryption with\n * configurable word size (w), rounds (r), and key length (b).\n *\n * RC6 was an AES finalist designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin.\n * Reference: https://en.wikipedia.org/wiki/RC6\n * Test Vectors: https://datatracker.ietf.org/doc/html/draft-krovetz-rc6-rc5-vectors-00\n *\n * The P and Q constants are derived from mathematical constants e (Euler's number) and\n * φ (golden ratio) as specified in the IETF draft. Master 256-bit values are scaled to\n * any word size.\n *\n * @author Medjedtxm\n * @copyright Crown Copyright 2026\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Master P constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00\n * Derived from Odd((e-2) * 2^256) where e = 2.71828...\n */\nconst P_256 = 0xb7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfefn;\n\n/**\n * Master Q constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00\n * Derived from Odd((φ-1) * 2^256) where φ = 1.61803... (golden ratio)\n */\nconst Q_256 = 0x9e3779b97f4a7c15f39cc0605cedc8341082276bf3a27251f86c6a11d0c18e95n;\n\n/**\n * Get P constant for given word size by scaling the 256-bit master constant\n * @param {number} w - Word size in bits\n * @returns {bigint} - P constant for word size w\n */\nfunction getP(w) {\n    return (P_256 >> BigInt(256 - w)) | 1n; // Ensure odd\n}\n\n/**\n * Get Q constant for given word size by scaling the 256-bit master constant\n * @param {number} w - Word size in bits\n * @returns {bigint} - Q constant for word size w\n */\nfunction getQ(w) {\n    return (Q_256 >> BigInt(256 - w)) | 1n; // Ensure odd\n}\n\n/**\n * Get block size in bytes for given word size\n * Block size = 4 words = 4 * (w/8) bytes\n * @param {number} w - Word size in bits\n * @returns {number} - Block size in bytes\n */\nexport function getBlockSize(w) {\n    return 4 * (w / 8);\n}\n\n/**\n * Get recommended number of rounds for given word size\n * @param {number} w - Word size in bits\n * @returns {number} - Recommended rounds\n */\nexport function getDefaultRounds(w) {\n    if (w <= 16) return 16;\n    if (w <= 32) return 20;\n    if (w <= 64) return 24;\n    return 28;\n}\n\n/**\n * Create mask for w-bit word\n * @param {number} w - Word size in bits\n * @returns {bigint} - Mask with w bits set\n */\nfunction wordMask(w) {\n    return (1n << BigInt(w)) - 1n;\n}\n\n/**\n * Rotate left for arbitrary word size using BigInt\n * Uses lower lg(w) bits of n for rotation amount (RC6 spec)\n * @param {bigint} x - Value to rotate\n * @param {bigint} n - Rotation amount\n * @param {number} w - Word size in bits\n * @param {bigint} lgMask - Mask for lower lg(w) bits\n * @returns {bigint} - Rotated value\n */\nfunction ROL(x, n, w, lgMask) {\n    const mask = wordMask(w);\n    // Mask to lg(w) bits, then mod w for non-power-of-2 word sizes\n    // For power-of-2, (n & lgMask) < w always, so mod w is no-op\n    const shift = (n & lgMask) % BigInt(w);\n    return ((x << shift) | (x >> (BigInt(w) - shift))) & mask;\n}\n\n/**\n * Rotate right for arbitrary word size using BigInt\n * Uses lower lg(w) bits of n for rotation amount (RC6 spec)\n * @param {bigint} x - Value to rotate\n * @param {bigint} n - Rotation amount\n * @param {number} w - Word size in bits\n * @param {bigint} lgMask - Mask for lower lg(w) bits\n * @returns {bigint} - Rotated value\n */\nfunction ROR(x, n, w, lgMask) {\n    const mask = wordMask(w);\n    // Mask to lg(w) bits, then mod w for non-power-of-2 word sizes\n    // For power-of-2, (n & lgMask) < w always, so mod w is no-op\n    const shift = (n & lgMask) % BigInt(w);\n    return ((x >> shift) | (x << (BigInt(w) - shift))) & mask;\n}\n\n/**\n * Convert byte array to word array (little-endian) using BigInt\n * @param {number[]} bytes - Input byte array\n * @param {number} w - Word size in bits\n * @returns {bigint[]} - Array of w-bit words as BigInt\n */\nfunction bytesToWords(bytes, w) {\n    const bytesPerWord = w / 8;\n    const words = [];\n    for (let i = 0; i < bytes.length; i += bytesPerWord) {\n        let word = 0n;\n        for (let j = 0; j < bytesPerWord && (i + j) < bytes.length; j++) {\n            word |= BigInt(bytes[i + j] || 0) << BigInt(j * 8);\n        }\n        words.push(word);\n    }\n    return words;\n}\n\n/**\n * Convert word array to byte array (little-endian) using BigInt\n * @param {bigint[]} words - Array of words\n * @param {number} w - Word size in bits\n * @returns {number[]} - Output byte array\n */\nfunction wordsToBytes(words, w) {\n    const bytesPerWord = w / 8;\n    const bytes = [];\n    for (const word of words) {\n        for (let j = 0; j < bytesPerWord; j++) {\n            bytes.push(Number((word >> BigInt(j * 8)) & 0xFFn));\n        }\n    }\n    return bytes;\n}\n\n/**\n * Generate round subkeys from user key\n *\n * @param {number[]} key - User key as byte array\n * @param {number} rounds - Number of rounds\n * @param {number} w - Word size in bits\n * @returns {bigint[]} - Array of 2r+4 subkeys as BigInt\n */\nfunction generateSubkeys(key, rounds, w) {\n    const bytesPerWord = w / 8;\n    const b = key.length;\n    const c = Math.max(Math.ceil(b / bytesPerWord), 1);\n\n    // Convert key bytes to words, pad with zeros if needed\n    const paddedKey = [...key];\n    while (paddedKey.length < c * bytesPerWord) {\n        paddedKey.push(0);\n    }\n    const L = bytesToWords(paddedKey, w);\n\n    // Number of subkeys: 2*r + 4\n    const t = 2 * rounds + 4;\n\n    // Get P and Q for this word size\n    const P = getP(w);\n    const Q = getQ(w);\n    const mask = wordMask(w);\n\n    // lg(w) mask for rotation amounts (floor of log2(w), per RC6 spec)\n    const lgw = Math.floor(Math.log2(w));\n    const lgMask = (1n << BigInt(lgw)) - 1n;\n\n    // Initialise S array with magic constants\n    const S = new Array(t);\n    S[0] = P;\n    for (let i = 1; i < t; i++) {\n        S[i] = (S[i - 1] + Q) & mask;\n    }\n\n    // Mix key into S\n    let A = 0n, B = 0n;\n    let i = 0, j = 0;\n    const v = 3 * Math.max(c, t);\n\n    for (let s = 0; s < v; s++) {\n        A = S[i] = ROL((S[i] + A + B) & mask, 3n, w, lgMask);\n        B = L[j] = ROL((L[j] + A + B) & mask, A + B, w, lgMask);\n        i = (i + 1) % t;\n        j = (j + 1) % c;\n    }\n\n    return S;\n}\n\n/**\n * Encrypt a single block using RC6\n *\n * @param {number[]} block - Plaintext block (4*w/8 bytes)\n * @param {bigint[]} S - Subkeys array\n * @param {number} rounds - Number of rounds\n * @param {number} w - Word size in bits\n * @returns {number[]} - Ciphertext block\n */\nfunction encryptBlock(block, S, rounds, w) {\n    const mask = wordMask(w);\n    const lgw = BigInt(Math.floor(Math.log2(w)));\n    const lgMask = (1n << lgw) - 1n;\n\n    // Convert block to 4 words (A, B, C, D)\n    let [A, B, C, D] = bytesToWords(block, w);\n\n    // Pre-whitening\n    B = (B + S[0]) & mask;\n    D = (D + S[1]) & mask;\n\n    // Main rounds\n    for (let i = 1; i <= rounds; i++) {\n        // t = ROL(B * (2B + 1), lg(w))\n        const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);\n\n        // u = ROL(D * (2D + 1), lg(w))\n        const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);\n\n        // A = ROL(A ^ t, u) + S[2i]\n        A = (ROL(A ^ t, u, w, lgMask) + S[2 * i]) & mask;\n\n        // C = ROL(C ^ u, t) + S[2i + 1]\n        C = (ROL(C ^ u, t, w, lgMask) + S[2 * i + 1]) & mask;\n\n        // Rotate registers: (A, B, C, D) = (B, C, D, A)\n        const temp = A;\n        A = B;\n        B = C;\n        C = D;\n        D = temp;\n    }\n\n    // Post-whitening\n    A = (A + S[2 * rounds + 2]) & mask;\n    C = (C + S[2 * rounds + 3]) & mask;\n\n    // Convert words back to bytes\n    return wordsToBytes([A, B, C, D], w);\n}\n\n/**\n * Decrypt a single block using RC6\n *\n * @param {number[]} block - Ciphertext block (4*w/8 bytes)\n * @param {bigint[]} S - Subkeys array\n * @param {number} rounds - Number of rounds\n * @param {number} w - Word size in bits\n * @returns {number[]} - Plaintext block\n */\nfunction decryptBlock(block, S, rounds, w) {\n    const mask = wordMask(w);\n    const lgw = BigInt(Math.floor(Math.log2(w)));\n    const lgMask = (1n << lgw) - 1n;\n\n    // Convert block to 4 words (A, B, C, D)\n    let [A, B, C, D] = bytesToWords(block, w);\n\n    // Reverse post-whitening\n    C = (C - S[2 * rounds + 3] + (1n << BigInt(w))) & mask;\n    A = (A - S[2 * rounds + 2] + (1n << BigInt(w))) & mask;\n\n    // Main rounds in reverse\n    for (let i = rounds; i >= 1; i--) {\n        // Reverse rotate registers: (A, B, C, D) = (D, A, B, C)\n        const temp = D;\n        D = C;\n        C = B;\n        B = A;\n        A = temp;\n\n        // u = ROL(D * (2D + 1), lg(w))\n        const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);\n\n        // t = ROL(B * (2B + 1), lg(w))\n        const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);\n\n        // C = ROR(C - S[2i + 1], t) ^ u\n        C = ROR((C - S[2 * i + 1] + (1n << BigInt(w))) & mask, t, w, lgMask) ^ u;\n\n        // A = ROR(A - S[2i], u) ^ t\n        A = ROR((A - S[2 * i] + (1n << BigInt(w))) & mask, u, w, lgMask) ^ t;\n    }\n\n    // Reverse pre-whitening\n    D = (D - S[1] + (1n << BigInt(w))) & mask;\n    B = (B - S[0] + (1n << BigInt(w))) & mask;\n\n    // Convert words back to bytes\n    return wordsToBytes([A, B, C, D], w);\n}\n\n/**\n * XOR two blocks\n * @param {number[]} a - First block\n * @param {number[]} b - Second block\n * @returns {number[]} - XOR result\n */\nfunction xorBlocks(a, b) {\n    const result = new Array(a.length);\n    for (let i = 0; i < a.length; i++) {\n        result[i] = a[i] ^ b[i];\n    }\n    return result;\n}\n\n/**\n * Increment counter (little-endian)\n * @param {number[]} counter - Counter block\n * @returns {number[]} - Incremented counter\n */\nfunction incrementCounter(counter) {\n    const result = [...counter];\n    for (let i = 0; i < result.length; i++) {\n        result[i]++;\n        if (result[i] <= 255) break;\n        result[i] = 0;\n    }\n    return result;\n}\n\n/**\n * Apply padding to message\n * @param {number[]} message - Original message\n * @param {string} padding - Padding type (\"NO\", \"PKCS5\", \"ZERO\", \"RANDOM\", \"BIT\")\n * @param {number} blockSize - Block size in bytes\n * @returns {number[]} - Padded message\n */\nfunction applyPadding(message, padding, blockSize) {\n    const remainder = message.length % blockSize;\n    let nPadding = remainder === 0 ? 0 : blockSize - remainder;\n\n    // For PKCS5, always add at least one byte (full block if already aligned)\n    if (padding === \"PKCS5\" && remainder === 0) {\n        nPadding = blockSize;\n    }\n\n    if (nPadding === 0) return [...message];\n\n    const paddedMessage = [...message];\n\n    switch (padding) {\n        case \"NO\":\n            throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`);\n\n        case \"PKCS5\":\n            for (let i = 0; i < nPadding; i++) {\n                paddedMessage.push(nPadding);\n            }\n            break;\n\n        case \"ZERO\":\n            for (let i = 0; i < nPadding; i++) {\n                paddedMessage.push(0);\n            }\n            break;\n\n        case \"RANDOM\":\n            for (let i = 0; i < nPadding; i++) {\n                paddedMessage.push(Math.floor(Math.random() * 256));\n            }\n            break;\n\n        case \"BIT\":\n            paddedMessage.push(0x80);\n            for (let i = 1; i < nPadding; i++) {\n                paddedMessage.push(0);\n            }\n            break;\n\n        default:\n            throw new OperationError(`Unknown padding type: ${padding}`);\n    }\n\n    return paddedMessage;\n}\n\n/**\n * Remove padding from message\n * @param {number[]} message - Padded message\n * @param {string} padding - Padding type (\"NO\", \"PKCS5\", \"ZERO\", \"RANDOM\", \"BIT\")\n * @param {number} blockSize - Block size in bytes\n * @returns {number[]} - Unpadded message\n */\nfunction removePadding(message, padding, blockSize) {\n    if (message.length === 0) return message;\n\n    switch (padding) {\n        case \"NO\":\n        case \"ZERO\":\n        case \"RANDOM\":\n            // These padding types cannot be reliably removed\n            return message;\n\n        case \"PKCS5\": {\n            const padByte = message[message.length - 1];\n            if (padByte > 0 && padByte <= blockSize) {\n                // Verify padding\n                for (let i = 0; i < padByte; i++) {\n                    if (message[message.length - 1 - i] !== padByte) {\n                        throw new OperationError(\"Invalid PKCS#5 padding.\");\n                    }\n                }\n                return message.slice(0, message.length - padByte);\n            }\n            throw new OperationError(\"Invalid PKCS#5 padding.\");\n        }\n\n        case \"BIT\": {\n            // Find 0x80 byte working backwards, skipping zeros\n            for (let i = message.length - 1; i >= 0; i--) {\n                if (message[i] === 0x80) {\n                    return message.slice(0, i);\n                } else if (message[i] !== 0) {\n                    throw new OperationError(\"Invalid BIT padding.\");\n                }\n            }\n            throw new OperationError(\"Invalid BIT padding.\");\n        }\n\n        default:\n            throw new OperationError(`Unknown padding type: ${padding}`);\n    }\n}\n\n/**\n * Encrypt using RC6 cipher with specified block mode\n *\n * @param {number[]} message - Plaintext as byte array\n * @param {number[]} key - Key as byte array\n * @param {number[]} iv - IV (block size bytes, not used for ECB)\n * @param {string} mode - Block cipher mode (\"ECB\", \"CBC\", \"CFB\", \"OFB\", \"CTR\")\n * @param {string} padding - Padding type (\"NO\", \"PKCS5\", \"ZERO\", \"RANDOM\", \"BIT\")\n * @param {number} rounds - Number of rounds (default: 20)\n * @param {number} w - Word size in bits (default: 32)\n * @returns {number[]} - Ciphertext as byte array\n */\nexport function encryptRC6(message, key, iv, mode = \"ECB\", padding = \"PKCS5\", rounds = 20, w = 32) {\n    const blockSize = getBlockSize(w);\n    const messageLength = message.length;\n    if (messageLength === 0) return [];\n\n    const S = generateSubkeys(key, rounds, w);\n\n    // Apply padding for ECB/CBC modes\n    let paddedMessage;\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        paddedMessage = applyPadding(message, padding, blockSize);\n    } else {\n        // Stream modes (CFB, OFB, CTR) don't need padding\n        paddedMessage = [...message];\n    }\n\n    const cipherText = [];\n\n    switch (mode) {\n        case \"ECB\":\n            for (let i = 0; i < paddedMessage.length; i += blockSize) {\n                const block = paddedMessage.slice(i, i + blockSize);\n                cipherText.push(...encryptBlock(block, S, rounds, w));\n            }\n            break;\n\n        case \"CBC\": {\n            let ivBlock = [...iv];\n            for (let i = 0; i < paddedMessage.length; i += blockSize) {\n                const block = paddedMessage.slice(i, i + blockSize);\n                const xored = xorBlocks(block, ivBlock);\n                ivBlock = encryptBlock(xored, S, rounds, w);\n                cipherText.push(...ivBlock);\n            }\n            break;\n        }\n\n        case \"CFB\": {\n            let ivBlock = [...iv];\n            for (let i = 0; i < paddedMessage.length; i += blockSize) {\n                const encrypted = encryptBlock(ivBlock, S, rounds, w);\n                const block = paddedMessage.slice(i, i + blockSize);\n                // Pad block if shorter than blockSize\n                while (block.length < blockSize) block.push(0);\n                ivBlock = xorBlocks(encrypted, block);\n                cipherText.push(...ivBlock);\n            }\n            return cipherText.slice(0, messageLength);\n        }\n\n        case \"OFB\": {\n            let ivBlock = [...iv];\n            for (let i = 0; i < paddedMessage.length; i += blockSize) {\n                ivBlock = encryptBlock(ivBlock, S, rounds, w);\n                const block = paddedMessage.slice(i, i + blockSize);\n                // Pad block if shorter than blockSize\n                while (block.length < blockSize) block.push(0);\n                cipherText.push(...xorBlocks(ivBlock, block));\n            }\n            return cipherText.slice(0, messageLength);\n        }\n\n        case \"CTR\": {\n            let counter = [...iv];\n            for (let i = 0; i < paddedMessage.length; i += blockSize) {\n                const encrypted = encryptBlock(counter, S, rounds, w);\n                const block = paddedMessage.slice(i, i + blockSize);\n                // Pad block if shorter than blockSize\n                while (block.length < blockSize) block.push(0);\n                cipherText.push(...xorBlocks(encrypted, block));\n                counter = incrementCounter(counter);\n            }\n            return cipherText.slice(0, messageLength);\n        }\n\n        default:\n            throw new OperationError(`Invalid block cipher mode: ${mode}`);\n    }\n\n    return cipherText;\n}\n\n/**\n * Decrypt using RC6 cipher with specified block mode\n *\n * @param {number[]} cipherText - Ciphertext as byte array\n * @param {number[]} key - Key as byte array\n * @param {number[]} iv - IV (block size bytes, not used for ECB)\n * @param {string} mode - Block cipher mode (\"ECB\", \"CBC\", \"CFB\", \"OFB\", \"CTR\")\n * @param {string} padding - Padding type (\"NO\", \"PKCS5\", \"ZERO\", \"RANDOM\", \"BIT\")\n * @param {number} rounds - Number of rounds (default: 20)\n * @param {number} w - Word size in bits (default: 32)\n * @returns {number[]} - Plaintext as byte array\n */\nexport function decryptRC6(cipherText, key, iv, mode = \"ECB\", padding = \"PKCS5\", rounds = 20, w = 32) {\n    const blockSize = getBlockSize(w);\n    const originalLength = cipherText.length;\n    if (originalLength === 0) return [];\n\n    const S = generateSubkeys(key, rounds, w);\n\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        if ((originalLength % blockSize) !== 0)\n            throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${blockSize}.`);\n    } else {\n        // Pad for stream modes\n        while ((cipherText.length % blockSize) !== 0)\n            cipherText.push(0);\n    }\n\n    const plainText = [];\n\n    switch (mode) {\n        case \"ECB\":\n            for (let i = 0; i < cipherText.length; i += blockSize) {\n                const block = cipherText.slice(i, i + blockSize);\n                plainText.push(...decryptBlock(block, S, rounds, w));\n            }\n            break;\n\n        case \"CBC\": {\n            let ivBlock = [...iv];\n            for (let i = 0; i < cipherText.length; i += blockSize) {\n                const block = cipherText.slice(i, i + blockSize);\n                const decrypted = decryptBlock(block, S, rounds, w);\n                plainText.push(...xorBlocks(decrypted, ivBlock));\n                ivBlock = block;\n            }\n            break;\n        }\n\n        case \"CFB\": {\n            let ivBlock = [...iv];\n            for (let i = 0; i < cipherText.length; i += blockSize) {\n                const encrypted = encryptBlock(ivBlock, S, rounds, w);\n                const block = cipherText.slice(i, i + blockSize);\n                plainText.push(...xorBlocks(encrypted, block));\n                ivBlock = block;\n            }\n            return plainText.slice(0, originalLength);\n        }\n\n        case \"OFB\": {\n            let ivBlock = [...iv];\n            for (let i = 0; i < cipherText.length; i += blockSize) {\n                ivBlock = encryptBlock(ivBlock, S, rounds, w);\n                const block = cipherText.slice(i, i + blockSize);\n                plainText.push(...xorBlocks(ivBlock, block));\n            }\n            return plainText.slice(0, originalLength);\n        }\n\n        case \"CTR\": {\n            let counter = [...iv];\n            for (let i = 0; i < cipherText.length; i += blockSize) {\n                const encrypted = encryptBlock(counter, S, rounds, w);\n                const block = cipherText.slice(i, i + blockSize);\n                plainText.push(...xorBlocks(encrypted, block));\n                counter = incrementCounter(counter);\n            }\n            return plainText.slice(0, originalLength);\n        }\n\n        default:\n            throw new OperationError(`Invalid block cipher mode: ${mode}`);\n    }\n\n    // Remove padding for ECB/CBC modes\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        return removePadding(plainText, padding, blockSize);\n    }\n\n    return plainText.slice(0, originalLength);\n}\n"
  },
  {
    "path": "src/core/lib/RSA.mjs",
    "content": "/**\n * RSA resources.\n *\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport forge from \"node-forge\";\n\nexport const MD_ALGORITHMS = {\n    \"SHA-1\": forge.md.sha1,\n    \"MD5\": forge.md.md5,\n    \"SHA-256\": forge.md.sha256,\n    \"SHA-384\": forge.md.sha384,\n    \"SHA-512\": forge.md.sha512,\n};\n"
  },
  {
    "path": "src/core/lib/Rotate.mjs",
    "content": "/**\n * Bit rotation functions.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n *\n * @todo Support for UTF16\n */\n\n\n/**\n * Runs rotation operations across the input data.\n *\n * @param {byteArray} data\n * @param {number} amount\n * @param {function} algo - The rotation operation to carry out\n * @returns {byteArray}\n */\nexport function rot(data, amount, algo) {\n    const result = [];\n    for (let i = 0; i < data.length; i++) {\n        let b = data[i];\n        for (let j = 0; j < amount; j++) {\n            b = algo(b);\n        }\n        result.push(b);\n    }\n    return result;\n}\n\n\n/**\n * Rotate right bitwise op.\n *\n * @param {byte} b\n * @returns {byte}\n */\nexport function rotr(b) {\n    const bit = (b & 1) << 7;\n    return (b >> 1) | bit;\n}\n\n/**\n * Rotate left bitwise op.\n *\n * @param {byte} b\n * @returns {byte}\n */\nexport function rotl(b) {\n    const bit = (b >> 7) & 1;\n    return ((b << 1) | bit) & 0xFF;\n}\n\n\n/**\n * Rotates a byte array to the right by a specific amount as a whole, so that bits are wrapped\n * from the end of the array to the beginning.\n *\n * @param {byteArray} data\n * @param {number} amount\n * @returns {byteArray}\n */\nexport function rotrCarry(data, amount) {\n    const result = [];\n    let carryBits = 0,\n        newByte;\n\n    amount = amount % 8;\n    for (let i = 0; i < data.length; i++) {\n        const oldByte = data[i] >>> 0;\n        newByte = (oldByte >> amount) | carryBits;\n        carryBits = (oldByte & (Math.pow(2, amount)-1)) << (8-amount);\n        result.push(newByte);\n    }\n    result[0] |= carryBits;\n    return result;\n}\n\n\n/**\n * Rotates a byte array to the left by a specific amount as a whole, so that bits are wrapped\n * from the beginning of the array to the end.\n *\n * @param {byteArray} data\n * @param {number} amount\n * @returns {byteArray}\n */\nexport function rotlCarry(data, amount) {\n    const result = [];\n    let carryBits = 0,\n        newByte;\n\n    amount = amount % 8;\n    for (let i = data.length-1; i >= 0; i--) {\n        const oldByte = data[i];\n        newByte = ((oldByte << amount) | carryBits) & 0xFF;\n        carryBits = (oldByte >> (8-amount)) & (Math.pow(2, amount)-1);\n        result[i] = (newByte);\n    }\n    result[data.length-1] = result[data.length-1] | carryBits;\n    return result;\n}\n"
  },
  {
    "path": "src/core/lib/SIGABA.mjs",
    "content": "/**\n * Emulation of the SIGABA machine\n *\n * @author hettysymes\n * @copyright hettysymes 2020\n * @license Apache-2.0\n */\n\n/**\n * A set of randomised example SIGABA cipher/control rotors (these rotors are interchangeable). Cipher and control rotors can be referred to as C and R rotors respectively.\n */\nexport const CR_ROTORS = [\n    {name: \"Example 1\", value: \"SRGWANHPJZFXVIDQCEUKBYOLMT\"},\n    {name: \"Example 2\", value: \"THQEFSAZVKJYULBODCPXNIMWRG\"},\n    {name: \"Example 3\", value: \"XDTUYLEVFNQZBPOGIRCSMHWKAJ\"},\n    {name: \"Example 4\", value: \"LOHDMCWUPSTNGVXYFJREQIKBZA\"},\n    {name: \"Example 5\", value: \"ERXWNZQIJYLVOFUMSGHTCKPBDA\"},\n    {name: \"Example 6\", value: \"FQECYHJIOUMDZVPSLKRTGWXBAN\"},\n    {name: \"Example 7\", value: \"TBYIUMKZDJSOPEWXVANHLCFQGR\"},\n    {name: \"Example 8\", value: \"QZUPDTFNYIAOMLEBWJXCGHKRSV\"},\n    {name: \"Example 9\", value: \"CZWNHEMPOVXLKRSIDGJFYBTQAU\"},\n    {name: \"Example 10\", value: \"ENPXJVKYQBFZTICAGMOHWRLDUS\"}\n];\n\n/**\n * A set of randomised example SIGABA index rotors (may be referred to as I rotors).\n */\nexport const I_ROTORS = [\n    {name: \"Example 1\", value: \"6201348957\"},\n    {name: \"Example 2\", value: \"6147253089\"},\n    {name: \"Example 3\", value: \"8239647510\"},\n    {name: \"Example 4\", value: \"7194835260\"},\n    {name: \"Example 5\", value: \"4873205916\"}\n];\n\nexport const NUMBERS = \"0123456789\".split(\"\");\n\n/**\n * Converts a letter to uppercase (if it already isn't)\n *\n * @param {char} letter - letter to convert to uppercase\n * @returns {char}\n */\nexport function convToUpperCase(letter) {\n    const charCode = letter.charCodeAt();\n    if (97<=charCode && charCode<=122) {\n        return String.fromCharCode(charCode-32);\n    }\n    return letter;\n}\n\n/**\n * The SIGABA machine consisting of the 3 rotor banks: cipher, control and index banks.\n */\nexport class SigabaMachine {\n\n    /**\n     * SigabaMachine constructor\n     *\n     * @param {Object[]} cipherRotors - list of CRRotors\n     * @param {Object[]} controlRotors - list of CRRotors\n     * @param {object[]} indexRotors - list of IRotors\n     */\n    constructor(cipherRotors, controlRotors, indexRotors) {\n        this.cipherBank = new CipherBank(cipherRotors);\n        this.controlBank = new ControlBank(controlRotors);\n        this.indexBank = new IndexBank(indexRotors);\n    }\n\n    /**\n     * Steps all the correct rotors in the machine.\n     */\n    step() {\n        const controlOut = this.controlBank.goThroughControl();\n        const indexOut = this.indexBank.goThroughIndex(controlOut);\n        this.cipherBank.step(indexOut);\n    }\n\n    /**\n     * Encrypts a letter. A space is converted to a \"Z\" before encryption, and a \"Z\" is converted to an \"X\". This allows spaces to be encrypted.\n     *\n     * @param {char} letter - letter to encrypt\n     * @returns {char}\n     */\n    encryptLetter(letter) {\n        letter = convToUpperCase(letter);\n        if (letter === \" \") {\n            letter = \"Z\";\n        } else if (letter === \"Z\") {\n            letter = \"X\";\n        }\n        const encryptedLetter = this.cipherBank.encrypt(letter);\n        this.step();\n        return encryptedLetter;\n    }\n\n    /**\n     * Decrypts a letter. A letter decrypted as a \"Z\" is converted to a space before it is output, since spaces are converted to \"Z\"s before encryption.\n     *\n     * @param {char} letter - letter to decrypt\n     * @returns {char}\n     */\n    decryptLetter(letter) {\n        letter = convToUpperCase(letter);\n        let decryptedLetter = this.cipherBank.decrypt(letter);\n        if (decryptedLetter === \"Z\") {\n            decryptedLetter = \" \";\n        }\n        this.step();\n        return decryptedLetter;\n    }\n\n    /**\n     * Encrypts a message of one or more letters\n     *\n     * @param {string} msg - message to encrypt\n     * @returns {string}\n     */\n    encrypt(msg) {\n        let ciphertext = \"\";\n        for (const letter of msg) {\n            ciphertext = ciphertext.concat(this.encryptLetter(letter));\n        }\n        return ciphertext;\n    }\n\n    /**\n     * Decrypts a message of one or more letters\n     *\n     * @param {string} msg - message to decrypt\n     * @returns {string}\n     */\n    decrypt(msg) {\n        let plaintext = \"\";\n        for (const letter of msg) {\n            plaintext = plaintext.concat(this.decryptLetter(letter));\n        }\n        return plaintext;\n    }\n\n}\n\n/**\n * The cipher rotor bank consists of 5 cipher rotors in either a forward or reversed orientation.\n */\nexport class CipherBank {\n\n    /**\n     * CipherBank constructor\n     *\n     * @param {Object[]} rotors - list of CRRotors\n     */\n    constructor(rotors) {\n        this.rotors = rotors;\n    }\n\n    /**\n     * Encrypts a letter through the cipher rotors (signal goes from left-to-right)\n     *\n     * @param {char} inputPos - the input position of the signal (letter to be encrypted)\n     * @returns {char}\n     */\n    encrypt(inputPos) {\n        for (const rotor of this.rotors) {\n            inputPos = rotor.crypt(inputPos, \"leftToRight\");\n        }\n        return inputPos;\n    }\n\n    /**\n     * Decrypts a letter through the cipher rotors (signal goes from right-to-left)\n     *\n     * @param {char} inputPos - the input position of the signal (letter to be decrypted)\n     * @returns {char}\n     */\n    decrypt(inputPos) {\n        const revOrderedRotors = [...this.rotors].reverse();\n        for (const rotor of revOrderedRotors) {\n            inputPos = rotor.crypt(inputPos, \"rightToLeft\");\n        }\n        return inputPos;\n    }\n\n    /**\n     * Step the cipher rotors forward according to the inputs from the index rotors\n     *\n     * @param {number[]} indexInputs - the inputs from the index rotors\n     */\n    step(indexInputs) {\n        const logicDict = {0: [0, 9], 1: [7, 8], 2: [5, 6], 3: [3, 4], 4: [1, 2]};\n        const rotorsToMove = [];\n        for (const key in logicDict) {\n            const item = logicDict[key];\n            for (const i of indexInputs) {\n                if (item.includes(i)) {\n                    rotorsToMove.push(this.rotors[key]);\n                    break;\n                }\n            }\n        }\n        for (const rotor of rotorsToMove) {\n            rotor.step();\n        }\n    }\n\n}\n\n/**\n * The control rotor bank consists of 5 control rotors in either a forward or reversed orientation. Signals to the control rotor bank always go from right-to-left.\n */\nexport class ControlBank {\n\n    /**\n     * ControlBank constructor. The rotors have been reversed as signals go from right-to-left through the control rotors.\n     *\n     * @param {Object[]} rotors - list of CRRotors\n     */\n    constructor(rotors) {\n        this.rotors = [...rotors].reverse();\n    }\n\n    /**\n     * Encrypts a letter.\n     *\n     * @param {char} inputPos - the input position of the signal\n     * @returns {char}\n     */\n    crypt(inputPos) {\n        for (const rotor of this.rotors) {\n            inputPos = rotor.crypt(inputPos, \"rightToLeft\");\n        }\n        return inputPos;\n    }\n\n    /**\n     * Gets the outputs of the control rotors. The inputs to the control rotors are always \"F\", \"G\", \"H\" and \"I\".\n     *\n     * @returns {number[]}\n     */\n    getOutputs() {\n        const outputs = [this.crypt(\"F\"), this.crypt(\"G\"), this.crypt(\"H\"), this.crypt(\"I\")];\n        const logicDict = {1: \"B\", 2: \"C\", 3: \"DE\", 4: \"FGH\", 5: \"IJK\", 6: \"LMNO\", 7: \"PQRST\", 8: \"UVWXYZ\", 9: \"A\"};\n        const numberOutputs = [];\n        for (const key in logicDict) {\n            const item = logicDict[key];\n            for (const output of outputs) {\n                if (item.includes(output)) {\n                    numberOutputs.push(key);\n                    break;\n                }\n            }\n        }\n        return numberOutputs;\n    }\n\n    /**\n     * Steps the control rotors. Only 3 of the control rotors step: one after every encryption, one after every 26, and one after every 26 squared.\n     */\n    step() {\n        const MRotor = this.rotors[1], FRotor = this.rotors[2], SRotor = this.rotors[3];\n        // 14 is the offset of \"O\" from \"A\" - the next rotor steps once the previous rotor reaches \"O\"\n        if (FRotor.state === 14) {\n            if (MRotor.state === 14) {\n                SRotor.step();\n            }\n            MRotor.step();\n        }\n        FRotor.step();\n    }\n\n    /**\n     * The goThroughControl function combines getting the outputs from the control rotor bank and then stepping them.\n     *\n     * @returns {number[]}\n     */\n    goThroughControl() {\n        const outputs = this.getOutputs();\n        this.step();\n        return outputs;\n    }\n\n}\n\n/**\n * The index rotor bank consists of 5 index rotors all placed in the forwards orientation.\n */\nexport class IndexBank {\n\n    /**\n     * IndexBank constructor\n     *\n     * @param {Object[]} rotors - list of IRotors\n     */\n    constructor(rotors) {\n        this.rotors = rotors;\n    }\n\n    /**\n     * Encrypts a number.\n     *\n     * @param {number} inputPos - the input position of the signal\n     * @returns {number}\n     */\n    crypt(inputPos) {\n        for (const rotor of this.rotors) {\n            inputPos = rotor.crypt(inputPos);\n        }\n        return inputPos;\n    }\n\n    /**\n     * The goThroughIndex function takes the inputs from the control rotor bank and returns the list of outputs after encryption through the index rotors.\n     *\n     * @param {number[]} controlInputs - inputs from the control rotors\n     * @returns {number[]}\n     */\n    goThroughIndex(controlInputs) {\n        const outputs = [];\n        for (const inp of controlInputs) {\n            outputs.push(this.crypt(inp));\n        }\n        return outputs;\n    }\n\n}\n\n/**\n * Rotor class\n */\nexport class Rotor {\n\n    /**\n     * Rotor constructor\n     *\n     * @param {number[]} wireSetting - the wirings within the rotor: mapping from left-to-right, the index of the number in the list maps onto the number at that index\n     * @param {bool} rev - true if the rotor is reversed, false if it isn't\n     * @param {number} key - the starting position or state of the rotor\n     */\n    constructor(wireSetting, key, rev) {\n        this.state = key;\n        this.numMapping = this.getNumMapping(wireSetting, rev);\n        this.posMapping = this.getPosMapping(rev);\n    }\n\n    /**\n     * Get the number mapping from the wireSetting (only different from wireSetting if rotor is reversed)\n     *\n     * @param {number[]} wireSetting - the wirings within the rotors\n     * @param {bool} rev - true if reversed, false if not\n     * @returns {number[]}\n     */\n    getNumMapping(wireSetting, rev) {\n        if (rev===false) {\n            return wireSetting;\n        } else {\n            const length = wireSetting.length;\n            const tempMapping = new Array(length);\n            for (let i=0; i<length; i++) {\n                tempMapping[wireSetting[i]] = i;\n            }\n            return tempMapping;\n        }\n    }\n\n    /**\n     * Get the position mapping (how the position numbers map onto the numbers of the rotor)\n     *\n     * @param {bool} rev - true if reversed, false if not\n     * @returns {number[]}\n     */\n    getPosMapping(rev) {\n        const length = this.numMapping.length;\n        const posMapping = [];\n        if (rev===false) {\n            for (let i = this.state; i < this.state+length; i++) {\n                let res = i%length;\n                if (res<0) {\n                    res += length;\n                }\n                posMapping.push(res);\n            }\n        } else {\n            for (let i = this.state; i > this.state-length; i--) {\n                let res = i%length;\n                if (res<0) {\n                    res += length;\n                }\n                posMapping.push(res);\n            }\n        }\n        return posMapping;\n    }\n\n    /**\n     * Encrypt/decrypt data. This process is identical to the rotors of cipher machines such as Enigma or Typex.\n     *\n     * @param {number} inputPos - the input position of the signal (the data to encrypt/decrypt)\n     * @param {string} direction - one of \"leftToRight\" and \"rightToLeft\", states the direction in which the signal passes through the rotor\n     * @returns {number}\n     */\n    cryptNum(inputPos, direction) {\n        const inpNum = this.posMapping[inputPos];\n        let outNum;\n        if (direction === \"leftToRight\") {\n            outNum = this.numMapping[inpNum];\n        } else if (direction === \"rightToLeft\") {\n            outNum = this.numMapping.indexOf(inpNum);\n        }\n        const outPos = this.posMapping.indexOf(outNum);\n        return outPos;\n    }\n\n    /**\n     * Steps the rotor. The number at position 0 will be moved to position 1 etc.\n     */\n    step() {\n        const lastNum = this.posMapping.pop();\n        this.posMapping.splice(0, 0, lastNum);\n        this.state = this.posMapping[0];\n    }\n\n}\n\n/**\n * A CRRotor is a cipher (C) or control (R) rotor. These rotors are identical and interchangeable. A C or R rotor consists of 26 contacts, one for each letter, and may be put into either a forwards of reversed orientation.\n */\nexport class CRRotor extends Rotor {\n\n    /**\n     * CRRotor constructor\n     *\n     * @param {string} wireSetting - the rotor wirings (string of letters)\n     * @param {char} key - initial state of rotor\n     * @param {bool} rev - true if reversed, false if not\n     */\n    constructor(wireSetting, key, rev=false) {\n        wireSetting = wireSetting.split(\"\").map(CRRotor.letterToNum);\n        super(wireSetting, CRRotor.letterToNum(key), rev);\n    }\n\n    /**\n     * Static function which converts a letter into its number i.e. its offset from the letter \"A\"\n     *\n     * @param {char} letter - letter to convert to number\n     * @returns {number}\n     */\n    static letterToNum(letter) {\n        return letter.charCodeAt()-65;\n    }\n\n    /**\n     * Static function which converts a number (a letter's offset from \"A\") into its letter\n     *\n     * @param {number} num - number to convert to letter\n     * @returns {char}\n     */\n    static numToLetter(num) {\n        return String.fromCharCode(num+65);\n    }\n\n    /**\n     * Encrypts/decrypts a letter.\n     *\n     * @param {char} inputPos - the input position of the signal (\"A\" refers to position 0 etc.)\n     * @param {string} direction - one of \"leftToRight\" and \"rightToLeft\"\n     * @returns {char}\n     */\n    crypt(inputPos, direction) {\n        inputPos = CRRotor.letterToNum(inputPos);\n        const outPos = this.cryptNum(inputPos, direction);\n        return CRRotor.numToLetter(outPos);\n    }\n\n}\n\n/**\n * An IRotor is an index rotor, which consists of 10 contacts each numbered from 0 to 9. Unlike C and R rotors, they cannot be put in the reversed orientation. The index rotors do not step at any point during encryption or decryption.\n */\nexport class IRotor extends Rotor {\n\n    /**\n     * IRotor constructor\n     *\n     * @param {string} wireSetting - the rotor wirings (string of numbers)\n     * @param {char} key - initial state of rotor\n     */\n    constructor(wireSetting, key) {\n        wireSetting = wireSetting.split(\"\").map(Number);\n        super(wireSetting, Number(key), false);\n    }\n\n    /**\n     * Encrypts a number\n     *\n     * @param {number} inputPos - the input position of the signal\n     * @returns {number}\n     */\n    crypt(inputPos) {\n        return this.cryptNum(inputPos, \"leftToRight\");\n    }\n\n}\n"
  },
  {
    "path": "src/core/lib/SM2.mjs",
    "content": "/**\n * Utilities and operations utilized for SM2 encryption and decryption\n * @author flakjacket95 [dflack95@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { fromHex } from \"../lib/Hex.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Sm3 from \"crypto-api/src/hasher/sm3.mjs\";\nimport {toHex} from \"crypto-api/src/encoder/hex.mjs\";\nimport r from \"jsrsasign\";\n\n/**\n * SM2 Class for encryption and decryption operations\n */\nexport class SM2 {\n    /**\n     * Constructor for SM2 class; sets up with the curve and the output format as specified in user args\n     *\n     * @param {*} curve\n     * @param {*} format\n     */\n    constructor(curve, format) {\n        this.ecParams = null;\n        this.rng = new r.SecureRandom();\n        /*\n        For any additional curve definitions utilized by SM2, add another block like the below for that curve, then add the curve name to the Curve selection dropdown\n        */\n        r.crypto.ECParameterDB.regist(\n            \"sm2p256v1\", // name / p = 2**256 - 2**224 - 2**96 + 2**64 - 1\n            256,\n            \"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF\", // p\n            \"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC\", // a\n            \"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93\", // b\n            \"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123\", // n\n            \"1\", // h\n            \"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7\", // gx\n            \"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0\", // gy\n            []\n        ); // alias\n        this.ecParams = r.crypto.ECParameterDB.getByName(curve);\n\n        this.format = format;\n    }\n\n    /**\n     * Set the public key coordinates for the SM2 class\n     *\n     * @param {string} publicKeyX\n     * @param {string} publicKeyY\n     */\n    setPublicKey(publicKeyX, publicKeyY) {\n        /*\n        * TODO: This needs some additional length validation; and checking for errors in the decoding process\n        * TODO: Can probably support other public key encoding methods here as well in the future\n        */\n        this.publicKey = this.ecParams.curve.decodePointHex(\"04\" + publicKeyX + publicKeyY);\n\n        if (this.publicKey.isInfinity()) {\n            throw new OperationError(\"Invalid Public Key\");\n        }\n    }\n\n    /**\n     * Set the private key value for the SM2 class\n     *\n     * @param {string} privateKey\n     */\n    setPrivateKey(privateKeyHex) {\n        this.privateKey = new r.BigInteger(privateKeyHex, 16);\n    }\n\n    /**\n     * Main encryption function; takes user input, processes encryption and returns the result in hex (with the components arranged as configured by the user args)\n     *\n     * @param {*} input\n     * @returns {string}\n     */\n    encrypt(input) {\n        const G = this.ecParams.G;\n\n        /*\n        * Compute a new, random public key along the same elliptic curve to form the starting point for our encryption process (record the resulting X and Y as hex to provide as part of the operation output)\n        * k: Randomly generated BigInteger\n        * c1: Result of dotting our curve generator point `G` with the value of `k`\n        */\n        const k = this.generatePublicKey();\n        const c1 = G.multiply(k);\n        const [hexC1X, hexC1Y] = this.getPointAsHex(c1);\n\n        /*\n        * Compute p2 (secret) using the public key, and the chosen k value above\n        */\n        const p2 = this.publicKey.multiply(k);\n\n        /*\n        * Compute the C3 SM3 hash before we transform the array\n        */\n        const c3 = this.c3(p2, input);\n\n        /*\n        * Genreate a proper length encryption key, XOR iteratively, and convert newly encrypted data to hex\n        */\n        const key = this.kdf(p2, input.byteLength);\n        for (let i = 0; i < input.byteLength; i++) {\n            input[i] ^= Utils.ord(key[i]);\n        }\n        const c2 = Buffer.from(input).toString(\"hex\");\n\n        /*\n         * Check user input specs; order the output components as selected\n         */\n        if (this.format === \"C1C3C2\") {\n            return hexC1X + hexC1Y + c3 + c2;\n        } else {\n            return hexC1X + hexC1Y + c2 + c3;\n        }\n    }\n    /**\n     * Function to decrypt an SM2 encrypted message\n     *\n     * @param {*} input\n     */\n    decrypt(input) {\n        const c1X = input.slice(0, 64);\n        const c1Y = input.slice(64, 128);\n\n        let c3 = \"\";\n        let c2 = \"\";\n\n        if (this.format === \"C1C3C2\") {\n            c3 = input.slice(128, 192);\n            c2 = input.slice(192);\n        } else {\n            c2 = input.slice(128, -64);\n            c3 = input.slice(-64);\n        }\n        c2 = Uint8Array.from(fromHex(c2));\n        const c1 = this.ecParams.curve.decodePointHex(\"04\" + c1X + c1Y);\n\n        /*\n        * Compute the p2 (secret) value by taking the C1 point provided in the encrypted package, and multiplying by the private k value\n        */\n        const p2 = c1.multiply(this.privateKey);\n\n        /*\n         * Similar to encryption; compute sufficient length key material and XOR the input data to recover the original message\n         */\n        const key = this.kdf(p2, c2.byteLength);\n\n        for (let i = 0; i < c2.byteLength; i++) {\n            c2[i] ^= Utils.ord(key[i]);\n        }\n\n        const check = this.c3(p2, c2);\n        if (check === c3) {\n            return c2.buffer;\n        } else {\n            throw new OperationError(\"Decryption Error -- Computed Hashes Do Not Match\");\n        }\n    }\n\n\n    /**\n     * Generates a large random number\n     *\n     * @param {*} limit\n     * @returns\n     */\n    getBigRandom(limit) {\n        return new r.BigInteger(limit.bitLength(), this.rng)\n\t    .mod(limit.subtract(r.BigInteger.ONE))\n\t    .add(r.BigInteger.ONE);\n    }\n\n    /**\n     * Helper function for generating a large random K number; utilized for generating our initial C1 point\n     * TODO: Do we need to do any sort of validation on the resulting k values?\n     *\n     * @returns {BigInteger}\n     */\n    generatePublicKey() {\n        const n = this.ecParams.n;\n        const k = this.getBigRandom(n);\n        return k;\n    }\n\n    /**\n     * SM2 Key Derivation Function (KDF); Takes P2 point, and generates a key material stream large enough to encrypt all of the input data\n     *\n     * @param {*} p2\n     * @param {*} len\n     * @returns {string}\n     */\n    kdf(p2, len) {\n        const [hX, hY] = this.getPointAsHex(p2);\n\n        const total = Math.ceil(len / 32) + 1;\n        let cnt = 1;\n\n        let keyMaterial = \"\";\n\n        while (cnt < total) {\n            const num = Utils.intToByteArray(cnt, 4, \"big\");\n            const overall = fromHex(hX).concat(fromHex(hY)).concat(num);\n            keyMaterial += this.sm3(overall);\n            cnt++;\n        }\n        return keyMaterial;\n    }\n\n    /**\n     * Calculates the C3 component of our final encrypted payload; which is the SM3 hash of the P2 point and the original, unencrypted input data\n     *\n     * @param {*} p2\n     * @param {*} input\n     * @returns {string}\n     */\n    c3(p2, input) {\n        const [hX, hY] = this.getPointAsHex(p2);\n\n        const overall = fromHex(hX).concat(Array.from(input)).concat(fromHex(hY));\n\n        return toHex(this.sm3(overall));\n\n    }\n\n    /**\n     * SM3 setup helper function; takes input data as an array, processes the hash and returns the result\n     *\n     * @param {*} data\n     * @returns {string}\n     */\n    sm3(data) {\n        const hashData = Utils.arrayBufferToStr(Uint8Array.from(data).buffer, false);\n        const hasher = new Sm3();\n        hasher.update(hashData);\n        return hasher.finalize();\n    }\n\n    /**\n    * Utility function, returns an elliptic curve points X and Y values as hex;\n    *\n    * @param {EcPointFp} point\n    * @returns {[]}\n    */\n    getPointAsHex(point) {\n        const biX = point.getX().toBigInteger();\n        const biY = point.getY().toBigInteger();\n\n        const charlen = this.ecParams.keycharlen;\n        const hX   = (\"0000000000\" + biX.toString(16)).slice(- charlen);\n        const hY   = (\"0000000000\" + biY.toString(16)).slice(- charlen);\n        return [hX, hY];\n    }\n}\n"
  },
  {
    "path": "src/core/lib/SM4.mjs",
    "content": "/**\n * Complete implementation of SM4 cipher encryption/decryption with\n * ECB, CBC, CFB, OFB, CTR block modes.\n * These modes are specified in IETF draft-ribose-cfrg-sm4-09, see:\n * https://tools.ietf.org/id/draft-ribose-cfrg-sm4-09.html\n * for details.\n *\n * Follows spec from Cryptography Standardization Technical Comittee:\n * http://www.gmbz.org.cn/upload/2018-04-04/1522788048733065051.pdf\n *\n * @author swesven\n * @copyright 2021\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/** Number of rounds */\nconst NROUNDS = 32;\n\n/** block size in bytes */\nconst BLOCKSIZE = 16;\n\n/** The S box, 256 8-bit values */\nconst Sbox = [\n    0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,\n    0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,\n    0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,\n    0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,\n    0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,\n    0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,\n    0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,\n    0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,\n    0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,\n    0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,\n    0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,\n    0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,\n    0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,\n    0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,\n    0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,\n    0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48\n];\n\n/** \"Fixed parameter CK\" used in key expansion */\nconst CK = [\n    0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,\n    0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,\n    0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,\n    0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,\n    0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,\n    0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,\n    0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,\n    0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279\n];\n\n/** \"System parameter FK\" */\nconst FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc];\n\n/**\n * Rotating 32-bit shift left\n *\n * (Note that although JS integers are stored in doubles and thus have 53 bits,\n * the JS bitwise operations are 32-bit)\n */\nfunction ROL(i, n) {\n    return (i << n) | (i >>> (32 - n));\n}\n\n/**\n * Linear transformation L\n *\n * @param {integer} b - a 32 bit integer\n */\nfunction transformL(b) {\n    /* Replace each of the 4 bytes in b with the value at its offset in the Sbox */\n    b = (Sbox[(b >>> 24) & 0xFF] << 24) | (Sbox[(b >>> 16) & 0xFF] << 16) |\n        (Sbox[(b >>> 8) & 0xFF] << 8) | Sbox[b & 0xFF];\n    /* circular rotate and xor */\n    return b ^ ROL(b, 2) ^ ROL(b, 10) ^ ROL(b, 18) ^ ROL(b, 24);\n}\n\n/**\n * Linear transformation L'\n *\n * @param {integer} b - a 32 bit integer\n */\nfunction transformLprime(b) {\n    /* Replace each of the 4 bytes in b with the value at its offset in the Sbox */\n    b = (Sbox[(b >>> 24) & 0xFF] << 24) | (Sbox[(b >>> 16) & 0xFF] << 16) |\n        (Sbox[(b >>> 8) & 0xFF] << 8) | Sbox[b & 0xFF];\n    return b ^ ROL(b, 13) ^ ROL(b, 23); /* circular rotate and XOR */\n}\n\n/**\n * Initialize the round key\n */\nfunction initSM4RoundKey(rawkey) {\n    const K = rawkey.map((a, i) => a ^ FK[i]);    /* K = rawkey ^ FK */\n    const roundKey = [];\n    for (let i = 0; i < 32; i++)\n        roundKey[i] = K[i + 4] = K[i] ^ transformLprime(K[i + 1] ^ K[i + 2] ^ K[i + 3] ^ CK[i]);\n    return roundKey;\n}\n\n/**\n * Encrypts/decrypts a single block X (4 32-bit values) with a prepared round key.\n *\n * @param {intArray} X - A cleartext block.\n * @param {intArray} roundKey - The round key from initSMRoundKey for encrypting (reversed for decrypting).\n * @returns {byteArray} - The cipher text.\n */\nfunction encryptBlockSM4(X, roundKey) {\n    for (let i = 0; i < NROUNDS; i++)\n        X[i + 4] = X[i] ^ transformL(X[i + 1] ^ X[i + 2] ^ X[i + 3] ^ roundKey[i]);\n    return [X[35], X[34], X[33], X[32]];\n}\n\n/**\n * Takes 16 bytes from an offset in an array and returns an array of 4 32-bit Big-Endian values.\n * (DataView won't work portably here as we need Big-Endian)\n *\n * @param {byteArray} bArray - the array of bytes\n * @param {integer} offset - starting offset in the array; 15 bytes must follow it.\n */\nfunction bytesToInts(bArray, offs=0) {\n    let offset = offs;\n    const A = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];\n    offset += 4;\n    const B = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];\n    offset += 4;\n    const C = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];\n    offset += 4;\n    const D = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];\n    return [A, B, C, D];\n}\n\n/**\n * Inverse of bytesToInts above; takes an array of 32-bit integers and turns it into an array of bytes.\n * Again, Big-Endian order.\n */\nfunction intsToBytes(ints) {\n    const bArr = [];\n    for (let i = 0; i < ints.length; i++) {\n        bArr.push((ints[i] >> 24) & 0xFF);\n        bArr.push((ints[i] >> 16) & 0xFF);\n        bArr.push((ints[i] >> 8) & 0xFF);\n        bArr.push(ints[i] & 0xFF);\n    }\n    return bArr;\n}\n\n/**\n * Encrypt using SM4 using a given block cipher mode.\n *\n * @param {byteArray} message - The clear text message; any length under 32 Gb or so.\n * @param {byteArray} key - The cipher key, 16 bytes.\n * @param {byteArray} iv - The IV or nonce, 16 bytes (not used with ECB mode)\n * @param {string} mode - The block cipher mode \"CBC\", \"ECB\", \"CFB\", \"OFB\", \"CTR\".\n * @param {boolean} noPadding - Don't add PKCS#7 padding if set.\n * @returns {byteArray} - The cipher text.\n */\nexport function encryptSM4(message, key, iv, mode=\"ECB\", noPadding=false) {\n    const messageLength = message.length;\n    if (messageLength === 0)\n        return [];\n    const roundKey = initSM4RoundKey(bytesToInts(key, 0));\n\n    /* Pad with PKCS#7 if requested for ECB/CBC else add zeroes (which are sliced off at the end) */\n    let padByte = 0;\n    let nPadding = 16 - (message.length & 0xF);\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        if (noPadding) {\n            if (nPadding !== 16)\n                throw new OperationError(`No padding requested in ${mode} mode but input is not a 16-byte multiple.`);\n            nPadding = 0;\n        } else\n            padByte = nPadding;\n    }\n    for (let i = 0; i < nPadding; i++)\n        message.push(padByte);\n\n    const cipherText = [];\n    switch (mode) {\n        case \"ECB\":\n            for (let i = 0; i < message.length; i += BLOCKSIZE)\n                Array.prototype.push.apply(cipherText, intsToBytes(encryptBlockSM4(bytesToInts(message, i), roundKey)));\n            break;\n        case \"CBC\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < message.length; i += BLOCKSIZE) {\n                const block = bytesToInts(message, i);\n                block[0] ^= iv[0]; block[1] ^= iv[1];\n                block[2] ^= iv[2]; block[3] ^= iv[3];\n                iv = encryptBlockSM4(block, roundKey);\n                Array.prototype.push.apply(cipherText, intsToBytes(iv));\n            }\n            break;\n        case \"CFB\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < message.length; i += BLOCKSIZE) {\n                iv = encryptBlockSM4(iv, roundKey);\n                const block = bytesToInts(message, i);\n                block[0] ^= iv[0]; block[1] ^= iv[1];\n                block[2] ^= iv[2]; block[3] ^= iv[3];\n                Array.prototype.push.apply(cipherText, intsToBytes(block));\n                iv = block;\n            }\n            break;\n        case \"OFB\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < message.length; i += BLOCKSIZE) {\n                iv = encryptBlockSM4(iv, roundKey);\n                const block = bytesToInts(message, i);\n                block[0] ^= iv[0]; block[1] ^= iv[1];\n                block[2] ^= iv[2]; block[3] ^= iv[3];\n                Array.prototype.push.apply(cipherText, intsToBytes(block));\n            }\n            break;\n        case \"CTR\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < message.length; i += BLOCKSIZE) {\n                let iv2 = [...iv]; /* containing the IV + counter */\n                iv2[3] += (i >> 4);/* Using a 32 bit counter here. 64 Gb encrypts should be enough for everyone. */\n                iv2 = encryptBlockSM4(iv2, roundKey);\n                const block = bytesToInts(message, i);\n                block[0] ^= iv2[0]; block[1] ^= iv2[1];\n                block[2] ^= iv2[2]; block[3] ^= iv2[3];\n                Array.prototype.push.apply(cipherText, intsToBytes(block));\n            }\n            break;\n        default:\n            throw new OperationError(\"Invalid block cipher mode: \"+mode);\n    }\n    if (mode !== \"ECB\" && mode !== \"CBC\")\n        return cipherText.slice(0, messageLength);\n    return cipherText;\n}\n\n/**\n * Decrypt using SM4 using a given block cipher mode.\n *\n * @param {byteArray} cipherText - The ciphertext\n * @param {byteArray} key - The cipher key, 16 bytes.\n * @param {byteArray} iv - The IV or nonce, 16 bytes (not used with ECB mode)\n * @param {string} mode - The block cipher mode \"CBC\", \"ECB\", \"CFB\", \"OFB\", \"CTR\"\n * @param {boolean] ignorePadding - If true, ignore padding issues in ECB/CBC mode.\n * @returns {byteArray} - The cipher text.\n */\nexport function decryptSM4(cipherText, key, iv, mode=\"ECB\", ignorePadding=false) {\n    const originalLength = cipherText.length;\n    if (originalLength === 0)\n        return [];\n    let roundKey = initSM4RoundKey(bytesToInts(key, 0));\n\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        /* Init decryption key */\n        roundKey = roundKey.reverse();\n        if ((originalLength & 0xF) !== 0 && !ignorePadding)\n            throw new OperationError(`With ECB or CBC modes, the input must be divisible into 16 byte blocks. (${cipherText.length & 0xF} bytes extra)`);\n    } else { /* Pad dummy bytes for other modes, chop them off at the end */\n        while ((cipherText.length & 0xF) !== 0)\n            cipherText.push(0);\n    }\n\n    const clearText = [];\n    switch (mode) {\n        case \"ECB\":\n            for (let i = 0; i < cipherText.length; i += BLOCKSIZE)\n                Array.prototype.push.apply(clearText, intsToBytes(encryptBlockSM4(bytesToInts(cipherText, i), roundKey)));\n            break;\n        case \"CBC\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {\n                const block = encryptBlockSM4(bytesToInts(cipherText, i), roundKey);\n                block[0] ^= iv[0]; block[1] ^= iv[1];\n                block[2] ^= iv[2]; block[3] ^= iv[3];\n                Array.prototype.push.apply(clearText, intsToBytes(block));\n                iv = bytesToInts(cipherText, i);\n            }\n            break;\n        case \"CFB\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {\n                iv = encryptBlockSM4(iv, roundKey);\n                const block = bytesToInts(cipherText, i);\n                block[0] ^= iv[0]; block[1] ^= iv[1];\n                block[2] ^= iv[2]; block[3] ^= iv[3];\n                Array.prototype.push.apply(clearText, intsToBytes(block));\n                iv = bytesToInts(cipherText, i);\n            }\n            break;\n        case \"OFB\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {\n                iv = encryptBlockSM4(iv, roundKey);\n                const block = bytesToInts(cipherText, i);\n                block[0] ^= iv[0]; block[1] ^= iv[1];\n                block[2] ^= iv[2]; block[3] ^= iv[3];\n                Array.prototype.push.apply(clearText, intsToBytes(block));\n            }\n            break;\n        case \"CTR\":\n            iv = bytesToInts(iv, 0);\n            for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {\n                let iv2 = [...iv]; /* containing the IV + counter */\n                iv2[3] += (i >> 4);/* Using a 32 bit counter here. 64 Gb encrypts should be enough for everyone. */\n                iv2 = encryptBlockSM4(iv2, roundKey);\n                const block = bytesToInts(cipherText, i);\n                block[0] ^= iv2[0]; block[1] ^= iv2[1];\n                block[2] ^= iv2[2]; block[3] ^= iv2[3];\n                Array.prototype.push.apply(clearText, intsToBytes(block));\n            }\n            break;\n        default:\n            throw new OperationError(`Invalid block cipher mode: ${mode}`);\n    }\n    /* Check PKCS#7 padding */\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        if (ignorePadding)\n            return clearText;\n        const padByte = clearText[clearText.length - 1];\n        if (padByte > 16)\n            throw new OperationError(\"Invalid PKCS#7 padding.\");\n        for (let i = 0; i < padByte; i++)\n            if (clearText[clearText.length -i - 1] !== padByte)\n                throw new OperationError(\"Invalid PKCS#7 padding.\");\n        return clearText.slice(0, clearText.length - padByte);\n    }\n    return clearText.slice(0, originalLength);\n}\n\n"
  },
  {
    "path": "src/core/lib/Salsa20.mjs",
    "content": "/**\n * @author joostrijneveld [joost@joostrijneveld.nl]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Computes the Salsa20 permute function\n *\n * @param {byteArray} x\n * @param {integer} rounds\n */\nfunction salsa20Permute(x, rounds) {\n    /**\n     * Macro to compute a 32-bit rotate-left operation\n     *\n     * @param {integer} x\n     * @param {integer} n\n     * @returns {integer}\n     */\n    function ROL32(x, n) {\n        return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));\n    }\n\n    /**\n     * Macro to compute a single Salsa20 quarterround operation\n     *\n     * @param {integer} x\n     * @param {integer} a\n     * @param {integer} b\n     * @param {integer} c\n     * @param {integer} d\n     * @returns {integer}\n     */\n    function quarterround(x, a, b, c, d) {\n        x[b] ^= ROL32((x[a] + x[d]) & 0xFFFFFFFF, 7);\n        x[c] ^= ROL32((x[b] + x[a]) & 0xFFFFFFFF, 9);\n        x[d] ^= ROL32((x[c] + x[b]) & 0xFFFFFFFF, 13);\n        x[a] ^= ROL32((x[d] + x[c]) & 0xFFFFFFFF, 18);\n    }\n\n    for (let i = 0; i < rounds / 2; i++)  {\n        quarterround(x, 0, 4, 8, 12);\n        quarterround(x, 5, 9, 13, 1);\n        quarterround(x, 10, 14, 2, 6);\n        quarterround(x, 15, 3, 7, 11);\n        quarterround(x, 0, 1, 2, 3);\n        quarterround(x, 5, 6, 7, 4);\n        quarterround(x, 10, 11, 8, 9);\n        quarterround(x, 15, 12, 13, 14);\n    }\n}\n\n/**\n * Computes the Salsa20 block function\n *\n * @param {byteArray} key\n * @param {byteArray} nonce\n * @param {byteArray} counter\n * @param {integer} rounds\n * @returns {byteArray}\n */\nexport function salsa20Block(key, nonce, counter, rounds) {\n    const tau = \"expand 16-byte k\";\n    const sigma = \"expand 32-byte k\";\n    let state, c;\n    if (key.length === 16) {\n        c = Utils.strToByteArray(tau);\n        key = key.concat(key);\n    } else {\n        c = Utils.strToByteArray(sigma);\n    }\n\n    state = c.slice(0, 4);\n    state = state.concat(key.slice(0, 16));\n    state = state.concat(c.slice(4, 8));\n    state = state.concat(nonce);\n    state = state.concat(counter);\n    state = state.concat(c.slice(8, 12));\n    state = state.concat(key.slice(16, 32));\n    state = state.concat(c.slice(12, 16));\n\n    const x = Array();\n    for (let i = 0; i < 64; i += 4) {\n        x.push(Utils.byteArrayToInt(state.slice(i, i + 4), \"little\"));\n    }\n    const a = [...x];\n\n    salsa20Permute(x, rounds);\n\n    for (let i = 0; i < 16; i++) {\n        x[i] = (x[i] + a[i]) & 0xFFFFFFFF;\n    }\n\n    let output = Array();\n    for (let i = 0; i < 16; i++) {\n        output = output.concat(Utils.intToByteArray(x[i], 4, \"little\"));\n    }\n    return output;\n}\n\n/**\n * Computes the hSalsa20 function\n *\n * @param {byteArray} key\n * @param {byteArray} nonce\n * @param {integer} rounds\n * @returns {byteArray}\n */\nexport function hsalsa20(key, nonce, rounds) {\n    const tau = \"expand 16-byte k\";\n    const sigma = \"expand 32-byte k\";\n    let state, c;\n    if (key.length === 16) {\n        c = Utils.strToByteArray(tau);\n        key = key.concat(key);\n    } else {\n        c = Utils.strToByteArray(sigma);\n    }\n\n    state = c.slice(0, 4);\n    state = state.concat(key.slice(0, 16));\n    state = state.concat(c.slice(4, 8));\n    state = state.concat(nonce);\n    state = state.concat(c.slice(8, 12));\n    state = state.concat(key.slice(16, 32));\n    state = state.concat(c.slice(12, 16));\n\n    const x = Array();\n    for (let i = 0; i < 64; i += 4) {\n        x.push(Utils.byteArrayToInt(state.slice(i, i + 4), \"little\"));\n    }\n\n    salsa20Permute(x, rounds);\n\n    let output = Array();\n    const idx = [0, 5, 10, 15, 6, 7, 8, 9];\n    for (let i = 0; i < 8; i++) {\n        output = output.concat(Utils.intToByteArray(x[idx[i]], 4, \"little\"));\n    }\n    return output;\n}\n"
  },
  {
    "path": "src/core/lib/Sort.mjs",
    "content": "/**\n * Sorting functions\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n *\n */\n\n/**\n * Comparison operation for sorting of strings ignoring case.\n *\n * @param {string} a\n * @param {string} b\n * @returns {number}\n */\nexport function caseInsensitiveSort(a, b) {\n    return a.toLowerCase().localeCompare(b.toLowerCase());\n}\n\n\n/**\n * Comparison operation for sorting of IPv4 addresses.\n *\n * @param {string} a\n * @param {string} b\n * @returns {number}\n */\nexport function ipSort(a, b) {\n    let a_ = a.split(\".\"),\n        b_ = b.split(\".\");\n\n    a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1;\n    b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1;\n\n    if (isNaN(a_) && !isNaN(b_)) return 1;\n    if (!isNaN(a_) && isNaN(b_)) return -1;\n    if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b);\n\n    return a_ - b_;\n}\n\n/**\n * Comparison operation for sorting of numeric values.\n *\n * @author Chris van Marle\n * @param {string} a\n * @param {string} b\n * @returns {number}\n */\nexport function numericSort(a, b) {\n    const a_ = a.split(/([^\\d]+)/),\n        b_ = b.split(/([^\\d]+)/);\n\n    for (let i = 0; i < a_.length && i < b.length; ++i) {\n        if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers\n        if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;\n        if (isNaN(a_[i]) && isNaN(b_[i])) {\n            const ret = a_[i].localeCompare(b_[i]); // Compare strings\n            if (ret !== 0) return ret;\n        }\n        if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers\n            if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];\n        }\n    }\n\n    return a.localeCompare(b);\n}\n\n/**\n * Comparison operation for sorting of hexadecimal values.\n *\n * @author Chris van Marle\n * @param {string} a\n * @param {string} b\n * @returns {number}\n */\nexport function hexadecimalSort(a, b) {\n    let a_ = a.split(/([^\\da-f]+)/i),\n        b_ = b.split(/([^\\da-f]+)/i);\n\n    a_ = a_.map(v => {\n        const t = parseInt(v, 16);\n        return isNaN(t) ? v : t;\n    });\n\n    b_ = b_.map(v => {\n        const t = parseInt(v, 16);\n        return isNaN(t) ? v : t;\n    });\n\n    for (let i = 0; i < a_.length && i < b.length; ++i) {\n        if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers\n        if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;\n        if (isNaN(a_[i]) && isNaN(b_[i])) {\n            const ret = a_[i].localeCompare(b_[i]); // Compare strings\n            if (ret !== 0) return ret;\n        }\n        if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers\n            if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];\n        }\n    }\n\n    return a.localeCompare(b);\n}\n\n/**\n * Comparison operation for sorting by length\n *\n * @param {string} a\n * @param {string} b\n * @returns {number}\n */\nexport function lengthSort(a, b) {\n    return a.length - b.length;\n}\n\n"
  },
  {
    "path": "src/core/lib/Stream.mjs",
    "content": "/**\n * Stream class for parsing binary protocols.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n *\n */\n\n/**\n * A Stream can be used to traverse a binary blob, interpreting sections of it\n * as various data types.\n */\nexport default class Stream {\n\n    /**\n     * Stream constructor.\n     *\n     * @param {Uint8Array} input\n     * @param {number} pos\n     * @param {number} bitPos\n     */\n    constructor(input, pos=0, bitPos=0) {\n        this.bytes = input;\n        this.length = this.bytes.length;\n        this.position = pos;\n        this.bitPos = bitPos;\n    }\n\n    /**\n     * Clone this Stream returning a new identical Stream.\n     *\n     * @returns {Stream}\n     */\n    clone() {\n        return new Stream(this.bytes, this.position, this.bitPos);\n    }\n\n    /**\n     * Get a number of bytes from the current position, or all remaining bytes.\n     *\n     * @param {number} [numBytes=null]\n     * @returns {Uint8Array}\n     */\n    getBytes(numBytes=null) {\n        if (this.position > this.length) return undefined;\n\n        const newPosition = numBytes !== null ?\n            this.position + numBytes :\n            this.length;\n        const bytes = this.bytes.slice(this.position, newPosition);\n        this.position = newPosition;\n        this.bitPos = 0;\n        return bytes;\n    }\n\n    /**\n     * Interpret the following bytes as a string, stopping at the next null byte or\n     * the supplied limit.\n     *\n     * @param {number} [numBytes=-1]\n     * @returns {string}\n     */\n    readString(numBytes=-1) {\n        if (this.position > this.length) return undefined;\n\n        if (numBytes === -1) numBytes = this.length - this.position;\n\n        let result = \"\";\n        for (let i = this.position; i < this.position + numBytes; i++) {\n            const currentByte = this.bytes[i];\n            if (currentByte === 0) break;\n            result += String.fromCharCode(currentByte);\n        }\n        this.position += numBytes;\n        this.bitPos = 0;\n        return result;\n    }\n\n    /**\n     * Interpret the following bytes as an integer in big or little endian.\n     *\n     * @param {number} numBytes\n     * @param {string} [endianness=\"be\"]\n     * @returns {number}\n     */\n    readInt(numBytes, endianness=\"be\") {\n        if (this.position > this.length) return undefined;\n\n        let val = 0;\n        if (endianness === \"be\") {\n            for (let i = this.position; i < this.position + numBytes; i++) {\n                val = val << 8;\n                val |= this.bytes[i];\n            }\n        } else {\n            for (let i = this.position + numBytes - 1; i >= this.position; i--) {\n                val = val << 8;\n                val |= this.bytes[i];\n            }\n        }\n        this.position += numBytes;\n        this.bitPos = 0;\n        return val;\n    }\n\n    /**\n     * Reads a number of bits from the buffer in big or little endian.\n     *\n     * @param {number} numBits\n     * @param {string} [endianness=\"be\"]\n     * @returns {number}\n     */\n    readBits(numBits, endianness=\"be\") {\n        if (this.position > this.length) return undefined;\n\n        let bitBuf = 0,\n            bitBufLen = 0;\n\n        // Add remaining bits from current byte\n        bitBuf = this.bytes[this.position++] & bitMask(this.bitPos);\n        if (endianness !== \"be\") bitBuf >>>= this.bitPos;\n        bitBufLen = 8 - this.bitPos;\n        this.bitPos = 0;\n\n        // Not enough bits yet\n        while (bitBufLen < numBits) {\n            if (endianness === \"be\")\n                bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++];\n            else\n                bitBuf |= this.bytes[this.position++] << bitBufLen;\n            bitBufLen += 8;\n        }\n\n        // Reverse back to numBits\n        if (bitBufLen > numBits) {\n            const excess = bitBufLen - numBits;\n            if (endianness === \"be\")\n                bitBuf >>>= excess;\n            else\n                bitBuf &= (1 << numBits) - 1;\n            bitBufLen -= excess;\n            this.position--;\n            this.bitPos = 8 - excess;\n        }\n\n        return bitBuf;\n\n        /**\n         * Calculates the bit mask based on the current bit position.\n         *\n         * @param {number} bitPos\n         * @returns {number} The bit mask\n         */\n        function bitMask(bitPos) {\n            return endianness === \"be\" ?\n                (1 << (8 - bitPos)) - 1 :\n                256 - (1 << bitPos);\n        }\n    }\n\n    /**\n     * Consume the stream until we reach the specified byte or sequence of bytes.\n     *\n     * @param {number|List<number>} val\n     */\n    continueUntil(val) {\n        if (this.position > this.length) return;\n\n        this.bitPos = 0;\n\n        if (typeof val === \"number\") {\n            while (++this.position < this.length && this.bytes[this.position] !== val) {\n                continue;\n            }\n            return;\n        }\n\n        // val is an array\n\n        /**\n         * Builds the skip forward table from the value to be searched.\n         *\n         * @param {Uint8Array} val\n         * @param {Number} len\n         * @returns {Uint8Array}\n         */\n        function preprocess(val, len) {\n            const skiptable = new Array();\n            val.forEach((element, index) => {\n                skiptable[element] = len - index;\n            });\n            return skiptable;\n        }\n\n        const length = val.length;\n        const initial = val[length-1];\n        this.position = length;\n\n        // Get the skip table.\n        const skiptable = preprocess(val, length);\n        let found;\n\n        while (this.position < this.length) {\n            // Until we hit the final element of val in the stream.\n            while ((this.position < this.length) && (this.bytes[this.position++] !== initial));\n\n            found = true;\n\n            // Loop through the elements comparing them to val.\n            for (let x = length-1; x >= 0; x--) {\n                if (this.bytes[this.position - length + x] !== val[x]) {\n                    found = false;\n\n                    // If element is not equal to val's element then jump forward by the correct amount.\n                    this.position += skiptable[val[x]];\n                    break;\n                }\n            }\n            if (found) {\n                this.position -= length;\n                break;\n            }\n        }\n    }\n\n\n    /**\n     * Consume bytes if they match the supplied value.\n     *\n     * @param {Number} val\n     */\n    consumeWhile(val) {\n        while (this.position < this.length) {\n            if (this.bytes[this.position] !== val) {\n                break;\n            }\n            this.position++;\n        }\n        this.bitPos = 0;\n    }\n\n    /**\n     * Consume the next byte if it matches the supplied value.\n     *\n     * @param {number} val\n     */\n    consumeIf(val) {\n        if (this.bytes[this.position] === val) {\n            this.position++;\n            this.bitPos = 0;\n        }\n    }\n\n    /**\n     * Move forwards through the stream by the specified number of bytes.\n     *\n     * @param {number} numBytes\n     */\n    moveForwardsBy(numBytes) {\n        const pos = this.position + numBytes;\n        if (pos < 0 || pos > this.length)\n            throw new Error(\"Cannot move to position \" + pos + \" in stream. Out of bounds.\");\n        this.position = pos;\n        this.bitPos = 0;\n    }\n\n    /**\n     * Move backwards through the stream by the specified number of bytes.\n     *\n     * @param {number} numBytes\n     */\n    moveBackwardsBy(numBytes) {\n        const pos = this.position - numBytes;\n        if (pos < 0 || pos > this.length)\n            throw new Error(\"Cannot move to position \" + pos + \" in stream. Out of bounds.\");\n        this.position = pos;\n        this.bitPos = 0;\n    }\n\n    /**\n     * Move backwards through the strem by the specified number of bits.\n     *\n     * @param {number} numBits\n     */\n    moveBackwardsByBits(numBits) {\n        if (numBits <= this.bitPos) {\n            this.bitPos -= numBits;\n        } else {\n            if (this.bitPos > 0) {\n                numBits -= this.bitPos;\n                this.bitPos = 0;\n            }\n\n            while (numBits > 0) {\n                this.moveBackwardsBy(1);\n                this.bitPos = 8;\n                this.moveBackwardsByBits(numBits);\n                numBits -= 8;\n            }\n        }\n    }\n\n    /**\n     * Move to a specified position in the stream.\n     *\n     * @param {number} pos\n     */\n    moveTo(pos) {\n        if (pos < 0 || pos > this.length)\n            throw new Error(\"Cannot move to position \" + pos + \" in stream. Out of bounds.\");\n        this.position = pos;\n        this.bitPos = 0;\n    }\n\n    /**\n     * Returns true if there are more bytes left in the stream.\n     *\n     * @returns {boolean}\n     */\n    hasMore() {\n        return this.position < this.length;\n    }\n\n    /**\n     * Returns a slice of the stream up to the current position.\n     *\n     * @param {number} [start=0]\n     * @param {number} [finish=this.position]\n     * @returns {Uint8Array}\n     */\n    carve(start=0, finish=this.position) {\n        if (this.bitPos > 0) finish++;\n        return this.bytes.slice(start, finish);\n    }\n\n}\n"
  },
  {
    "path": "src/core/lib/TLS.mjs",
    "content": "/**\n * TLS resources.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\n\n/**\n * Parse a TLS Record\n * @param {Uint8Array} bytes\n * @returns {JSON}\n */\nexport function parseTLSRecord(bytes) {\n    const s = new Stream(bytes);\n    const b = s.clone();\n    const r = {};\n\n    // Content type\n    r.contentType = {\n        description: \"Content Type\",\n        length: 1,\n        data: b.getBytes(1),\n        value: s.readInt(1)\n    };\n    if (r.contentType.value !== 0x16)\n        throw new OperationError(\"Not handshake data.\");\n\n    // Version\n    r.version = {\n        description: \"Protocol Version\",\n        length: 2,\n        data: b.getBytes(2),\n        value: s.readInt(2)\n    };\n\n    // Length\n    r.length = {\n        description: \"Record Length\",\n        length: 2,\n        data: b.getBytes(2),\n        value: s.readInt(2)\n    };\n    if (s.length !== r.length.value + 5)\n        throw new OperationError(\"Incorrect handshake length.\");\n\n    // Handshake\n    r.handshake = {\n        description: \"Handshake\",\n        length: r.length.value,\n        data: b.getBytes(r.length.value),\n        value: parseHandshake(s.getBytes(r.length.value))\n    };\n\n    return r;\n}\n\n/**\n * Parse a TLS Handshake\n * @param {Uint8Array} bytes\n * @returns {JSON}\n */\nfunction parseHandshake(bytes) {\n    const s = new Stream(bytes);\n    const b = s.clone();\n    const h = {};\n\n    // Handshake type\n    h.handshakeType = {\n        description: \"Handshake Type\",\n        length: 1,\n        data: b.getBytes(1),\n        value: s.readInt(1)\n    };\n\n    // Handshake length\n    h.handshakeLength = {\n        description: \"Handshake Length\",\n        length: 3,\n        data: b.getBytes(3),\n        value: s.readInt(3)\n    };\n    if (s.length !== h.handshakeLength.value + 4)\n        throw new OperationError(\"Not enough data in Handshake message.\");\n\n\n    switch (h.handshakeType.value) {\n        case 0x01:\n            h.handshakeType.description = \"Client Hello\";\n            parseClientHello(s, b, h);\n            break;\n        case 0x02:\n            h.handshakeType.description = \"Server Hello\";\n            parseServerHello(s, b, h);\n            break;\n        default:\n            throw new OperationError(\"Not a known handshake message.\");\n    }\n\n    return h;\n}\n\n/**\n * Parse a TLS Client Hello\n * @param {Stream} s\n * @param {Stream} b\n * @param {Object} h\n * @returns {JSON}\n */\nfunction parseClientHello(s, b, h) {\n    // Hello version\n    h.helloVersion = {\n        description: \"Client Hello Version\",\n        length: 2,\n        data: b.getBytes(2),\n        value: s.readInt(2)\n    };\n\n    // Random\n    h.random = {\n        description: \"Client Random\",\n        length: 32,\n        data: b.getBytes(32),\n        value: s.getBytes(32)\n    };\n\n    // Session ID Length\n    h.sessionIDLength = {\n        description: \"Session ID Length\",\n        length: 1,\n        data: b.getBytes(1),\n        value: s.readInt(1)\n    };\n\n    // Session ID\n    h.sessionID = {\n        description: \"Session ID\",\n        length: h.sessionIDLength.value,\n        data: b.getBytes(h.sessionIDLength.value),\n        value: s.getBytes(h.sessionIDLength.value)\n    };\n\n    // Cipher Suites Length\n    h.cipherSuitesLength = {\n        description: \"Cipher Suites Length\",\n        length: 2,\n        data: b.getBytes(2),\n        value: s.readInt(2)\n    };\n\n    // Cipher Suites\n    h.cipherSuites = {\n        description: \"Cipher Suites\",\n        length: h.cipherSuitesLength.value,\n        data: b.getBytes(h.cipherSuitesLength.value),\n        value: parseCipherSuites(s.getBytes(h.cipherSuitesLength.value))\n    };\n\n    // Compression Methods Length\n    h.compressionMethodsLength = {\n        description: \"Compression Methods Length\",\n        length: 1,\n        data: b.getBytes(1),\n        value: s.readInt(1)\n    };\n\n    // Compression Methods\n    h.compressionMethods = {\n        description: \"Compression Methods\",\n        length: h.compressionMethodsLength.value,\n        data: b.getBytes(h.compressionMethodsLength.value),\n        value: parseCompressionMethods(s.getBytes(h.compressionMethodsLength.value))\n    };\n\n    // Extensions Length\n    h.extensionsLength = {\n        description: \"Extensions Length\",\n        length: 2,\n        data: b.getBytes(2),\n        value: s.readInt(2)\n    };\n\n    // Extensions\n    h.extensions = {\n        description: \"Extensions\",\n        length: h.extensionsLength.value,\n        data: b.getBytes(h.extensionsLength.value),\n        value: parseExtensions(s.getBytes(h.extensionsLength.value))\n    };\n\n    return h;\n}\n\n/**\n * Parse a TLS Server Hello\n * @param {Stream} s\n * @param {Stream} b\n * @param {Object} h\n * @returns {JSON}\n */\nfunction parseServerHello(s, b, h) {\n    // Hello version\n    h.helloVersion = {\n        description: \"Server Hello Version\",\n        length: 2,\n        data: b.getBytes(2),\n        value: s.readInt(2)\n    };\n\n    // Random\n    h.random = {\n        description: \"Server Random\",\n        length: 32,\n        data: b.getBytes(32),\n        value: s.getBytes(32)\n    };\n\n    // Session ID Length\n    h.sessionIDLength = {\n        description: \"Session ID Length\",\n        length: 1,\n        data: b.getBytes(1),\n        value: s.readInt(1)\n    };\n\n    // Session ID\n    h.sessionID = {\n        description: \"Session ID\",\n        length: h.sessionIDLength.value,\n        data: b.getBytes(h.sessionIDLength.value),\n        value: s.getBytes(h.sessionIDLength.value)\n    };\n\n    // Cipher Suite\n    h.cipherSuite = {\n        description: \"Selected Cipher Suite\",\n        length: 2,\n        data: b.getBytes(2),\n        value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || \"Unknown\"\n    };\n\n    // Compression Method\n    h.compressionMethod = {\n        description: \"Selected Compression Method\",\n        length: 1,\n        data: b.getBytes(1),\n        value: s.readInt(1) // TODO: Compression method name here\n    };\n\n    // Extensions Length\n    h.extensionsLength = {\n        description: \"Extensions Length\",\n        length: 2,\n        data: b.getBytes(2),\n        value: s.readInt(2)\n    };\n\n    // Extensions\n    h.extensions = {\n        description: \"Extensions\",\n        length: h.extensionsLength.value,\n        data: b.getBytes(h.extensionsLength.value),\n        value: parseExtensions(s.getBytes(h.extensionsLength.value))\n    };\n}\n\n/**\n * Parse Cipher Suites\n * @param {Uint8Array} bytes\n * @returns {JSON}\n */\nfunction parseCipherSuites(bytes) {\n    const s = new Stream(bytes);\n    const b = s.clone();\n    const cs = [];\n\n    while (s.hasMore()) {\n        cs.push({\n            description: \"Cipher Suite\",\n            length: 2,\n            data: b.getBytes(2),\n            value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || \"Unknown\"\n        });\n    }\n    return cs;\n}\n\n/**\n * Parse Compression Methods\n * @param {Uint8Array} bytes\n * @returns {JSON}\n */\nfunction parseCompressionMethods(bytes) {\n    const s = new Stream(bytes);\n    const b = s.clone();\n    const cm = [];\n\n    while (s.hasMore()) {\n        cm.push({\n            description: \"Compression Method\",\n            length: 1,\n            data: b.getBytes(1),\n            value: s.readInt(1) // TODO: Compression method name here\n        });\n    }\n    return cm;\n}\n\n/**\n * Parse Extensions\n * @param {Uint8Array} bytes\n * @returns {JSON}\n */\nfunction parseExtensions(bytes) {\n    const s = new Stream(bytes);\n    const b = s.clone();\n\n    const exts = [];\n    while (s.hasMore()) {\n        const ext = {};\n\n        // Type\n        ext.type = {\n            description: \"Extension Type\",\n            length: 2,\n            data: b.getBytes(2),\n            value: EXTENSION_LOOKUP[s.readInt(2)] || \"unknown\"\n        };\n\n        // Length\n        ext.length = {\n            description: \"Extension Length\",\n            length: 2,\n            data: b.getBytes(2),\n            value: s.readInt(2)\n        };\n\n        // Value\n        ext.value = {\n            description: \"Extension Value\",\n            length: ext.length.value,\n            data: b.getBytes(ext.length.value),\n            value: s.getBytes(ext.length.value)\n        };\n\n        exts.push(ext);\n    }\n\n    return exts;\n}\n\n/**\n * Extension type lookup table\n */\nconst EXTENSION_LOOKUP = {\n    0: \"server_name\",\n    1: \"max_fragment_length\",\n    2: \"client_certificate_url\",\n    3: \"trusted_ca_keys\",\n    4: \"truncated_hmac\",\n    5: \"status_request\",\n    6: \"user_mapping\",\n    7: \"client_authz\",\n    8: \"server_authz\",\n    9: \"cert_type\",\n    10: \"supported_groups\",\n    11: \"ec_point_formats\",\n    12: \"srp\",\n    13: \"signature_algorithms\",\n    14: \"use_srtp\",\n    15: \"heartbeat\",\n    16: \"application_layer_protocol_negotiation\",\n    17: \"status_request_v2\",\n    18: \"signed_certificate_timestamp\",\n    19: \"client_certificate_type\",\n    20: \"server_certificate_type\",\n    21: \"padding\",\n    22: \"encrypt_then_mac\",\n    23: \"extended_master_secret\",\n    24: \"token_binding\",\n    25: \"cached_info\",\n    26: \"tls_lts\",\n    27: \"compress_certificate\",\n    28: \"record_size_limit\",\n    29: \"pwd_protect\",\n    30: \"pwd_clear\",\n    31: \"password_salt\",\n    32: \"ticket_pinning\",\n    33: \"tls_cert_with_extern_psk\",\n    34: \"delegated_credential\",\n    35: \"session_ticket\",\n    36: \"TLMSP\",\n    37: \"TLMSP_proxying\",\n    38: \"TLMSP_delegate\",\n    39: \"supported_ekt_ciphers\",\n    40: \"Reserved\",\n    41: \"pre_shared_key\",\n    42: \"early_data\",\n    43: \"supported_versions\",\n    44: \"cookie\",\n    45: \"psk_key_exchange_modes\",\n    46: \"Reserved\",\n    47: \"certificate_authorities\",\n    48: \"oid_filters\",\n    49: \"post_handshake_auth\",\n    50: \"signature_algorithms_cert\",\n    51: \"key_share\",\n    52: \"transparency_info\",\n    53: \"connection_id (deprecated)\",\n    54: \"connection_id\",\n    55: \"external_id_hash\",\n    56: \"external_session_id\",\n    57: \"quic_transport_parameters\",\n    58: \"ticket_request\",\n    59: \"dnssec_chain\",\n    60: \"sequence_number_encryption_algorithms\",\n    61: \"rrc\",\n    2570: \"GREASE\",\n    6682: \"GREASE\",\n    10794: \"GREASE\",\n    14906: \"GREASE\",\n    17513: \"application_settings\",\n    19018: \"GREASE\",\n    23130: \"GREASE\",\n    27242: \"GREASE\",\n    31354: \"GREASE\",\n    35466: \"GREASE\",\n    39578: \"GREASE\",\n    43690: \"GREASE\",\n    47802: \"GREASE\",\n    51914: \"GREASE\",\n    56026: \"GREASE\",\n    60138: \"GREASE\",\n    64250: \"GREASE\",\n    64768: \"ech_outer_extensions\",\n    65037: \"encrypted_client_hello\",\n    65281: \"renegotiation_info\"\n};\n\n/**\n * Cipher suites lookup table\n */\nconst CIPHER_SUITES_LOOKUP = {\n    0x0000: \"TLS_NULL_WITH_NULL_NULL\",\n    0x0001: \"TLS_RSA_WITH_NULL_MD5\",\n    0x0002: \"TLS_RSA_WITH_NULL_SHA\",\n    0x0003: \"TLS_RSA_EXPORT_WITH_RC4_40_MD5\",\n    0x0004: \"TLS_RSA_WITH_RC4_128_MD5\",\n    0x0005: \"TLS_RSA_WITH_RC4_128_SHA\",\n    0x0006: \"TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5\",\n    0x0007: \"TLS_RSA_WITH_IDEA_CBC_SHA\",\n    0x0008: \"TLS_RSA_EXPORT_WITH_DES40_CBC_SHA\",\n    0x0009: \"TLS_RSA_WITH_DES_CBC_SHA\",\n    0x000A: \"TLS_RSA_WITH_3DES_EDE_CBC_SHA\",\n    0x000B: \"TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA\",\n    0x000C: \"TLS_DH_DSS_WITH_DES_CBC_SHA\",\n    0x000D: \"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA\",\n    0x000E: \"TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA\",\n    0x000F: \"TLS_DH_RSA_WITH_DES_CBC_SHA\",\n    0x0010: \"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA\",\n    0x0011: \"TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA\",\n    0x0012: \"TLS_DHE_DSS_WITH_DES_CBC_SHA\",\n    0x0013: \"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA\",\n    0x0014: \"TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA\",\n    0x0015: \"TLS_DHE_RSA_WITH_DES_CBC_SHA\",\n    0x0016: \"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA\",\n    0x0017: \"TLS_DH_anon_EXPORT_WITH_RC4_40_MD5\",\n    0x0018: \"TLS_DH_anon_WITH_RC4_128_MD5\",\n    0x0019: \"TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA\",\n    0x001A: \"TLS_DH_anon_WITH_DES_CBC_SHA\",\n    0x001B: \"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA\",\n    0x001E: \"TLS_KRB5_WITH_DES_CBC_SHA\",\n    0x001F: \"TLS_KRB5_WITH_3DES_EDE_CBC_SHA\",\n    0x0020: \"TLS_KRB5_WITH_RC4_128_SHA\",\n    0x0021: \"TLS_KRB5_WITH_IDEA_CBC_SHA\",\n    0x0022: \"TLS_KRB5_WITH_DES_CBC_MD5\",\n    0x0023: \"TLS_KRB5_WITH_3DES_EDE_CBC_MD5\",\n    0x0024: \"TLS_KRB5_WITH_RC4_128_MD5\",\n    0x0025: \"TLS_KRB5_WITH_IDEA_CBC_MD5\",\n    0x0026: \"TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA\",\n    0x0027: \"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA\",\n    0x0028: \"TLS_KRB5_EXPORT_WITH_RC4_40_SHA\",\n    0x0029: \"TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5\",\n    0x002A: \"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5\",\n    0x002B: \"TLS_KRB5_EXPORT_WITH_RC4_40_MD5\",\n    0x002C: \"TLS_PSK_WITH_NULL_SHA\",\n    0x002D: \"TLS_DHE_PSK_WITH_NULL_SHA\",\n    0x002E: \"TLS_RSA_PSK_WITH_NULL_SHA\",\n    0x002F: \"TLS_RSA_WITH_AES_128_CBC_SHA\",\n    0x0030: \"TLS_DH_DSS_WITH_AES_128_CBC_SHA\",\n    0x0031: \"TLS_DH_RSA_WITH_AES_128_CBC_SHA\",\n    0x0032: \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA\",\n    0x0033: \"TLS_DHE_RSA_WITH_AES_128_CBC_SHA\",\n    0x0034: \"TLS_DH_anon_WITH_AES_128_CBC_SHA\",\n    0x0035: \"TLS_RSA_WITH_AES_256_CBC_SHA\",\n    0x0036: \"TLS_DH_DSS_WITH_AES_256_CBC_SHA\",\n    0x0037: \"TLS_DH_RSA_WITH_AES_256_CBC_SHA\",\n    0x0038: \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA\",\n    0x0039: \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA\",\n    0x003A: \"TLS_DH_anon_WITH_AES_256_CBC_SHA\",\n    0x003B: \"TLS_RSA_WITH_NULL_SHA256\",\n    0x003C: \"TLS_RSA_WITH_AES_128_CBC_SHA256\",\n    0x003D: \"TLS_RSA_WITH_AES_256_CBC_SHA256\",\n    0x003E: \"TLS_DH_DSS_WITH_AES_128_CBC_SHA256\",\n    0x003F: \"TLS_DH_RSA_WITH_AES_128_CBC_SHA256\",\n    0x0040: \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256\",\n    0x0041: \"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA\",\n    0x0042: \"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA\",\n    0x0043: \"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA\",\n    0x0044: \"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA\",\n    0x0045: \"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA\",\n    0x0046: \"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA\",\n    0x0067: \"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256\",\n    0x0068: \"TLS_DH_DSS_WITH_AES_256_CBC_SHA256\",\n    0x0069: \"TLS_DH_RSA_WITH_AES_256_CBC_SHA256\",\n    0x006A: \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256\",\n    0x006B: \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256\",\n    0x006C: \"TLS_DH_anon_WITH_AES_128_CBC_SHA256\",\n    0x006D: \"TLS_DH_anon_WITH_AES_256_CBC_SHA256\",\n    0x0084: \"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA\",\n    0x0085: \"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA\",\n    0x0086: \"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA\",\n    0x0087: \"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA\",\n    0x0088: \"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA\",\n    0x0089: \"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA\",\n    0x008A: \"TLS_PSK_WITH_RC4_128_SHA\",\n    0x008B: \"TLS_PSK_WITH_3DES_EDE_CBC_SHA\",\n    0x008C: \"TLS_PSK_WITH_AES_128_CBC_SHA\",\n    0x008D: \"TLS_PSK_WITH_AES_256_CBC_SHA\",\n    0x008E: \"TLS_DHE_PSK_WITH_RC4_128_SHA\",\n    0x008F: \"TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA\",\n    0x0090: \"TLS_DHE_PSK_WITH_AES_128_CBC_SHA\",\n    0x0091: \"TLS_DHE_PSK_WITH_AES_256_CBC_SHA\",\n    0x0092: \"TLS_RSA_PSK_WITH_RC4_128_SHA\",\n    0x0093: \"TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA\",\n    0x0094: \"TLS_RSA_PSK_WITH_AES_128_CBC_SHA\",\n    0x0095: \"TLS_RSA_PSK_WITH_AES_256_CBC_SHA\",\n    0x0096: \"TLS_RSA_WITH_SEED_CBC_SHA\",\n    0x0097: \"TLS_DH_DSS_WITH_SEED_CBC_SHA\",\n    0x0098: \"TLS_DH_RSA_WITH_SEED_CBC_SHA\",\n    0x0099: \"TLS_DHE_DSS_WITH_SEED_CBC_SHA\",\n    0x009A: \"TLS_DHE_RSA_WITH_SEED_CBC_SHA\",\n    0x009B: \"TLS_DH_anon_WITH_SEED_CBC_SHA\",\n    0x009C: \"TLS_RSA_WITH_AES_128_GCM_SHA256\",\n    0x009D: \"TLS_RSA_WITH_AES_256_GCM_SHA384\",\n    0x009E: \"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256\",\n    0x009F: \"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384\",\n    0x00A0: \"TLS_DH_RSA_WITH_AES_128_GCM_SHA256\",\n    0x00A1: \"TLS_DH_RSA_WITH_AES_256_GCM_SHA384\",\n    0x00A2: \"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256\",\n    0x00A3: \"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384\",\n    0x00A4: \"TLS_DH_DSS_WITH_AES_128_GCM_SHA256\",\n    0x00A5: \"TLS_DH_DSS_WITH_AES_256_GCM_SHA384\",\n    0x00A6: \"TLS_DH_anon_WITH_AES_128_GCM_SHA256\",\n    0x00A7: \"TLS_DH_anon_WITH_AES_256_GCM_SHA384\",\n    0x00A8: \"TLS_PSK_WITH_AES_128_GCM_SHA256\",\n    0x00A9: \"TLS_PSK_WITH_AES_256_GCM_SHA384\",\n    0x00AA: \"TLS_DHE_PSK_WITH_AES_128_GCM_SHA256\",\n    0x00AB: \"TLS_DHE_PSK_WITH_AES_256_GCM_SHA384\",\n    0x00AC: \"TLS_RSA_PSK_WITH_AES_128_GCM_SHA256\",\n    0x00AD: \"TLS_RSA_PSK_WITH_AES_256_GCM_SHA384\",\n    0x00AE: \"TLS_PSK_WITH_AES_128_CBC_SHA256\",\n    0x00AF: \"TLS_PSK_WITH_AES_256_CBC_SHA384\",\n    0x00B0: \"TLS_PSK_WITH_NULL_SHA256\",\n    0x00B1: \"TLS_PSK_WITH_NULL_SHA384\",\n    0x00B2: \"TLS_DHE_PSK_WITH_AES_128_CBC_SHA256\",\n    0x00B3: \"TLS_DHE_PSK_WITH_AES_256_CBC_SHA384\",\n    0x00B4: \"TLS_DHE_PSK_WITH_NULL_SHA256\",\n    0x00B5: \"TLS_DHE_PSK_WITH_NULL_SHA384\",\n    0x00B6: \"TLS_RSA_PSK_WITH_AES_128_CBC_SHA256\",\n    0x00B7: \"TLS_RSA_PSK_WITH_AES_256_CBC_SHA384\",\n    0x00B8: \"TLS_RSA_PSK_WITH_NULL_SHA256\",\n    0x00B9: \"TLS_RSA_PSK_WITH_NULL_SHA384\",\n    0x00BA: \"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256\",\n    0x00BB: \"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256\",\n    0x00BC: \"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256\",\n    0x00BD: \"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256\",\n    0x00BE: \"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256\",\n    0x00BF: \"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256\",\n    0x00C0: \"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256\",\n    0x00C1: \"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256\",\n    0x00C2: \"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256\",\n    0x00C3: \"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256\",\n    0x00C4: \"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256\",\n    0x00C5: \"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256\",\n    0x00C6: \"TLS_SM4_GCM_SM3\",\n    0x00C7: \"TLS_SM4_CCM_SM3\",\n    0x00FF: \"TLS_EMPTY_RENEGOTIATION_INFO_SCSV\",\n    0x0A0A: \"GREASE\",\n    0x1301: \"TLS_AES_128_GCM_SHA256\",\n    0x1302: \"TLS_AES_256_GCM_SHA384\",\n    0x1303: \"TLS_CHACHA20_POLY1305_SHA256\",\n    0x1304: \"TLS_AES_128_CCM_SHA256\",\n    0x1305: \"TLS_AES_128_CCM_8_SHA256\",\n    0x1306: \"TLS_AEGIS_256_SHA512\",\n    0x1307: \"TLS_AEGIS_128L_SHA256\",\n    0x1A1A: \"GREASE\",\n    0x2A2A: \"GREASE\",\n    0x3A3A: \"GREASE\",\n    0x4A4A: \"GREASE\",\n    0x5600: \"TLS_FALLBACK_SCSV\",\n    0x5A5A: \"GREASE\",\n    0x6A6A: \"GREASE\",\n    0x7A7A: \"GREASE\",\n    0x8A8A: \"GREASE\",\n    0x9A9A: \"GREASE\",\n    0xAAAA: \"GREASE\",\n    0xBABA: \"GREASE\",\n    0xC001: \"TLS_ECDH_ECDSA_WITH_NULL_SHA\",\n    0xC002: \"TLS_ECDH_ECDSA_WITH_RC4_128_SHA\",\n    0xC003: \"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA\",\n    0xC004: \"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA\",\n    0xC005: \"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA\",\n    0xC006: \"TLS_ECDHE_ECDSA_WITH_NULL_SHA\",\n    0xC007: \"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA\",\n    0xC008: \"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA\",\n    0xC009: \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA\",\n    0xC00A: \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA\",\n    0xC00B: \"TLS_ECDH_RSA_WITH_NULL_SHA\",\n    0xC00C: \"TLS_ECDH_RSA_WITH_RC4_128_SHA\",\n    0xC00D: \"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA\",\n    0xC00E: \"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA\",\n    0xC00F: \"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA\",\n    0xC010: \"TLS_ECDHE_RSA_WITH_NULL_SHA\",\n    0xC011: \"TLS_ECDHE_RSA_WITH_RC4_128_SHA\",\n    0xC012: \"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA\",\n    0xC013: \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA\",\n    0xC014: \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA\",\n    0xC015: \"TLS_ECDH_anon_WITH_NULL_SHA\",\n    0xC016: \"TLS_ECDH_anon_WITH_RC4_128_SHA\",\n    0xC017: \"TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA\",\n    0xC018: \"TLS_ECDH_anon_WITH_AES_128_CBC_SHA\",\n    0xC019: \"TLS_ECDH_anon_WITH_AES_256_CBC_SHA\",\n    0xC01A: \"TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA\",\n    0xC01B: \"TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA\",\n    0xC01C: \"TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA\",\n    0xC01D: \"TLS_SRP_SHA_WITH_AES_128_CBC_SHA\",\n    0xC01E: \"TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA\",\n    0xC01F: \"TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA\",\n    0xC020: \"TLS_SRP_SHA_WITH_AES_256_CBC_SHA\",\n    0xC021: \"TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA\",\n    0xC022: \"TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA\",\n    0xC023: \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256\",\n    0xC024: \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384\",\n    0xC025: \"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256\",\n    0xC026: \"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384\",\n    0xC027: \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256\",\n    0xC028: \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384\",\n    0xC029: \"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256\",\n    0xC02A: \"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384\",\n    0xC02B: \"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\",\n    0xC02C: \"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384\",\n    0xC02D: \"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256\",\n    0xC02E: \"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384\",\n    0xC02F: \"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\",\n    0xC030: \"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\",\n    0xC031: \"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256\",\n    0xC032: \"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384\",\n    0xC033: \"TLS_ECDHE_PSK_WITH_RC4_128_SHA\",\n    0xC034: \"TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA\",\n    0xC035: \"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA\",\n    0xC036: \"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA\",\n    0xC037: \"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256\",\n    0xC038: \"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384\",\n    0xC039: \"TLS_ECDHE_PSK_WITH_NULL_SHA\",\n    0xC03A: \"TLS_ECDHE_PSK_WITH_NULL_SHA256\",\n    0xC03B: \"TLS_ECDHE_PSK_WITH_NULL_SHA384\",\n    0xC03C: \"TLS_RSA_WITH_ARIA_128_CBC_SHA256\",\n    0xC03D: \"TLS_RSA_WITH_ARIA_256_CBC_SHA384\",\n    0xC03E: \"TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256\",\n    0xC03F: \"TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384\",\n    0xC040: \"TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256\",\n    0xC041: \"TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384\",\n    0xC042: \"TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256\",\n    0xC043: \"TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384\",\n    0xC044: \"TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256\",\n    0xC045: \"TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384\",\n    0xC046: \"TLS_DH_anon_WITH_ARIA_128_CBC_SHA256\",\n    0xC047: \"TLS_DH_anon_WITH_ARIA_256_CBC_SHA384\",\n    0xC048: \"TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256\",\n    0xC049: \"TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384\",\n    0xC04A: \"TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256\",\n    0xC04B: \"TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384\",\n    0xC04C: \"TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256\",\n    0xC04D: \"TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384\",\n    0xC04E: \"TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256\",\n    0xC04F: \"TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384\",\n    0xC050: \"TLS_RSA_WITH_ARIA_128_GCM_SHA256\",\n    0xC051: \"TLS_RSA_WITH_ARIA_256_GCM_SHA384\",\n    0xC052: \"TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256\",\n    0xC053: \"TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384\",\n    0xC054: \"TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256\",\n    0xC055: \"TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384\",\n    0xC056: \"TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256\",\n    0xC057: \"TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384\",\n    0xC058: \"TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256\",\n    0xC059: \"TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384\",\n    0xC05A: \"TLS_DH_anon_WITH_ARIA_128_GCM_SHA256\",\n    0xC05B: \"TLS_DH_anon_WITH_ARIA_256_GCM_SHA384\",\n    0xC05C: \"TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256\",\n    0xC05D: \"TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384\",\n    0xC05E: \"TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256\",\n    0xC05F: \"TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384\",\n    0xC060: \"TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256\",\n    0xC061: \"TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384\",\n    0xC062: \"TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256\",\n    0xC063: \"TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384\",\n    0xC064: \"TLS_PSK_WITH_ARIA_128_CBC_SHA256\",\n    0xC065: \"TLS_PSK_WITH_ARIA_256_CBC_SHA384\",\n    0xC066: \"TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256\",\n    0xC067: \"TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384\",\n    0xC068: \"TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256\",\n    0xC069: \"TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384\",\n    0xC06A: \"TLS_PSK_WITH_ARIA_128_GCM_SHA256\",\n    0xC06B: \"TLS_PSK_WITH_ARIA_256_GCM_SHA384\",\n    0xC06C: \"TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256\",\n    0xC06D: \"TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384\",\n    0xC06E: \"TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256\",\n    0xC06F: \"TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384\",\n    0xC070: \"TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256\",\n    0xC071: \"TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384\",\n    0xC072: \"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC073: \"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC074: \"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC075: \"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC076: \"TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC077: \"TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC078: \"TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC079: \"TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC07A: \"TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC07B: \"TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC07C: \"TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC07D: \"TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC07E: \"TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC07F: \"TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC080: \"TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC081: \"TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC082: \"TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC083: \"TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC084: \"TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC085: \"TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC086: \"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC087: \"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC088: \"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC089: \"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC08A: \"TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC08B: \"TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC08C: \"TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC08D: \"TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC08E: \"TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC08F: \"TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC090: \"TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC091: \"TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC092: \"TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256\",\n    0xC093: \"TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384\",\n    0xC094: \"TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC095: \"TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC096: \"TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC097: \"TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC098: \"TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC099: \"TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC09A: \"TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256\",\n    0xC09B: \"TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384\",\n    0xC09C: \"TLS_RSA_WITH_AES_128_CCM\",\n    0xC09D: \"TLS_RSA_WITH_AES_256_CCM\",\n    0xC09E: \"TLS_DHE_RSA_WITH_AES_128_CCM\",\n    0xC09F: \"TLS_DHE_RSA_WITH_AES_256_CCM\",\n    0xC0A0: \"TLS_RSA_WITH_AES_128_CCM_8\",\n    0xC0A1: \"TLS_RSA_WITH_AES_256_CCM_8\",\n    0xC0A2: \"TLS_DHE_RSA_WITH_AES_128_CCM_8\",\n    0xC0A3: \"TLS_DHE_RSA_WITH_AES_256_CCM_8\",\n    0xC0A4: \"TLS_PSK_WITH_AES_128_CCM\",\n    0xC0A5: \"TLS_PSK_WITH_AES_256_CCM\",\n    0xC0A6: \"TLS_DHE_PSK_WITH_AES_128_CCM\",\n    0xC0A7: \"TLS_DHE_PSK_WITH_AES_256_CCM\",\n    0xC0A8: \"TLS_PSK_WITH_AES_128_CCM_8\",\n    0xC0A9: \"TLS_PSK_WITH_AES_256_CCM_8\",\n    0xC0AA: \"TLS_PSK_DHE_WITH_AES_128_CCM_8\",\n    0xC0AB: \"TLS_PSK_DHE_WITH_AES_256_CCM_8\",\n    0xC0AC: \"TLS_ECDHE_ECDSA_WITH_AES_128_CCM\",\n    0xC0AD: \"TLS_ECDHE_ECDSA_WITH_AES_256_CCM\",\n    0xC0AE: \"TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8\",\n    0xC0AF: \"TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8\",\n    0xC0B0: \"TLS_ECCPWD_WITH_AES_128_GCM_SHA256\",\n    0xC0B1: \"TLS_ECCPWD_WITH_AES_256_GCM_SHA384\",\n    0xC0B2: \"TLS_ECCPWD_WITH_AES_128_CCM_SHA256\",\n    0xC0B3: \"TLS_ECCPWD_WITH_AES_256_CCM_SHA384\",\n    0xC0B4: \"TLS_SHA256_SHA256\",\n    0xC0B5: \"TLS_SHA384_SHA384\",\n    0xC100: \"TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC\",\n    0xC101: \"TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC\",\n    0xC102: \"TLS_GOSTR341112_256_WITH_28147_CNT_IMIT\",\n    0xC103: \"TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L\",\n    0xC104: \"TLS_GOSTR341112_256_WITH_MAGMA_MGM_L\",\n    0xC105: \"TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S\",\n    0xC106: \"TLS_GOSTR341112_256_WITH_MAGMA_MGM_S\",\n    0xCACA: \"GREASE\",\n    0xCCA8: \"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256\",\n    0xCCA9: \"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256\",\n    0xCCAA: \"TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256\",\n    0xCCAB: \"TLS_PSK_WITH_CHACHA20_POLY1305_SHA256\",\n    0xCCAC: \"TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256\",\n    0xCCAD: \"TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256\",\n    0xCCAE: \"TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256\",\n    0xD001: \"TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256\",\n    0xD002: \"TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384\",\n    0xD003: \"TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256\",\n    0xD005: \"TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256\",\n    0xDADA: \"GREASE\",\n    0xEAEA: \"GREASE\",\n    0xFAFA: \"GREASE\",\n};\n\n/**\n * GREASE values\n */\nexport const GREASE_VALUES = [\n    0x0a0a,\n    0x1a1a,\n    0x2a2a,\n    0x3a3a,\n    0x4a4a,\n    0x5a5a,\n    0x6a6a,\n    0x7a7a,\n    0x8a8a,\n    0x9a9a,\n    0xaaaa,\n    0xbaba,\n    0xcaca,\n    0xdada,\n    0xeaea,\n    0xfafa\n];\n\n/**\n * Parses the supported_versions extension and returns the highest supported version.\n * @param {Uint8Array} bytes\n * @returns {number}\n */\nexport function parseHighestSupportedVersion(bytes) {\n    const s = new Stream(bytes);\n\n    // The Server Hello supported_versions extension simply contains the chosen version\n    if (s.length === 2) {\n        return s.readInt(2);\n    }\n\n    // Length\n    let i = s.readInt(1);\n\n    let highestVersion = 0;\n    while (s.hasMore() && i-- > 0) {\n        const v = s.readInt(2);\n        if (GREASE_VALUES.includes(v)) continue;\n        if (v > highestVersion) highestVersion = v;\n    }\n\n    return highestVersion;\n}\n\n/**\n * Parses the application_layer_protocol_negotiation extension and returns the first value as raw bytes.\n * @param {Uint8Array} bytes\n * @returns {Uint8Array|null}\n */\nexport function parseFirstALPNValue(bytes) {\n    const s = new Stream(bytes);\n    const alpnExtLen = s.readInt(2);\n    if (alpnExtLen < 2) return null;\n    const strLen = s.readInt(1);\n    if (strLen < 1) return null;\n    return s.getBytes(strLen);\n}\n"
  },
  {
    "path": "src/core/lib/TLVParser.mjs",
    "content": "/**\n * Parser for Type-length-value data.\n *\n * @author gchq77703 []\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nconst defaults = {\n    location: 0,\n    bytesInLength: 1,\n    basicEncodingRules: false\n};\n\n/**\n * TLVParser library\n */\nexport default class TLVParser {\n\n    /**\n     * TLVParser constructor\n     *\n     * @param {byteArray|Uint8Array} input\n     * @param {Object} options\n     */\n    constructor(input, options) {\n        this.input = input;\n        Object.assign(this, defaults, options);\n    }\n\n    /**\n     * @returns {number}\n     */\n    getLength() {\n        if (this.basicEncodingRules) {\n            const bit = this.input[this.location];\n            if (bit & 0x80) {\n                this.bytesInLength = bit & ~0x80;\n            } else {\n                this.location++;\n                return bit & ~0x80;\n            }\n        }\n\n        let length = 0;\n\n        for (let i = 0; i < this.bytesInLength; i++) {\n            length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);\n            this.location++;\n        }\n\n        return length;\n    }\n\n    /**\n     * @param {number} length\n     * @returns {number[]}\n     */\n    getValue(length) {\n        const value = [];\n\n        for (let i = 0; i < length; i++) {\n            if (this.location > this.input.length) return value;\n            value.push(this.input[this.location]);\n            this.location++;\n        }\n\n        return value;\n    }\n\n    /**\n     * @returns {boolean}\n     */\n    atEnd() {\n        return this.input.length <= this.location;\n    }\n}\n"
  },
  {
    "path": "src/core/lib/Typex.mjs",
    "content": "/**\n * Emulation of the Typex machine.\n *\n * @author s2224834\n * @author The National Museum of Computing - Bombe Rebuild Project\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport OperationError from \"../errors/OperationError.mjs\";\nimport * as Enigma from \"../lib/Enigma.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * A set of example Typex rotors. No Typex rotor wirings are publicly available, so these are\n * randomised.\n */\nexport const ROTORS = [\n    {name: \"Example 1\", value: \"MCYLPQUVRXGSAOWNBJEZDTFKHI<BFHNQUW\"},\n    {name: \"Example 2\", value: \"KHWENRCBISXJQGOFMAPVYZDLTU<BFHNQUW\"},\n    {name: \"Example 3\", value: \"BYPDZMGIKQCUSATREHOJNLFWXV<BFHNQUW\"},\n    {name: \"Example 4\", value: \"ZANJCGDLVHIXOBRPMSWQUKFYET<BFHNQUW\"},\n    {name: \"Example 5\", value: \"QXBGUTOVFCZPJIHSWERYNDAMLK<BFHNQUW\"},\n    {name: \"Example 6\", value: \"BDCNWUEIQVFTSXALOGZJYMHKPR<BFHNQUW\"},\n    {name: \"Example 7\", value: \"WJUKEIABMSGFTQZVCNPHORDXYL<BFHNQUW\"},\n    {name: \"Example 8\", value: \"TNVCZXDIPFWQKHSJMAOYLEURGB<BFHNQUW\"},\n];\n\n/**\n * An example Typex reflector. Again, randomised.\n */\nexport const REFLECTORS = [\n    {name: \"Example\", value: \"AN BC FG IE KD LU MH OR TS VZ WQ XJ YP\"},\n];\n\n// Special character handling on Typex keyboard\nconst KEYBOARD = {\n    \"Q\": \"1\", \"W\": \"2\", \"E\": \"3\", \"R\": \"4\", \"T\": \"5\", \"Y\": \"6\", \"U\": \"7\", \"I\": \"8\", \"O\": \"9\", \"P\": \"0\",\n    \"A\": \"-\", \"S\": \"/\", \"D\": \"Z\", \"F\": \"%\", \"G\": \"X\", \"H\": \"£\", \"K\": \"(\", \"L\": \")\",\n    \"C\": \"V\", \"B\": \"'\", \"N\": \",\", \"M\": \".\"\n};\nconst KEYBOARD_REV = {};\nfor (const i of Object.keys(KEYBOARD)) {\n    KEYBOARD_REV[KEYBOARD[i]] = i;\n}\n\n/**\n * Typex machine. A lot like the Enigma, but five rotors, of which the first two are static.\n */\nexport class TypexMachine extends Enigma.EnigmaBase {\n    /**\n     * TypexMachine constructor.\n     *\n     * @param {Object[]} rotors - List of Rotors.\n     * @param {Object} reflector - A Reflector.\n     * @param {Plugboard} plugboard - A Plugboard.\n     */\n    constructor(rotors, reflector, plugboard, keyboard) {\n        super(rotors, reflector, plugboard);\n        if (rotors.length !== 5) {\n            throw new OperationError(\"Typex must have 5 rotors\");\n        }\n        this.keyboard = keyboard;\n    }\n\n    /**\n     * This is the same as the Enigma step function, it's just that the right-\n     * most two rotors are static.\n     */\n    step() {\n        const r0 = this.rotors[2];\n        const r1 = this.rotors[3];\n        r0.step();\n        // The second test here is the double-stepping anomaly\n        if (r0.steps.has(r0.pos) || r1.steps.has(Utils.mod(r1.pos + 1, 26))) {\n            r1.step();\n            if (r1.steps.has(r1.pos)) {\n                const r2 = this.rotors[4];\n                r2.step();\n            }\n        }\n    }\n\n    /**\n     * Encrypt/decrypt data. This is identical to the Enigma version cryptographically, but we have\n     * additional handling for the Typex's keyboard (which handles various special characters by\n     * mapping them to particular letter combinations).\n     *\n     * @param {string} input - The data to encrypt/decrypt.\n     * @return {string}\n     */\n    crypt(input) {\n        let inputMod = input;\n        if (this.keyboard === \"Encrypt\") {\n            inputMod = \"\";\n            // true = in symbol mode\n            let mode = false;\n            for (const x of input) {\n                if (x === \" \") {\n                    inputMod += \"X\";\n                } else if (mode) {\n                    if (Object.prototype.hasOwnProperty.call(KEYBOARD_REV, x)) {\n                        inputMod += KEYBOARD_REV[x];\n                    } else {\n                        mode = false;\n                        inputMod += \"V\" + x;\n                    }\n                } else {\n                    if (Object.prototype.hasOwnProperty.call(KEYBOARD_REV, x)) {\n                        mode = true;\n                        inputMod += \"Z\" + KEYBOARD_REV[x];\n                    } else {\n                        inputMod += x;\n                    }\n                }\n            }\n        }\n\n        const output = super.crypt(inputMod);\n\n        let outputMod = output;\n        if (this.keyboard === \"Decrypt\") {\n            outputMod = \"\";\n            let mode = false;\n            for (const x of output) {\n                if (x === \"X\") {\n                    outputMod += \" \";\n                } else if (x === \"V\") {\n                    mode = false;\n                } else if (x === \"Z\") {\n                    mode = true;\n                } else if (mode) {\n                    outputMod += KEYBOARD[x];\n                } else {\n                    outputMod += x;\n                }\n            }\n        }\n        return outputMod;\n    }\n}\n\n/**\n * Typex rotor. Like an Enigma rotor, but no ring setting, and can be reversed.\n */\nexport class Rotor extends Enigma.Rotor {\n    /**\n     * Rotor constructor.\n     *\n     * @param {string} wiring - A 26 character string of the wiring order.\n     * @param {string} steps - A 0..26 character string of stepping points.\n     * @param {bool} reversed - Whether to reverse the rotor.\n     * @param {char} ringSetting - Ring setting of the rotor.\n     * @param {char} initialPosition - The initial position of the rotor.\n     */\n    constructor(wiring, steps, reversed, ringSetting, initialPos) {\n        let wiringMod = wiring;\n        if (reversed) {\n            const outMap = new Array(26);\n            for (let i=0; i<26; i++) {\n                // wiring[i] is the original output\n                // Enigma.LETTERS[i] is the original input\n                const input = Utils.mod(26 - Enigma.a2i(wiring[i]), 26);\n                const output = Enigma.i2a(Utils.mod(26 - Enigma.a2i(Enigma.LETTERS[i]), 26));\n                outMap[input] = output;\n            }\n            wiringMod = outMap.join(\"\");\n        }\n        super(wiringMod, steps, ringSetting, initialPos);\n    }\n}\n\n/**\n * Typex input plugboard. Based on a Rotor, because it allows arbitrary maps, not just switches\n * like the Enigma plugboard.\n * Not to be confused with the reflector plugboard.\n * This is also where the Typex's backwards input wiring is implemented - it's a bit of a hack, but\n * it means everything else continues to work like in the Enigma.\n */\nexport class Plugboard extends Enigma.Rotor {\n    /**\n     * Typex plugboard constructor.\n     *\n     * @param {string} wiring - 26 character string of mappings from A-Z, as per rotors, or \"\".\n     */\n    constructor(wiring) {\n        // Typex input wiring is backwards vs Enigma: that is, letters enter the rotors in a\n        // clockwise order, vs. Enigma's anticlockwise (or vice versa depending on which side\n        // you're looking at it from). I'm doing the transform here to avoid having to rewrite\n        // the Engima crypt() method in Typex as well.\n        // Note that the wiring for the reflector is the same way around as Enigma, so no\n        // transformation is necessary on that side.\n        // We're going to achieve this by mapping the plugboard settings through an additional\n        // transform that mirrors the alphabet before we pass it to the superclass.\n        if (!/^[A-Z]{26}$/.test(wiring)) {\n            throw new OperationError(\"Plugboard wiring must be 26 unique uppercase letters\");\n        }\n        const reversed = \"AZYXWVUTSRQPONMLKJIHGFEDCB\";\n        wiring = wiring.replace(/./g, x => {\n            return reversed[Enigma.a2i(x)];\n        });\n        try {\n            super(wiring, \"\", \"A\", \"A\");\n        } catch (err) {\n            throw new OperationError(err.message.replace(\"Rotor\", \"Plugboard\"));\n        }\n    }\n\n    /**\n     * Transform a character through this rotor forwards.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */\n    transform(c) {\n        return Utils.mod(this.map[Utils.mod(c + this.pos, 26)] - this.pos, 26);\n    }\n\n    /**\n     * Transform a character through this rotor backwards.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */\n    revTransform(c) {\n        return Utils.mod(this.revMap[Utils.mod(c + this.pos, 26)] - this.pos, 26);\n    }\n}\n"
  },
  {
    "path": "src/core/lib/XXTEA.mjs",
    "content": "/**\n * XXTEA library\n *\n * Encryption Algorithm Authors:\n *     David J. Wheeler\n *     Roger M. Needham\n *\n * @author Ma Bingyao [mabingyao@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @license MIT\n */\n\nconst DELTA = 0x9E3779B9;\n\n/**\n * Convert a buffer to a Uint8Array\n * @param {Uint32Array} v\n * @param {boolean} includeLength\n * @returns {Uint8Array}\n */\nfunction toUint8Array(v, includeLength) {\n    const length = v.length;\n    let n = length << 2;\n    if (includeLength) {\n        const m = v[length - 1];\n        n -= 4;\n        if ((m < n - 3) || (m > n)) {\n            return null;\n        }\n        n = m;\n    }\n    const bytes = new Uint8Array(n);\n    for (let i = 0; i < n; i++) {\n        bytes[i] = v[i >> 2] >> ((i & 3) << 3);\n    }\n    return bytes;\n}\n\n/**\n * Convert a buffer to a Uint32Array\n * @param {TypedArray} bs\n * @param {boolean} includeLength\n * @returns {Uint32Array}\n */\nfunction toUint32Array(bs, includeLength) {\n    const length = bs.length;\n    let n = length >> 2;\n    if ((length & 3) !== 0) {\n        ++n;\n    }\n    let v;\n    if (includeLength) {\n        v = new Uint32Array(n + 1);\n        v[n] = length;\n    } else {\n        v = new Uint32Array(n);\n    }\n    for (let i = 0; i < length; ++i) {\n        v[i >> 2] |= bs[i] << ((i & 3) << 3);\n    }\n    return v;\n}\n\n/**\n * Mask an int to 32 bits\n * @param {number} i\n * @returns {number}\n */\nfunction int32(i) {\n    return i & 0xFFFFFFFF;\n}\n\n/**\n * MX function for data randomisation\n * @param {number} sum\n * @param {number} y\n * @param {number} z\n * @param {number} p\n * @param {number} e\n * @param {number} k\n * @returns {number}\n */\nfunction mx(sum, y, z, p, e, k) {\n    return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));\n}\n\n/**\n * Ensure an array is a multiple of 16 bits\n * @param {TypedArray} k\n * @returns {TypedArray}\n */\nfunction fixk(k) {\n    if (k.length < 16) {\n        const key = new Uint8Array(16);\n        key.set(k);\n        return key;\n    }\n    return k;\n}\n\n/**\n * Performs XXTEA encryption on a Uint32Array\n * @param {Uint32Array} v\n * @param {Uint32Array} k\n * @returns {Uint32Array}\n */\nfunction encryptUint32Array(v, k) {\n    const length = v.length;\n    const n = length - 1;\n    let y, z, sum, e, p, q;\n    z = v[n];\n    sum = 0;\n    for (q = Math.floor(6 + 52 / length) | 0; q > 0; --q) {\n        sum = int32(sum + DELTA);\n        e = sum >>> 2 & 3;\n        for (p = 0; p < n; ++p) {\n            y = v[p + 1];\n            z = v[p] = int32(v[p] + mx(sum, y, z, p, e, k));\n        }\n        y = v[0];\n        z = v[n] = int32(v[n] + mx(sum, y, z, n, e, k));\n    }\n    return v;\n}\n\n/**\n * Performs XXTEA decryption on a Uint32Array\n * @param {Uint32Array} v\n * @param {Uint32Array} k\n * @returns {Uint32Array}\n */\nfunction decryptUint32Array(v, k) {\n    const length = v.length;\n    const n = length - 1;\n    let y, z, sum, e, p;\n    y = v[0];\n    const q = Math.floor(6 + 52 / length);\n    for (sum = int32(q * DELTA); sum !== 0; sum = int32(sum - DELTA)) {\n        e = sum >>> 2 & 3;\n        for (p = n; p > 0; --p) {\n            z = v[p - 1];\n            y = v[p] = int32(v[p] - mx(sum, y, z, p, e, k));\n        }\n        z = v[n];\n        y = v[0] = int32(v[0] - mx(sum, y, z, 0, e, k));\n    }\n    return v;\n}\n\n/**\n * Encrypt function\n * @param {TypedArray} data\n * @param {TypedArray} key\n * @returns {Uint8Array}\n */\nexport function encrypt(data, key) {\n    if (data === undefined || data === null || data.length === 0) {\n        return data;\n    }\n    return toUint8Array(encryptUint32Array(toUint32Array(data, true), toUint32Array(fixk(key), false)), false);\n}\n\n/**\n * Decrypt function\n * @param {TypedArray} data\n * @param {TypedArray} key\n * @returns {Uint8Array}\n */\nexport function decrypt(data, key) {\n    if (data === undefined || data === null || data.length === 0) {\n        return data;\n    }\n    return toUint8Array(decryptUint32Array(toUint32Array(data, false), toUint32Array(fixk(key), false)), true);\n}\n"
  },
  {
    "path": "src/core/lib/Zlib.mjs",
    "content": "/**\n * Zlib exports.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport zlibAndGzip from \"zlibjs/bin/zlib_and_gzip.min.js\";\n\nconst Zlib = zlibAndGzip.Zlib;\n\nexport const COMPRESSION_TYPE = [\"Dynamic Huffman Coding\", \"Fixed Huffman Coding\", \"None (Store)\"];\nexport const INFLATE_BUFFER_TYPE = [\"Adaptive\", \"Block\"];\nexport const ZLIB_COMPRESSION_TYPE_LOOKUP = {\n    \"Fixed Huffman Coding\":   Zlib.Deflate.CompressionType.FIXED,\n    \"Dynamic Huffman Coding\": Zlib.Deflate.CompressionType.DYNAMIC,\n    \"None (Store)\":           Zlib.Deflate.CompressionType.NONE,\n};\n"
  },
  {
    "path": "src/core/operations/A1Z26CipherDecode.mjs",
    "content": "/**\n * @author Jarmo van Lenthe [github.com/jarmovanlenthe]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * A1Z26 Cipher Decode operation\n */\nclass A1Z26CipherDecode extends Operation {\n\n    /**\n     * A1Z26CipherDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"A1Z26 Cipher Decode\";\n        this.module = \"Ciphers\";\n        this.description = \"Converts alphabet order numbers into their corresponding  alphabet character.<br><br>e.g. <code>1</code> becomes <code>a</code> and <code>2</code> becomes <code>b</code>.\";\n        this.infoURL = \"\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: DELIM_OPTIONS\n            }\n        ];\n        this.checks = [\n            {\n                pattern:  \"^\\\\s*([12]?[0-9] )+[12]?[0-9]\\\\s*$\",\n                flags:  \"\",\n                args:   [\"Space\"]\n            },\n            {\n                pattern:  \"^\\\\s*([12]?[0-9],)+[12]?[0-9]\\\\s*$\",\n                flags:  \"\",\n                args:   [\"Comma\"]\n            },\n            {\n                pattern:  \"^\\\\s*([12]?[0-9];)+[12]?[0-9]\\\\s*$\",\n                flags:  \"\",\n                args:   [\"Semi-colon\"]\n            },\n            {\n                pattern:  \"^\\\\s*([12]?[0-9]:)+[12]?[0-9]\\\\s*$\",\n                flags:  \"\",\n                args:   [\"Colon\"]\n            },\n            {\n                pattern:  \"^\\\\s*([12]?[0-9]\\\\n)+[12]?[0-9]\\\\s*$\",\n                flags:  \"\",\n                args:   [\"Line feed\"]\n            },\n            {\n                pattern:  \"^\\\\s*([12]?[0-9]\\\\r\\\\n)+[12]?[0-9]\\\\s*$\",\n                flags:  \"\",\n                args:   [\"CRLF\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n\n        if (input.length === 0) {\n            return \"\";\n        }\n\n        const bites = input.split(delim);\n        let latin1 = \"\";\n        for (let i = 0; i < bites.length; i++) {\n            if (bites[i] < 1 || bites[i] > 26) {\n                throw new OperationError(\"Error: all numbers must be between 1 and 26.\");\n            }\n            latin1 += Utils.chr(parseInt(bites[i], 10) + 96);\n        }\n        return latin1;\n    }\n\n}\n\nexport default A1Z26CipherDecode;\n"
  },
  {
    "path": "src/core/operations/A1Z26CipherEncode.mjs",
    "content": "/**\n * @author Jarmo van Lenthe [github.com/jarmovanlenthe]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * A1Z26 Cipher Encode operation\n */\nclass A1Z26CipherEncode extends Operation {\n\n    /**\n     * A1Z26CipherEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"A1Z26 Cipher Encode\";\n        this.module = \"Ciphers\";\n        this.description = \"Converts alphabet characters into their corresponding alphabet order number.<br><br>e.g. <code>a</code> becomes <code>1</code> and <code>b</code> becomes <code>2</code>.<br><br>Non-alphabet characters are dropped.\";\n        this.infoURL = \"\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n        let output = \"\";\n\n        const sanitizedinput = input.toLowerCase(),\n            charcode = Utils.strToCharcode(sanitizedinput);\n\n        for (let i = 0; i < charcode.length; i++) {\n            const ordinal = charcode[i] - 96;\n\n            if (ordinal > 0 && ordinal <= 26) {\n                output += ordinal.toString(10) + delim;\n            }\n        }\n        return output.slice(0, -delim.length);\n    }\n\n}\n\nexport default A1Z26CipherEncode;\n"
  },
  {
    "path": "src/core/operations/ADD.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { bitOp, add, BITWISE_OP_DELIMS } from \"../lib/BitwiseOp.mjs\";\n\n/**\n * ADD operation\n */\nclass ADD extends Operation {\n\n    /**\n     * ADD constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ADD\";\n        this.module = \"Default\";\n        this.description = \"ADD the input with the given key (e.g. <code>fe023da5</code>), MOD 255\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#Bitwise_operators\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": BITWISE_OP_DELIMS\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string || \"\", args[0].option);\n\n        return bitOp(input, key, add);\n    }\n\n    /**\n     * Highlight ADD\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight ADD in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default ADD;\n"
  },
  {
    "path": "src/core/operations/AESDecrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * AES Decrypt operation\n */\nclass AESDecrypt extends Operation {\n\n    /**\n     * AESDecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"AES Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Advanced_Encryption_Standard\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"argSelector\",\n                \"value\": [\n                    {\n                        name: \"CBC\",\n                        off: [5, 6]\n                    },\n                    {\n                        name: \"CFB\",\n                        off: [5, 6]\n                    },\n                    {\n                        name: \"OFB\",\n                        off: [5, 6]\n                    },\n                    {\n                        name: \"CTR\",\n                        off: [5, 6]\n                    },\n                    {\n                        name: \"GCM\",\n                        on: [5, 6]\n                    },\n                    {\n                        name: \"ECB\",\n                        off: [5, 6]\n                    },\n                    {\n                        name: \"CBC/NoPadding\",\n                        off: [5, 6]\n                    },\n                    {\n                        name: \"ECB/NoPadding\",\n                        off: [5, 6]\n                    }\n                ]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"GCM Tag\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Additional Authenticated Data\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if cannot decrypt input or invalid key length\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            mode = args[2].split(\"/\")[0],\n            noPadding = args[2].endsWith(\"NoPadding\"),\n            inputType = args[3],\n            outputType = args[4],\n            gcmTag = Utils.convertToByteString(args[5].string, args[5].option),\n            aad = Utils.convertToByteString(args[6].string, args[6].option);\n\n        if ([16, 24, 32].indexOf(key.length) < 0) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nThe following algorithms will be used based on the size of the key:\n  16 bytes = AES-128\n  24 bytes = AES-192\n  32 bytes = AES-256`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        const decipher = forge.cipher.createDecipher(\"AES-\" + mode, key);\n\n        /* Allow for a \"no padding\" mode */\n        if (noPadding) {\n            decipher.mode.unpad = function(output, options) {\n                return true;\n            };\n        }\n\n        decipher.start({\n            iv: iv.length === 0 ? \"\" : iv,\n            tag: mode === \"GCM\" ? gcmTag : undefined,\n            additionalData: mode === \"GCM\" ? aad : undefined\n        });\n        decipher.update(forge.util.createBuffer(input));\n        const result = decipher.finish();\n\n        if (result) {\n            return outputType === \"Hex\" ? decipher.output.toHex() : decipher.output.getBytes();\n        } else {\n            throw new OperationError(\"Unable to decrypt input with these parameters.\");\n        }\n    }\n\n}\n\nexport default AESDecrypt;\n"
  },
  {
    "path": "src/core/operations/AESEncrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * AES Encrypt operation\n */\nclass AESEncrypt extends Operation {\n\n    /**\n     * AESEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"AES Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Advanced_Encryption_Standard\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"argSelector\",\n                \"value\": [\n                    {\n                        name: \"CBC\",\n                        off: [5]\n                    },\n                    {\n                        name: \"CFB\",\n                        off: [5]\n                    },\n                    {\n                        name: \"OFB\",\n                        off: [5]\n                    },\n                    {\n                        name: \"CTR\",\n                        off: [5]\n                    },\n                    {\n                        name: \"GCM\",\n                        on: [5]\n                    },\n                    {\n                        name: \"ECB\",\n                        off: [5]\n                    },\n                    {\n                        name: \"CBC/NoPadding\",\n                        off: [5]\n                    },\n                    {\n                        name: \"ECB/NoPadding\",\n                        off: [5]\n                    }\n                ]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Additional Authenticated Data\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if invalid key length\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            mode = args[2].split(\"/\")[0],\n            noPadding =  args[2].endsWith(\"NoPadding\"),\n            inputType = args[3],\n            outputType = args[4],\n            aad = Utils.convertToByteString(args[5].string, args[5].option);\n\n        if ([16, 24, 32].indexOf(key.length) < 0) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nThe following algorithms will be used based on the size of the key:\n  16 bytes = AES-128\n  24 bytes = AES-192\n  32 bytes = AES-256`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        // Handle NoPadding modes\n        if (noPadding && input.length % 16 !== 0) {\n            throw new OperationError(\"Input length must be a multiple of 16 bytes for NoPadding modes.\");\n        }\n        const cipher = forge.cipher.createCipher(\"AES-\" + mode, key);\n        cipher.start({\n            iv: iv,\n            additionalData: mode === \"GCM\" ? aad : undefined\n        });\n        if (noPadding) {\n            cipher.mode.pad = function(output, options) {\n                return true;\n            };\n        }\n        cipher.update(forge.util.createBuffer(input));\n        cipher.finish();\n\n        if (outputType === \"Hex\") {\n            if (mode === \"GCM\") {\n                return cipher.output.toHex() + \"\\n\\n\" +\n                    \"Tag: \" + cipher.mode.tag.toHex();\n            }\n            return cipher.output.toHex();\n        } else {\n            if (mode === \"GCM\") {\n                return cipher.output.getBytes() + \"\\n\\n\" +\n                    \"Tag: \" + cipher.mode.tag.getBytes();\n            }\n            return cipher.output.getBytes();\n        }\n    }\n\n}\n\nexport default AESEncrypt;\n"
  },
  {
    "path": "src/core/operations/AESKeyUnwrap.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\nimport forge from \"node-forge\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * AES Key Unwrap operation\n */\nclass AESKeyUnwrap extends Operation {\n\n    /**\n     * AESKeyUnwrap constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"AES Key Unwrap\";\n        this.module = \"Ciphers\";\n        this.description = \"Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to decrypt 64-bit blocks.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Key_wrap\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key (KEK)\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"a6a6a6a6a6a6a6a6\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const kek = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            inputType = args[2],\n            outputType = args[3];\n\n        if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {\n            throw new OperationError(\"KEK must be either 16, 24, or 32 bytes (currently \" + kek.length + \" bytes)\");\n        }\n        if (iv.length !== 8) {\n            throw new OperationError(\"IV must be 8 bytes (currently \" + iv.length + \" bytes)\");\n        }\n        const inputData = Utils.convertToByteString(input, inputType);\n        if (inputData.length % 8 !== 0 || inputData.length < 24) {\n            throw new OperationError(\"input must be 8n (n>=3) bytes (currently \" + inputData.length + \" bytes)\");\n        }\n\n        const cipher = forge.cipher.createCipher(\"AES-ECB\", kek);\n        cipher.start();\n        cipher.update(forge.util.createBuffer(\"\"));\n        cipher.finish();\n        const paddingBlock = cipher.output.getBytes();\n\n        const decipher = forge.cipher.createDecipher(\"AES-ECB\", kek);\n\n        let A = inputData.substring(0, 8);\n        const R = [];\n        for (let i = 8; i < inputData.length; i += 8) {\n            R.push(inputData.substring(i, i + 8));\n        }\n        let cntLower = R.length >>> 0;\n        let cntUpper = (R.length / ((1 << 30) * 4)) >>> 0;\n        cntUpper = cntUpper * 6 + ((cntLower * 6 / ((1 << 30) * 4)) >>> 0);\n        cntLower = cntLower * 6 >>> 0;\n        for (let j = 5; j >= 0; j--) {\n            for (let i = R.length - 1; i >= 0; i--) {\n                const aBuffer = Utils.strToArrayBuffer(A);\n                const aView = new DataView(aBuffer);\n                aView.setUint32(0, aView.getUint32(0) ^ cntUpper);\n                aView.setUint32(4, aView.getUint32(4) ^ cntLower);\n                A = Utils.arrayBufferToStr(aBuffer, false);\n                decipher.start();\n                decipher.update(forge.util.createBuffer(A + R[i] + paddingBlock));\n                decipher.finish();\n                const B = decipher.output.getBytes();\n                A = B.substring(0, 8);\n                R[i] = B.substring(8, 16);\n                cntLower--;\n                if (cntLower < 0) {\n                    cntUpper--;\n                    cntLower = 0xffffffff;\n                }\n            }\n        }\n        if (A !== iv) {\n            throw new OperationError(\"IV mismatch\");\n        }\n        const P = R.join(\"\");\n\n        if (outputType === \"Hex\") {\n            return toHexFast(Utils.strToArrayBuffer(P));\n        }\n        return P;\n    }\n\n}\n\nexport default AESKeyUnwrap;\n"
  },
  {
    "path": "src/core/operations/AESKeyWrap.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\nimport forge from \"node-forge\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * AES Key Wrap operation\n */\nclass AESKeyWrap extends Operation {\n\n    /**\n     * AESKeyWrap constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"AES Key Wrap\";\n        this.module = \"Ciphers\";\n        this.description = \"A key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Key_wrap\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key (KEK)\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"a6a6a6a6a6a6a6a6\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const kek = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            inputType = args[2],\n            outputType = args[3];\n\n        if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {\n            throw new OperationError(\"KEK must be either 16, 24, or 32 bytes (currently \" + kek.length + \" bytes)\");\n        }\n        if (iv.length !== 8) {\n            throw new OperationError(\"IV must be 8 bytes (currently \" + iv.length + \" bytes)\");\n        }\n        const inputData = Utils.convertToByteString(input, inputType);\n        if (inputData.length % 8 !== 0 || inputData.length < 16) {\n            throw new OperationError(\"input must be 8n (n>=2) bytes (currently \" + inputData.length + \" bytes)\");\n        }\n\n        const cipher = forge.cipher.createCipher(\"AES-ECB\", kek);\n\n        let A = iv;\n        const R = [];\n        for (let i = 0; i < inputData.length; i += 8) {\n            R.push(inputData.substring(i, i + 8));\n        }\n        let cntLower = 1, cntUpper = 0;\n        for (let j = 0; j < 6; j++) {\n            for (let i = 0; i < R.length; i++) {\n                cipher.start();\n                cipher.update(forge.util.createBuffer(A + R[i]));\n                cipher.finish();\n                const B = cipher.output.getBytes();\n                const msbBuffer = Utils.strToArrayBuffer(B.substring(0, 8));\n                const msbView = new DataView(msbBuffer);\n                msbView.setUint32(0, msbView.getUint32(0) ^ cntUpper);\n                msbView.setUint32(4, msbView.getUint32(4) ^ cntLower);\n                A = Utils.arrayBufferToStr(msbBuffer, false);\n                R[i] = B.substring(8, 16);\n                cntLower++;\n                if (cntLower > 0xffffffff) {\n                    cntUpper++;\n                    cntLower = 0;\n                }\n            }\n        }\n        const C = A + R.join(\"\");\n\n        if (outputType === \"Hex\") {\n            return toHexFast(Utils.strToArrayBuffer(C));\n        }\n        return C;\n    }\n\n}\n\nexport default AESKeyWrap;\n"
  },
  {
    "path": "src/core/operations/AMFDecode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport \"reflect-metadata\"; // Required as a shim for the amf library\nimport { AMF0, AMF3 } from \"@astronautlabs/amf\";\n\n/**\n * AMF Decode operation\n */\nclass AMFDecode extends Operation {\n\n    /**\n     * AMFDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"AMF Decode\";\n        this.module = \"Encodings\";\n        this.description = \"Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Action_Message_Format\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.args = [\n            {\n                name: \"Format\",\n                type: \"option\",\n                value: [\"AMF0\", \"AMF3\"],\n                defaultIndex: 1\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        const [format] = args;\n        const handler = format === \"AMF0\" ? AMF0 : AMF3;\n        const encoded = new Uint8Array(input);\n        return handler.Value.deserialize(encoded);\n    }\n\n}\n\nexport default AMFDecode;\n"
  },
  {
    "path": "src/core/operations/AMFEncode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport \"reflect-metadata\"; // Required as a shim for the amf library\nimport { AMF0, AMF3 } from \"@astronautlabs/amf\";\n\n/**\n * AMF Encode operation\n */\nclass AMFEncode extends Operation {\n\n    /**\n     * AMFEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"AMF Encode\";\n        this.module = \"Encodings\";\n        this.description = \"Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Action_Message_Format\";\n        this.inputType = \"JSON\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Format\",\n                type: \"option\",\n                value: [\"AMF0\", \"AMF3\"],\n                defaultIndex: 1\n            }\n        ];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const [format] = args;\n        const handler = format === \"AMF0\" ? AMF0 : AMF3;\n        const output = handler.Value.any(input).serialize();\n        return output.buffer;\n    }\n\n}\n\nexport default AMFEncode;\n"
  },
  {
    "path": "src/core/operations/AND.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { bitOp, and, BITWISE_OP_DELIMS } from \"../lib/BitwiseOp.mjs\";\n\n/**\n * AND operation\n */\nclass AND extends Operation {\n\n    /**\n     * AND constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"AND\";\n        this.module = \"Default\";\n        this.description = \"AND the input with the given key.<br>e.g. <code>fe023da5</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#AND\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": BITWISE_OP_DELIMS\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string || \"\", args[0].option);\n\n        return bitOp(input, key, and);\n    }\n\n    /**\n     * Highlight AND\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight AND in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default AND;\n"
  },
  {
    "path": "src/core/operations/AddLineNumbers.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Add line numbers operation\n */\nclass AddLineNumbers extends Operation {\n\n    /**\n     * AddLineNumbers constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Add line numbers\";\n        this.module = \"Default\";\n        this.description = \"Adds line numbers to the output.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Offset\",\n                \"type\": \"number\",\n                \"value\": 0\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const lines = input.split(\"\\n\"),\n            width = lines.length.toString().length;\n        const offset = args[0] ? parseInt(args[0], 10) : 0;\n        let output = \"\";\n\n        for (let n = 0; n < lines.length; n++) {\n            output += (n+1+offset).toString().padStart(width, \" \") + \" \" + lines[n] + \"\\n\";\n        }\n        return output.slice(0, output.length-1);\n    }\n\n}\n\nexport default AddLineNumbers;\n"
  },
  {
    "path": "src/core/operations/AddTextToImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport {\n    Jimp,\n    JimpMime,\n    ResizeStrategy,\n    measureText,\n    measureTextHeight,\n    loadFont,\n} from \"jimp\";\n\n/**\n * Add Text To Image operation\n */\nclass AddTextToImage extends Operation {\n    /**\n     * AddTextToImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Add Text To Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Adds text onto an image.<br><br>Text can be horizontally or vertically aligned, or the position can be manually specified.<br>Variants of the Roboto font face are available in any size or colour.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Text\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Horizontal align\",\n                type: \"option\",\n                value: [\"None\", \"Left\", \"Center\", \"Right\"],\n            },\n            {\n                name: \"Vertical align\",\n                type: \"option\",\n                value: [\"None\", \"Top\", \"Middle\", \"Bottom\"],\n            },\n            {\n                name: \"X position\",\n                type: \"number\",\n                value: 0,\n            },\n            {\n                name: \"Y position\",\n                type: \"number\",\n                value: 0,\n            },\n            {\n                name: \"Size\",\n                type: \"number\",\n                value: 32,\n                min: 8,\n            },\n            {\n                name: \"Font face\",\n                type: \"option\",\n                value: [\"Roboto\", \"Roboto Black\", \"Roboto Mono\", \"Roboto Slab\"],\n            },\n            {\n                name: \"Red\",\n                type: \"number\",\n                value: 255,\n                min: 0,\n                max: 255,\n            },\n            {\n                name: \"Green\",\n                type: \"number\",\n                value: 255,\n                min: 0,\n                max: 255,\n            },\n            {\n                name: \"Blue\",\n                type: \"number\",\n                value: 255,\n                min: 0,\n                max: 255,\n            },\n            {\n                name: \"Alpha\",\n                type: \"number\",\n                value: 255,\n                min: 0,\n                max: 255,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const text = args[0],\n            hAlign = args[1],\n            vAlign = args[2],\n            size = args[5],\n            fontFace = args[6],\n            red = args[7],\n            green = args[8],\n            blue = args[9],\n            alpha = args[10];\n\n        let xPos = args[3],\n            yPos = args[4];\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n\n        if (isWorkerEnvironment())\n            self.sendStatusMessage(\"Adding text to image...\");\n\n        const fontsMap = {};\n        try {\n            const fonts = [\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/Roboto72White.fnt\"\n                ),\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/RobotoBlack72White.fnt\"\n                ),\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/RobotoMono72White.fnt\"\n                ),\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/RobotoSlab72White.fnt\"\n                ),\n            ];\n\n            await Promise.all(fonts).then((fonts) => {\n                fontsMap.Roboto = fonts[0];\n                fontsMap[\"Roboto Black\"] = fonts[1];\n                fontsMap[\"Roboto Mono\"] = fonts[2];\n                fontsMap[\"Roboto Slab\"] = fonts[3];\n            });\n            // Make Webpack load the png font images\n            await Promise.all([\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/Roboto72White.png\"\n                ),\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/RobotoSlab72White.png\"\n                ),\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/RobotoMono72White.png\"\n                ),\n                import(\n                    /* webpackMode: \"eager\" */ \"../../web/static/fonts/bmfonts/RobotoBlack72White.png\"\n                ),\n            ]);\n        } catch (err) {\n            throw new OperationError(`Error preparing fonts. (${err})`);\n        }\n\n        let jimpFont;\n        try {\n            const font = fontsMap[fontFace];\n\n            // LoadFont needs an absolute url, so append the font name to self.docURL\n            jimpFont = await loadFont(self.docURL + \"/\" + font.default);\n\n            jimpFont.pages.forEach(function (page) {\n                if (page.bitmap) {\n                    // Adjust the RGB values of the image pages to change the font colour.\n                    const pageWidth = page.bitmap.width;\n                    const pageHeight = page.bitmap.height;\n                    for (let ix = 0; ix < pageWidth; ix++) {\n                        for (let iy = 0; iy < pageHeight; iy++) {\n                            const idx = (iy * pageWidth + ix) << 2;\n\n                            const newRed = page.bitmap.data[idx] - (255 - red);\n                            const newGreen =\n                                page.bitmap.data[idx + 1] - (255 - green);\n                            const newBlue =\n                                page.bitmap.data[idx + 2] - (255 - blue);\n                            const newAlpha =\n                                page.bitmap.data[idx + 3] - (255 - alpha);\n\n                            // Make sure the bitmap values don't go below 0 as that makes jimp very unhappy\n                            page.bitmap.data[idx] = newRed > 0 ? newRed : 0;\n                            page.bitmap.data[idx + 1] =\n                                newGreen > 0 ? newGreen : 0;\n                            page.bitmap.data[idx + 2] =\n                                newBlue > 0 ? newBlue : 0;\n                            page.bitmap.data[idx + 3] =\n                                newAlpha > 0 ? newAlpha : 0;\n                        }\n                    }\n                }\n            });\n        } catch (err) {\n            throw new OperationError(`Error loading font. (${err})`);\n        }\n\n        try {\n            // Create a temporary image to hold the rendered text\n            const textImage = new Jimp({\n                width: measureText(jimpFont, text),\n                height: measureTextHeight(jimpFont, text),\n            });\n            textImage.print({\n                font: jimpFont,\n                x: 0,\n                y: 0,\n                text,\n            });\n\n            // Scale the rendered text image to the correct size\n            const scaleFactor = size / 72;\n            if (size !== 1) {\n                // Use bicubic for decreasing size\n                if (size > 1) {\n                    textImage.scale({\n                        f: scaleFactor,\n                        mode: ResizeStrategy.BICUBIC,\n                    });\n                } else {\n                    textImage.scale({\n                        f: scaleFactor,\n                        mode: ResizeStrategy.BILINEAR,\n                    });\n                }\n            }\n\n            // If using the alignment options, calculate the pixel values AFTER the image has been scaled\n            switch (hAlign) {\n                case \"Left\":\n                    xPos = 0;\n                    break;\n                case \"Center\":\n                    xPos = image.width / 2 - textImage.width / 2;\n                    break;\n                case \"Right\":\n                    xPos = image.width - textImage.width;\n                    break;\n            }\n\n            switch (vAlign) {\n                case \"Top\":\n                    yPos = 0;\n                    break;\n                case \"Middle\":\n                    yPos = image.height / 2 - textImage.height / 2;\n                    break;\n                case \"Bottom\":\n                    yPos = image.height - textImage.height;\n                    break;\n            }\n\n            // Blit the rendered text image onto the original source image\n            image.blit({\n                src: textImage,\n                x: xPos,\n                y: yPos,\n            });\n        } catch (err) {\n            throw new OperationError(`Error adding text to image. (${err})`);\n        }\n\n        try {\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error exporting image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the blurred image using HTML for web apps\n     *\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default AddTextToImage;\n"
  },
  {
    "path": "src/core/operations/Adler32Checksum.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Adler-32 Checksum operation\n */\nclass Adler32Checksum extends Operation {\n\n    /**\n     * Adler32Checksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Adler-32 Checksum\";\n        this.module = \"Crypto\";\n        this.description = \"Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995, and is a modification of the Fletcher checksum. Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter).<br><br>Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Adler-32\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const MOD_ADLER = 65521;\n        let a = 1,\n            b = 0;\n        input = new Uint8Array(input);\n\n        for (let i = 0; i < input.length; i++) {\n            a += input[i];\n            b += a;\n        }\n\n        a %= MOD_ADLER;\n        b %= MOD_ADLER;\n\n        return Utils.hex(((b << 16) | a) >>> 0, 8);\n    }\n\n}\n\nexport default Adler32Checksum;\n"
  },
  {
    "path": "src/core/operations/AffineCipherDecode.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Affine Cipher Decode operation\n */\nclass AffineCipherDecode extends Operation {\n\n    /**\n     * AffineCipherDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Affine Cipher Decode\";\n        this.module = \"Ciphers\";\n        this.description = \"The Affine cipher is a type of monoalphabetic substitution cipher. To decrypt, each letter in an alphabet is mapped to its numeric equivalent, decrypted by a mathematical function, and converted back to a letter.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Affine_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"a\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"b\",\n                \"type\": \"number\",\n                \"value\": 0\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if a or b values are invalid\n     */\n    run(input, args) {\n        const alphabet = \"abcdefghijklmnopqrstuvwxyz\",\n            [a, b] = args,\n            aModInv = Utils.modInv(a, 26); // Calculates modular inverse of a\n        let output = \"\";\n\n        if (!/^\\+?(0|[1-9]\\d*)$/.test(a) || !/^\\+?(0|[1-9]\\d*)$/.test(b)) {\n            throw new OperationError(\"The values of a and b can only be integers.\");\n        }\n\n        if (Utils.gcd(a, 26) !== 1) {\n            throw new OperationError(\"The value of `a` must be coprime to 26.\");\n        }\n\n        for (let i = 0; i < input.length; i++) {\n            if (alphabet.indexOf(input[i]) >= 0) {\n                // Uses the affine decode function (y-b * A') % m = x (where m is length of the alphabet and A' is modular inverse)\n                output += alphabet[Utils.mod((alphabet.indexOf(input[i]) - b) * aModInv, 26)];\n            } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {\n                // Same as above, accounting for uppercase\n                output += alphabet[Utils.mod((alphabet.indexOf(input[i].toLowerCase()) - b) * aModInv, 26)].toUpperCase();\n            } else {\n                // Non-alphabetic characters\n                output += input[i];\n            }\n        }\n        return output;\n    }\n\n    /**\n     * Highlight Affine Cipher Decode\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Affine Cipher Decode in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default AffineCipherDecode;\n"
  },
  {
    "path": "src/core/operations/AffineCipherEncode.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { affineEncode } from \"../lib/Ciphers.mjs\";\n\n/**\n * Affine Cipher Encode operation\n */\nclass AffineCipherEncode extends Operation {\n\n    /**\n     * AffineCipherEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Affine Cipher Encode\";\n        this.module = \"Ciphers\";\n        this.description = \"The Affine cipher is a type of monoalphabetic substitution cipher, wherein each letter in an alphabet is mapped to its numeric equivalent, encrypted using simple mathematical function, <code>(ax + b) % 26</code>, and converted back to a letter.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Affine_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"a\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"b\",\n                \"type\": \"number\",\n                \"value\": 0\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return affineEncode(input, args);\n    }\n\n    /**\n     * Highlight Affine Cipher Encode\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Affine Cipher Encode in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default AffineCipherEncode;\n"
  },
  {
    "path": "src/core/operations/AlternatingCaps.mjs",
    "content": "/**\n * @author sw5678\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Alternating caps operation\n */\nclass AlternatingCaps extends Operation {\n\n    /**\n     * AlternatingCaps constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Alternating Caps\";\n        this.module = \"Default\";\n        this.description = \"Alternating caps, also known as studly caps, sticky caps, or spongecase is a form of text notation in which the capitalization of letters varies by some pattern, or arbitrarily. An example of this would be spelling 'alternative caps' as 'aLtErNaTiNg CaPs'.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/Alternating_caps\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args= [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let output = \"\";\n        let previousCaps = true;\n        for (let i = 0; i < input.length; i++) {\n            // Check if the element is a letter\n            if (!RegExp(/^\\p{L}/, \"u\").test(input[i])) {\n                output += input[i];\n            } else if (previousCaps) {\n                output += input[i].toLowerCase();\n                previousCaps = false;\n            } else {\n                output += input[i].toUpperCase();\n                previousCaps = true;\n            }\n        }\n        return output;\n    }\n}\n\nexport default AlternatingCaps;\n"
  },
  {
    "path": "src/core/operations/AnalyseHash.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Analyse hash operation\n */\nclass AnalyseHash extends Operation {\n\n    /**\n     * AnalyseHash constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Analyse hash\";\n        this.module = \"Crypto\";\n        this.description = \"Tries to determine information about a given hash and suggests which algorithm may have been used to generate it based on its length.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = input.replace(/\\s/g, \"\");\n\n        let output = \"\",\n            possibleHashFunctions = [];\n        const byteLength = input.length / 2,\n            bitLength = byteLength * 8;\n\n        if (!/^[a-f0-9]+$/i.test(input)) {\n            throw new OperationError(\"Invalid hash\");\n        }\n\n        output += \"Hash length: \" + input.length + \"\\n\" +\n            \"Byte length: \" + byteLength + \"\\n\" +\n            \"Bit length:  \" + bitLength + \"\\n\\n\" +\n            \"Based on the length, this hash could have been generated by one of the following hashing functions:\\n\";\n\n        switch (bitLength) {\n            case 4:\n                possibleHashFunctions = [\n                    \"Fletcher-4\",\n                    \"Luhn algorithm\",\n                    \"Verhoeff algorithm\",\n                ];\n                break;\n            case 8:\n                possibleHashFunctions = [\n                    \"Fletcher-8\",\n                ];\n                break;\n            case 16:\n                possibleHashFunctions = [\n                    \"BSD checksum\",\n                    \"CRC-16\",\n                    \"SYSV checksum\",\n                    \"Fletcher-16\"\n                ];\n                break;\n            case 32:\n                possibleHashFunctions = [\n                    \"CRC-32\",\n                    \"Fletcher-32\",\n                    \"Adler-32\",\n                ];\n                break;\n            case 64:\n                possibleHashFunctions = [\n                    \"CRC-64\",\n                    \"RIPEMD-64\",\n                    \"SipHash\",\n                ];\n                break;\n            case 128:\n                possibleHashFunctions = [\n                    \"MD5\",\n                    \"MD4\",\n                    \"MD2\",\n                    \"HAVAL-128\",\n                    \"RIPEMD-128\",\n                    \"Snefru\",\n                    \"Tiger-128\",\n                ];\n                break;\n            case 160:\n                possibleHashFunctions = [\n                    \"SHA-1\",\n                    \"SHA-0\",\n                    \"FSB-160\",\n                    \"HAS-160\",\n                    \"HAVAL-160\",\n                    \"RIPEMD-160\",\n                    \"Tiger-160\",\n                ];\n                break;\n            case 192:\n                possibleHashFunctions = [\n                    \"Tiger\",\n                    \"HAVAL-192\",\n                ];\n                break;\n            case 224:\n                possibleHashFunctions = [\n                    \"SHA-224\",\n                    \"SHA3-224\",\n                    \"ECOH-224\",\n                    \"FSB-224\",\n                    \"HAVAL-224\",\n                ];\n                break;\n            case 256:\n                possibleHashFunctions = [\n                    \"SHA-256\",\n                    \"SHA3-256\",\n                    \"BLAKE-256\",\n                    \"ECOH-256\",\n                    \"FSB-256\",\n                    \"GOST\",\n                    \"Grøstl-256\",\n                    \"HAVAL-256\",\n                    \"PANAMA\",\n                    \"RIPEMD-256\",\n                    \"Snefru\",\n                ];\n                break;\n            case 320:\n                possibleHashFunctions = [\n                    \"RIPEMD-320\",\n                ];\n                break;\n            case 384:\n                possibleHashFunctions = [\n                    \"SHA-384\",\n                    \"SHA3-384\",\n                    \"ECOH-384\",\n                    \"FSB-384\",\n                ];\n                break;\n            case 512:\n                possibleHashFunctions = [\n                    \"SHA-512\",\n                    \"SHA3-512\",\n                    \"BLAKE-512\",\n                    \"ECOH-512\",\n                    \"FSB-512\",\n                    \"Grøstl-512\",\n                    \"JH\",\n                    \"MD6\",\n                    \"Spectral Hash\",\n                    \"SWIFFT\",\n                    \"Whirlpool\",\n                ];\n                break;\n            case 1024:\n                possibleHashFunctions = [\n                    \"Fowler-Noll-Vo\",\n                ];\n                break;\n            default:\n                possibleHashFunctions = [\n                    \"Unknown\"\n                ];\n                break;\n        }\n\n        return output + possibleHashFunctions.join(\"\\n\");\n    }\n\n}\n\nexport default AnalyseHash;\n"
  },
  {
    "path": "src/core/operations/AnalyseUUID.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport * as uuid from \"uuid\";\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Analyse UUID operation\n */\nclass AnalyseUUID extends Operation {\n\n    /**\n     * AnalyseUUID constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Analyse UUID\";\n        this.module = \"Crypto\";\n        this.description = \"Tries to determine information about a given UUID and suggests which version may have been used to generate it\";\n        this.infoURL = \"https://wikipedia.org/wiki/Universally_unique_identifier\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        try {\n            const uuidVersion = uuid.version(input);\n            return \"UUID version: \" + uuidVersion;\n        } catch (error) {\n            throw new OperationError(\"Invalid UUID\");\n        }\n    }\n\n}\n\nexport default AnalyseUUID;\n"
  },
  {
    "path": "src/core/operations/Argon2.mjs",
    "content": "/**\n * @author Tan Zhen Yong [tzy@beyondthesprawl.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport argon2 from \"argon2-browser\";\n\n/**\n * Argon2 operation\n */\nclass Argon2 extends Operation {\n\n    /**\n     * Argon2 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Argon2\";\n        this.module = \"Crypto\";\n        this.description = \"Argon2 is a key derivation function that was selected as the winner of the Password Hashing Competition in July 2015. It was designed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich from the University of Luxembourg.<br><br>Enter the password in the input to generate its hash.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Argon2\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Salt\",\n                \"type\": \"toggleString\",\n                \"value\": \"somesalt\",\n                \"toggleValues\": [\"UTF8\", \"Hex\", \"Base64\", \"Latin1\"]\n            },\n            {\n                \"name\": \"Iterations\",\n                \"type\": \"number\",\n                \"value\": 3\n            },\n            {\n                \"name\": \"Memory (KiB)\",\n                \"type\": \"number\",\n                \"value\": 4096\n            },\n            {\n                \"name\": \"Parallelism\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"Hash length (bytes)\",\n                \"type\": \"number\",\n                \"value\": 32\n            },\n            {\n                \"name\": \"Type\",\n                \"type\": \"option\",\n                \"value\": [\"Argon2i\", \"Argon2d\", \"Argon2id\"],\n                \"defaultIndex\": 0\n            },\n            {\n                \"name\": \"Output format\",\n                \"type\": \"option\",\n                \"value\": [\"Encoded hash\", \"Hex hash\", \"Raw hash\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const argon2Types = {\n            \"Argon2i\": argon2.ArgonType.Argon2i,\n            \"Argon2d\": argon2.ArgonType.Argon2d,\n            \"Argon2id\": argon2.ArgonType.Argon2id\n        };\n\n        const salt = Utils.convertToByteString(args[0].string || \"\", args[0].option),\n            time = args[1],\n            mem = args[2],\n            parallelism = args[3],\n            hashLen = args[4],\n            type = argon2Types[args[5]],\n            outFormat = args[6];\n\n        try {\n            const result = await argon2.hash({\n                pass: input,\n                salt,\n                time,\n                mem,\n                parallelism,\n                hashLen,\n                type,\n            });\n\n            switch (outFormat) {\n                case \"Hex hash\":\n                    return result.hashHex;\n                case \"Raw hash\":\n                    return Utils.arrayBufferToStr(result.hash);\n                case \"Encoded hash\":\n                default:\n                    return result.encoded;\n            }\n        } catch (err) {\n            throw new OperationError(`Error: ${err.message}`);\n        }\n    }\n\n}\n\nexport default Argon2;\n"
  },
  {
    "path": "src/core/operations/Argon2Compare.mjs",
    "content": "/**\n * @author Tan Zhen Yong [tzy@beyondthesprawl.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport argon2 from \"argon2-browser\";\n\n/**\n * Argon2 compare operation\n */\nclass Argon2Compare extends Operation {\n\n    /**\n     * Argon2Compare constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Argon2 compare\";\n        this.module = \"Crypto\";\n        this.description = \"Tests whether the input matches the given Argon2 hash. To test multiple possible passwords, use the 'Fork' operation.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Argon2\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Encoded hash\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const encoded = args[0];\n\n        try {\n            await argon2.verify({\n                pass: input,\n                encoded\n            });\n\n            return `Match: ${input}`;\n        } catch (err) {\n            return \"No match\";\n        }\n    }\n\n}\n\nexport default Argon2Compare;\n"
  },
  {
    "path": "src/core/operations/AtbashCipher.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { affineEncode } from \"../lib/Ciphers.mjs\";\n\n/**\n * Atbash Cipher operation\n */\nclass AtbashCipher extends Operation {\n\n    /**\n     * AtbashCipher constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Atbash Cipher\";\n        this.module = \"Ciphers\";\n        this.description = \"Atbash is a mono-alphabetic substitution cipher originally used to encode the Hebrew alphabet. It has been modified here for use with the Latin alphabet.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Atbash\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return affineEncode(input, [25, 25]);\n    }\n\n    /**\n     * Highlight Atbash Cipher\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Atbash Cipher in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default AtbashCipher;\n"
  },
  {
    "path": "src/core/operations/AvroToJSON.mjs",
    "content": "/**\n * @author jarrodconnolly [jarrod@nestedquotes.ca]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport avro from \"avsc\";\n\n/**\n * Avro to JSON operation\n */\nclass AvroToJSON extends Operation {\n\n    /**\n     * AvroToJSON constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Avro to JSON\";\n        this.module = \"Serialise\";\n        this.description = \"Converts Avro encoded data into JSON.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Apache_Avro\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Force Valid JSON\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (input.byteLength <= 0) {\n            throw new OperationError(\"Please provide an input.\");\n        }\n\n        const forceJSON = args[0];\n\n        return new Promise((resolve, reject) => {\n            const result = [];\n            const inpArray = new Uint8Array(input);\n            const decoder = new avro.streams.BlockDecoder();\n\n            decoder\n                .on(\"data\", function (obj) {\n                    result.push(obj);\n                })\n                .on(\"error\", function () {\n                    reject(new OperationError(\"Error parsing Avro file.\"));\n                })\n                .on(\"end\", function () {\n                    if (forceJSON) {\n                        resolve(result.length === 1 ? JSON.stringify(result[0], null, 4) : JSON.stringify(result, null, 4));\n                    } else {\n                        const data = result.reduce((result, current) => result + JSON.stringify(current) + \"\\n\", \"\");\n                        resolve(data);\n                    }\n                });\n\n            decoder.write(inpArray);\n            decoder.end();\n        });\n    }\n}\n\nexport default AvroToJSON;\n"
  },
  {
    "path": "src/core/operations/BLAKE2b.mjs",
    "content": "/**\n * @author h345983745\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport blakejs from \"blakejs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\n\n/**\n * BLAKE2b operation\n */\nclass BLAKE2b extends Operation {\n\n    /**\n     * BLAKE2b constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"BLAKE2b\";\n        this.module = \"Hashing\";\n        this.description = `Performs BLAKE2b hashing on the input.  \n        <br><br> BLAKE2b is a flavour of the BLAKE cryptographic hash function that is optimized for 64-bit platforms and produces digests of any size between 1 and 64 bytes.\n        <br><br> Supports the use of an optional key.`;\n        this.infoURL = \"https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2b_algorithm\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Size\",\n                \"type\": \"option\",\n                \"value\": [\"512\", \"384\", \"256\", \"160\", \"128\"]\n            }, {\n                \"name\": \"Output Encoding\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Base64\", \"Raw\"]\n            }, {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"UTF8\", \"Decimal\", \"Base64\", \"Hex\", \"Latin1\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string} The input having been hashed with BLAKE2b in the encoding format specified.\n     */\n    run(input, args) {\n        const [outSize, outFormat] = args;\n        let key = Utils.convertToByteArray(args[2].string || \"\", args[2].option);\n        if (key.length === 0) {\n            key = null;\n        } else if (key.length > 64) {\n            throw new OperationError([\"Key cannot be greater than 64 bytes\", \"It is currently \" + key.length + \" bytes.\"].join(\"\\n\"));\n        }\n\n        input = new Uint8Array(input);\n        switch (outFormat) {\n            case \"Hex\":\n                return blakejs.blake2bHex(input, key, outSize / 8);\n            case \"Base64\":\n                return toBase64(blakejs.blake2b(input, key, outSize / 8));\n            case \"Raw\":\n                return Utils.arrayBufferToStr(blakejs.blake2b(input, key, outSize / 8).buffer);\n            default:\n                return new OperationError(\"Unsupported Output Type\");\n        }\n    }\n\n}\n\nexport default BLAKE2b;\n"
  },
  {
    "path": "src/core/operations/BLAKE2s.mjs",
    "content": "/**\n * @author h345983745\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport blakejs from \"blakejs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\n\n/**\n * BLAKE2s Operation\n */\nclass BLAKE2s extends Operation {\n\n    /**\n     * BLAKE2s constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"BLAKE2s\";\n        this.module = \"Hashing\";\n        this.description = `Performs BLAKE2s hashing on the input.  \n        <br><br>BLAKE2s is a flavour of the BLAKE cryptographic hash function that is optimized for 8- to 32-bit platforms and produces digests of any size between 1 and 32 bytes.\n        <br><br>Supports the use of an optional key.`;\n        this.infoURL = \"https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Size\",\n                \"type\": \"option\",\n                \"value\": [\"256\", \"160\", \"128\"]\n            }, {\n                \"name\": \"Output Encoding\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Base64\", \"Raw\"]\n            },\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"UTF8\", \"Decimal\", \"Base64\", \"Hex\", \"Latin1\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string} The input having been hashed with BLAKE2s in the encoding format specified.\n     */\n    run(input, args) {\n        const [outSize, outFormat] = args;\n        let key = Utils.convertToByteArray(args[2].string || \"\", args[2].option);\n        if (key.length === 0) {\n            key = null;\n        } else if (key.length > 32) {\n            throw new OperationError([\"Key cannot be greater than 32 bytes\", \"It is currently \" + key.length + \" bytes.\"].join(\"\\n\"));\n        }\n\n        input = new Uint8Array(input);\n        switch (outFormat) {\n            case \"Hex\":\n                return blakejs.blake2sHex(input, key, outSize / 8);\n            case \"Base64\":\n                return toBase64(blakejs.blake2s(input, key, outSize / 8));\n            case \"Raw\":\n                return Utils.arrayBufferToStr(blakejs.blake2s(input, key, outSize / 8).buffer);\n            default:\n                return new OperationError(\"Unsupported Output Type\");\n        }\n    }\n\n}\n\nexport default BLAKE2s;\n"
  },
  {
    "path": "src/core/operations/BLAKE3.mjs",
    "content": "/**\n * @author xumptex [xumptex@outlook.fr]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { blake3 } from \"hash-wasm\";\n/**\n * BLAKE3 operation\n */\nclass BLAKE3 extends Operation {\n\n    /**\n     * BLAKE3 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"BLAKE3\";\n        this.module = \"Hashing\";\n        this.description = \"Hashes the input using BLAKE3 (UTF-8 encoded), with an optional key (also UTF-8), and outputs the result in hexadecimal format.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Size (bytes)\",\n                \"type\": \"number\"\n            }, {\n                \"name\": \"Key\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = args[1];\n        const size = args[0];\n        // Check if the user want a key hash or not\n        if (key === \"\") {\n            return blake3(input, size*8);\n        } if (key.length !== 32) {\n            throw new OperationError(\"The key must be exactly 32 bytes long\");\n        }\n        return blake3(input, size*8, key);\n    }\n\n}\n\nexport default BLAKE3;\n"
  },
  {
    "path": "src/core/operations/BSONDeserialise.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport bson from \"bson\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * BSON deserialise operation\n */\nclass BSONDeserialise extends Operation {\n\n    /**\n     * BSONDeserialise constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"BSON deserialise\";\n        this.module = \"Serialise\";\n        this.description = \"BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be in a raw bytes format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/BSON\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input.byteLength) return \"\";\n\n        try {\n            const data = bson.deserialize(new Buffer(input));\n            return JSON.stringify(data, null, 2);\n        } catch (err) {\n            throw new OperationError(err.toString());\n        }\n    }\n\n}\n\nexport default BSONDeserialise;\n"
  },
  {
    "path": "src/core/operations/BSONSerialise.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport bson from \"bson\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * BSON serialise operation\n */\nclass BSONSerialise extends Operation {\n\n    /**\n     * BSONSerialise constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"BSON serialise\";\n        this.module = \"Serialise\";\n        this.description = \"BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be valid JSON.\";\n        this.infoURL = \"https://wikipedia.org/wiki/BSON\";\n        this.inputType = \"string\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        if (!input) return new ArrayBuffer();\n\n        try {\n            const data = JSON.parse(input);\n            return bson.serialize(data).buffer;\n        } catch (err) {\n            throw new OperationError(err.toString());\n        }\n    }\n\n}\n\nexport default BSONSerialise;\n"
  },
  {
    "path": "src/core/operations/BaconCipherDecode.mjs",
    "content": "/**\n * @author Karsten Silkenbäumer [github.com/kassi]\n * @copyright Karsten Silkenbäumer 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {\n    BACON_ALPHABETS,\n    BACON_TRANSLATION_CASE, BACON_TRANSLATION_AMNZ, BACON_TRANSLATIONS, BACON_CLEARER_MAP, BACON_NORMALIZE_MAP,\n    swapZeroAndOne\n} from \"../lib/Bacon.mjs\";\n\n/**\n * Bacon Cipher Decode operation\n */\nclass BaconCipherDecode extends Operation {\n    /**\n     * BaconCipherDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bacon Cipher Decode\";\n        this.module = \"Default\";\n        this.description = \"Bacon's cipher or the Baconian cipher is a method of steganography devised by Francis Bacon in 1605. A message is concealed in the presentation of text, rather than its content.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bacon%27s_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Alphabet\",\n                \"type\": \"option\",\n                \"value\": Object.keys(BACON_ALPHABETS)\n            },\n            {\n                \"name\": \"Translation\",\n                \"type\": \"option\",\n                \"value\": BACON_TRANSLATIONS\n            },\n            {\n                \"name\": \"Invert Translation\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n        this.checks = [\n            {\n                pattern:  \"^\\\\s*([01]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Standard (I=J and U=V)\", \"0/1\", false]\n            },\n            {\n                pattern:  \"^\\\\s*([01]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Standard (I=J and U=V)\", \"0/1\", true]\n            },\n            {\n                pattern:  \"^\\\\s*([AB]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Standard (I=J and U=V)\", \"A/B\", false]\n            },\n            {\n                pattern:  \"^\\\\s*([AB]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Standard (I=J and U=V)\", \"A/B\", true]\n            },\n            {\n                pattern:  \"^\\\\s*([01]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Complete\", \"0/1\", false]\n            },\n            {\n                pattern:  \"^\\\\s*([01]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Complete\", \"0/1\", true]\n            },\n            {\n                pattern:  \"^\\\\s*([AB]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Complete\", \"A/B\", false]\n            },\n            {\n                pattern:  \"^\\\\s*([AB]{5}\\\\s?)+$\",\n                flags:  \"\",\n                args:   [\"Complete\", \"A/B\", true]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [alphabet, translation, invert] = args;\n        const alphabetObject = BACON_ALPHABETS[alphabet];\n\n        // remove invalid characters\n        input = input.replace(BACON_CLEARER_MAP[translation], \"\");\n\n        // normalize to unique alphabet\n        if (BACON_NORMALIZE_MAP[translation] !== undefined) {\n            input = input.replace(/./g, function (c) {\n                return BACON_NORMALIZE_MAP[translation][c];\n            });\n        } else if (translation === BACON_TRANSLATION_CASE) {\n            const codeA = \"A\".charCodeAt(0);\n            const codeZ = \"Z\".charCodeAt(0);\n            input = input.replace(/./g, function (c) {\n                const code = c.charCodeAt(0);\n                if (code >= codeA && code <= codeZ) {\n                    return \"1\";\n                } else {\n                    return \"0\";\n                }\n            });\n        } else if (translation === BACON_TRANSLATION_AMNZ) {\n            const words = input.split(/\\s+/);\n            const letters = words.map(function (e) {\n                if (e) {\n                    const code = e[0].toUpperCase().charCodeAt(0);\n                    return code >= \"N\".charCodeAt(0) ? \"1\" : \"0\";\n                } else {\n                    return \"\";\n                }\n            });\n            input = letters.join(\"\");\n        }\n\n        if (invert) {\n            input = swapZeroAndOne(input);\n        }\n\n        // group into 5\n        const inputArray = input.match(/(.{5})/g) || [];\n\n        let output = \"\";\n        for (let i = 0; i < inputArray.length; i++) {\n            const code = inputArray[i];\n            const number = parseInt(code, 2);\n            output += number < alphabetObject.alphabet.length ? alphabetObject.alphabet[number] : \"?\";\n        }\n        return output;\n    }\n}\n\nexport default BaconCipherDecode;\n"
  },
  {
    "path": "src/core/operations/BaconCipherEncode.mjs",
    "content": "/**\n * @author Karsten Silkenbäumer [github.com/kassi]\n * @copyright Karsten Silkenbäumer 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {\n    BACON_ALPHABETS,\n    BACON_TRANSLATIONS_FOR_ENCODING, BACON_TRANSLATION_AB,\n    swapZeroAndOne\n} from \"../lib/Bacon.mjs\";\n\n/**\n * Bacon Cipher Encode operation\n */\nclass BaconCipherEncode extends Operation {\n    /**\n     * BaconCipherEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bacon Cipher Encode\";\n        this.module = \"Default\";\n        this.description = \"Bacon's cipher or the Baconian cipher is a method of steganography devised by Francis Bacon in 1605. A message is concealed in the presentation of text, rather than its content.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bacon%27s_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Alphabet\",\n                \"type\": \"option\",\n                \"value\": Object.keys(BACON_ALPHABETS)\n            },\n            {\n                \"name\": \"Translation\",\n                \"type\": \"option\",\n                \"value\": BACON_TRANSLATIONS_FOR_ENCODING\n            },\n            {\n                \"name\": \"Keep extra characters\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Invert Translation\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [alphabet, translation, keep, invert] = args;\n\n        const alphabetObject = BACON_ALPHABETS[alphabet];\n        const charCodeA = \"A\".charCodeAt(0);\n        const charCodeZ = \"Z\".charCodeAt(0);\n\n        let output = input.replace(/./g, function (c) {\n            const charCode = c.toUpperCase().charCodeAt(0);\n            if (charCode >= charCodeA && charCode <= charCodeZ) {\n                let code = charCode - charCodeA;\n                if (alphabetObject.codes !== undefined) {\n                    code = alphabetObject.codes[code];\n                }\n                const bacon = (\"00000\" + code.toString(2)).substr(-5, 5);\n                return bacon;\n            } else {\n                return c;\n            }\n        });\n\n        if (invert) {\n            output = swapZeroAndOne(output);\n        }\n        if (!keep) {\n            output = output.replace(/[^01]/g, \"\");\n            const outputArray = output.match(/(.{5})/g) || [];\n            output = outputArray.join(\" \");\n        }\n        if (translation === BACON_TRANSLATION_AB) {\n            output = output.replace(/[01]/g, function (c) {\n                return {\n                    \"0\": \"A\",\n                    \"1\": \"B\"\n                }[c];\n            });\n        }\n\n        return output;\n    }\n}\n\nexport default BaconCipherEncode;\n"
  },
  {
    "path": "src/core/operations/Bcrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport bcrypt from \"bcryptjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * Bcrypt operation\n */\nclass Bcrypt extends Operation {\n\n    /**\n     * Bcrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bcrypt\";\n        this.module = \"Crypto\";\n        this.description = \"bcrypt is a password hashing function designed by Niels Provos and David Mazi\\xe8res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count (rounds) can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.<br><br>Enter the password in the input to generate its hash.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bcrypt\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Rounds\",\n                \"type\": \"number\",\n                \"value\": 10\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const rounds = args[0];\n        const salt = await bcrypt.genSalt(rounds);\n\n        return await bcrypt.hash(input, salt, null, p => {\n            // Progress callback\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);\n        });\n\n    }\n\n}\n\nexport default Bcrypt;\n"
  },
  {
    "path": "src/core/operations/BcryptCompare.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport bcrypt from \"bcryptjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n\n/**\n * Bcrypt compare operation\n */\nclass BcryptCompare extends Operation {\n\n    /**\n     * BcryptCompare constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bcrypt compare\";\n        this.module = \"Crypto\";\n        this.description = \"Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bcrypt\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Hash\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const hash = args[0];\n\n        const match = await bcrypt.compare(input, hash, null, p => {\n            // Progress callback\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);\n        });\n\n        return match ? \"Match: \" + input : \"No match\";\n\n    }\n\n}\n\nexport default BcryptCompare;\n"
  },
  {
    "path": "src/core/operations/BcryptParse.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport bcrypt from \"bcryptjs\";\n\n/**\n * Bcrypt parse operation\n */\nclass BcryptParse extends Operation {\n\n    /**\n     * BcryptParse constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bcrypt parse\";\n        this.module = \"Crypto\";\n        this.description = \"Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bcrypt\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        try {\n            return `Rounds: ${bcrypt.getRounds(input)}\nSalt: ${bcrypt.getSalt(input)}\nPassword hash: ${input.split(bcrypt.getSalt(input))[1]}\nFull hash: ${input}`;\n        } catch (err) {\n            throw new OperationError(\"Error: \" + err.toString());\n        }\n    }\n\n}\n\nexport default BcryptParse;\n"
  },
  {
    "path": "src/core/operations/BifidCipherDecode.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { genPolybiusSquare } from \"../lib/Ciphers.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Bifid Cipher Decode operation\n */\nclass BifidCipherDecode extends Operation {\n\n    /**\n     * BifidCipherDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bifid Cipher Decode\";\n        this.module = \"Ciphers\";\n        this.description = \"The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bifid_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Keyword\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if invalid key\n     */\n    run(input, args) {\n        const keywordStr = args[0].toUpperCase().replace(\"J\", \"I\"),\n            keyword = keywordStr.split(\"\").unique(),\n            alpha = \"ABCDEFGHIKLMNOPQRSTUVWXYZ\",\n            structure = [];\n\n        let output = \"\",\n            count = 0,\n            trans = \"\";\n\n        if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0)\n            throw new OperationError(\"The key must consist only of letters in the English alphabet\");\n\n        const polybius = genPolybiusSquare(keywordStr);\n\n        input.replace(\"J\", \"I\").split(\"\").forEach((letter) => {\n            const alpInd = alpha.split(\"\").indexOf(letter.toLocaleUpperCase()) >= 0;\n            let polInd;\n\n            if (alpInd) {\n                for (let i = 0; i < 5; i++) {\n                    polInd = polybius[i].indexOf(letter.toLocaleUpperCase());\n                    if (polInd >= 0) {\n                        trans += `${i}${polInd}`;\n                    }\n                }\n\n                if (alpha.split(\"\").indexOf(letter) >= 0) {\n                    structure.push(true);\n                } else if (alpInd) {\n                    structure.push(false);\n                }\n            } else {\n                structure.push(letter);\n            }\n        });\n\n        structure.forEach(pos => {\n            if (typeof pos === \"boolean\") {\n                const coords = [trans[count], trans[count+trans.length/2]];\n\n                output += pos ?\n                    polybius[coords[0]][coords[1]] :\n                    polybius[coords[0]][coords[1]].toLocaleLowerCase();\n                count++;\n            } else {\n                output += pos;\n            }\n        });\n\n        return output;\n    }\n\n    /**\n     * Highlight Bifid Cipher Decode\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Bifid Cipher Decode in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default BifidCipherDecode;\n"
  },
  {
    "path": "src/core/operations/BifidCipherEncode.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { genPolybiusSquare } from \"../lib/Ciphers.mjs\";\n\n/**\n * Bifid Cipher Encode operation\n */\nclass BifidCipherEncode extends Operation {\n\n    /**\n     * BifidCipherEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bifid Cipher Encode\";\n        this.module = \"Ciphers\";\n        this.description = \"The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bifid_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Keyword\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if key is invalid\n     */\n    run(input, args) {\n        const keywordStr = args[0].toUpperCase().replace(\"J\", \"I\"),\n            keyword = keywordStr.split(\"\").unique(),\n            alpha = \"ABCDEFGHIKLMNOPQRSTUVWXYZ\",\n            xCo = [],\n            yCo = [],\n            structure = [];\n\n        let output = \"\",\n            count = 0;\n\n\n        if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0)\n            throw new OperationError(\"The key must consist only of letters in the English alphabet\");\n\n        const polybius = genPolybiusSquare(keywordStr);\n\n        input.replace(\"J\", \"I\").split(\"\").forEach(letter => {\n            const alpInd = alpha.split(\"\").indexOf(letter.toLocaleUpperCase()) >= 0;\n            let polInd;\n\n            if (alpInd) {\n                for (let i = 0; i < 5; i++) {\n                    polInd = polybius[i].indexOf(letter.toLocaleUpperCase());\n                    if (polInd >= 0) {\n                        xCo.push(polInd);\n                        yCo.push(i);\n                    }\n                }\n\n                if (alpha.split(\"\").indexOf(letter) >= 0) {\n                    structure.push(true);\n                } else if (alpInd) {\n                    structure.push(false);\n                }\n            } else {\n                structure.push(letter);\n            }\n        });\n\n        const trans = `${yCo.join(\"\")}${xCo.join(\"\")}`;\n\n        structure.forEach(pos => {\n            if (typeof pos === \"boolean\") {\n                const coords = trans.substr(2*count, 2).split(\"\");\n\n                output += pos ?\n                    polybius[coords[0]][coords[1]] :\n                    polybius[coords[0]][coords[1]].toLocaleLowerCase();\n                count++;\n            } else {\n                output += pos;\n            }\n        });\n\n        return output;\n    }\n\n    /**\n     * Highlight Bifid Cipher Encode\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Bifid Cipher Encode in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default BifidCipherEncode;\n"
  },
  {
    "path": "src/core/operations/BitShiftLeft.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Bit shift left operation\n */\nclass BitShiftLeft extends Operation {\n\n    /**\n     * BitShiftLeft constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bit shift left\";\n        this.module = \"Default\";\n        this.description = \"Shifts the bits in each byte towards the left by the specified amount.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Amount\",\n                \"type\": \"number\",\n                \"value\": 1\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const amount = args[0];\n        input = new Uint8Array(input);\n\n        return input.map(b => {\n            return (b << amount) & 0xff;\n        }).buffer;\n    }\n\n    /**\n     * Highlight Bit shift left\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Bit shift left in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default BitShiftLeft;\n"
  },
  {
    "path": "src/core/operations/BitShiftRight.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Bit shift right operation\n */\nclass BitShiftRight extends Operation {\n\n    /**\n     * BitShiftRight constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bit shift right\";\n        this.module = \"Default\";\n        this.description = \"Shifts the bits in each byte towards the right by the specified amount.<br><br><i>Logical shifts</i> replace the leftmost bits with zeros.<br><i>Arithmetic shifts</i> preserve the most significant bit (MSB) of the original byte keeping the sign the same (positive or negative).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Amount\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"Type\",\n                \"type\": \"option\",\n                \"value\": [\"Logical shift\", \"Arithmetic shift\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const amount = args[0],\n            type = args[1],\n            mask = type === \"Logical shift\" ? 0 : 0x80;\n        input = new Uint8Array(input);\n\n        return input.map(b => {\n            return (b >>> amount) ^ (b & mask);\n        }).buffer;\n    }\n\n    /**\n     * Highlight Bit shift right\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Bit shift right in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default BitShiftRight;\n"
  },
  {
    "path": "src/core/operations/BlowfishDecrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { Blowfish } from \"../lib/Blowfish.mjs\";\n\n/**\n * Blowfish Decrypt operation\n */\nclass BlowfishDecrypt extends Operation {\n\n    /**\n     * BlowfishDecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Blowfish Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Blowfish_(cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            mode = args[2],\n            inputType = args[3],\n            outputType = args[4];\n\n        if (key.length < 4 || key.length > 56) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nBlowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);\n        }\n\n        if (mode !== \"ECB\" && iv.length !== 8) {\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        const decipher = Blowfish.createDecipher(key, mode);\n        decipher.start({iv: iv});\n        decipher.update(forge.util.createBuffer(input));\n        const result = decipher.finish();\n\n        if (result) {\n            return outputType === \"Hex\" ? decipher.output.toHex() : decipher.output.getBytes();\n        } else {\n            throw new OperationError(\"Unable to decrypt input with these parameters.\");\n        }\n    }\n\n}\n\nexport default BlowfishDecrypt;\n"
  },
  {
    "path": "src/core/operations/BlowfishEncrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { Blowfish } from \"../lib/Blowfish.mjs\";\n\n/**\n * Blowfish Encrypt operation\n */\nclass BlowfishEncrypt extends Operation {\n\n    /**\n     * BlowfishEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Blowfish Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Blowfish_(cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            mode = args[2],\n            inputType = args[3],\n            outputType = args[4];\n\n        if (key.length < 4 || key.length > 56) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nBlowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);\n        }\n\n        if (mode !== \"ECB\" && iv.length !== 8) {\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        const cipher = Blowfish.createCipher(key, mode);\n        cipher.start({iv: iv});\n        cipher.update(forge.util.createBuffer(input));\n        cipher.finish();\n\n        if (outputType === \"Hex\") {\n            return cipher.output.toHex();\n        } else {\n            return cipher.output.getBytes();\n        }\n    }\n\n}\n\nexport default BlowfishEncrypt;\n"
  },
  {
    "path": "src/core/operations/BlurImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Blur Image operation\n */\nclass BlurImage extends Operation {\n    /**\n     * BlurImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Blur Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Applies a blur effect to the image.<br><br>Gaussian blur is much slower than fast blur, but produces better results.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Gaussian_blur\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Amount\",\n                type: \"number\",\n                value: 5,\n                min: 1,\n            },\n            {\n                name: \"Type\",\n                type: \"option\",\n                value: [\"Fast\", \"Gaussian\"],\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [blurAmount, blurType] = args;\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            switch (blurType) {\n                case \"Fast\":\n                    if (isWorkerEnvironment())\n                        self.sendStatusMessage(\"Fast blurring image...\");\n                    image.blur(blurAmount);\n                    break;\n                case \"Gaussian\":\n                    if (isWorkerEnvironment())\n                        self.sendStatusMessage(\"Gaussian blurring image...\");\n                    image.gaussian(blurAmount);\n                    break;\n            }\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error blurring image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the blurred image using HTML for web apps\n     *\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default BlurImage;\n"
  },
  {
    "path": "src/core/operations/Bombe.mjs",
    "content": "/**\n * Emulation of the Bombe machine.\n *\n * Tested against the Bombe Rebuild at Bletchley Park's TNMOC\n * using a variety of inputs and settings to confirm correctness.\n *\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { BombeMachine } from \"../lib/Bombe.mjs\";\nimport { ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector } from \"../lib/Enigma.mjs\";\n\n/**\n * Bombe operation\n */\nclass Bombe extends Operation {\n    /**\n     * Bombe constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bombe\";\n        this.module = \"Bletchley\";\n        this.description = \"Emulation of the Bombe machine used at Bletchley Park to attack Enigma, based on work by Polish and British cryptanalysts.<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 or is too short, a large number of incorrect outputs will usually 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.<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bombe\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Model\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"3-rotor\",\n                        off: [1]\n                    },\n                    {\n                        name: \"4-rotor\",\n                        on: [1]\n                    }\n                ]\n            },\n            {\n                name: \"Left-most (4th) rotor\",\n                type: \"editableOption\",\n                value: ROTORS_FOURTH,\n                defaultIndex: 0\n            },\n            {\n                name: \"Left-hand rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"Middle rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 1\n            },\n            {\n                name: \"Right-hand rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 2\n            },\n            {\n                name: \"Reflector\",\n                type: \"editableOption\",\n                value: REFLECTORS\n            },\n            {\n                name: \"Crib\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Crib offset\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Use checking machine\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n    }\n\n    /**\n     * Format and send a status update message.\n     * @param {number} nLoops - Number of loops in the menu\n     * @param {number} nStops - How many stops so far\n     * @param {number} progress - Progress (as a float in the range 0..1)\n     */\n    updateStatus(nLoops, nStops, progress) {\n        const msg = `Bombe run with ${nLoops} loop${nLoops === 1 ? \"\" : \"s\"} in menu (2+ desirable): ${nStops} stops, ${Math.floor(100 * progress)}% done`;\n        self.sendStatusMessage(msg);\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const model = args[0];\n        const reflectorstr = args[5];\n        let crib = args[6];\n        const offset = args[7];\n        const check = args[8];\n        const rotors = [];\n        for (let i=0; i<4; i++) {\n            if (i === 0 && model === \"3-rotor\") {\n                // No fourth rotor\n                continue;\n            }\n            let rstr = args[i + 1];\n            // The Bombe doesn't take stepping into account so we'll just ignore it here\n            if (rstr.includes(\"<\")) {\n                rstr = rstr.split(\"<\", 2)[0];\n            }\n            rotors.push(rstr);\n        }\n        // Rotors are handled in reverse\n        rotors.reverse();\n        if (crib.length === 0) {\n            throw new OperationError(\"Crib cannot be empty\");\n        }\n        if (offset < 0) {\n            throw new OperationError(\"Offset cannot be negative\");\n        }\n        // For symmetry with the Enigma op, for the input we'll just remove all invalid characters\n        input = input.replace(/[^A-Za-z]/g, \"\").toUpperCase();\n        crib = crib.replace(/[^A-Za-z]/g, \"\").toUpperCase();\n        const ciphertext = input.slice(offset);\n        const reflector = new Reflector(reflectorstr);\n        let update;\n        if (isWorkerEnvironment()) {\n            update = this.updateStatus;\n        } else {\n            update = undefined;\n        }\n        const bombe = new BombeMachine(rotors, reflector, ciphertext, crib, check, update);\n        const result = bombe.run();\n        return {\n            nLoops: bombe.nLoops,\n            result: result\n        };\n    }\n\n\n    /**\n     * Displays the Bombe results in an HTML table\n     *\n     * @param {Object} output\n     * @param {number} output.nLoops\n     * @param {Array[]} output.result\n     * @returns {html}\n     */\n    present(output) {\n        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`;\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>\\n\";\n        for (const [setting, stecker, decrypt] of output.result) {\n            html += `<tr><td>${setting}</td>  <td>${stecker}</td>  <td>${decrypt}</td></tr>\\n`;\n        }\n        html += \"</table>\";\n        return html;\n    }\n}\n\nexport default Bombe;\n"
  },
  {
    "path": "src/core/operations/Bzip2Compress.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Bzip2 from \"libbzip2-wasm\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * Bzip2 Compress operation\n */\nclass Bzip2Compress extends Operation {\n\n    /**\n     * Bzip2Compress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bzip2 Compress\";\n        this.module = \"Compression\";\n        this.description = \"Bzip2 is a compression library developed by Julian Seward (of GHC fame) that uses the Burrows-Wheeler algorithm. It only supports compressing single files and its compression is slow, however is more effective than Deflate (.gz & .zip).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bzip2\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Block size (100s of kb)\",\n                type: \"number\",\n                value: 9,\n                min: 1,\n                max: 9\n            },\n            {\n                name: \"Work factor\",\n                type: \"number\",\n                value: 30\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {File}\n     */\n    run(input, args) {\n        const [blockSize, workFactor] = args;\n        if (input.byteLength <= 0) {\n            throw new OperationError(\"Please provide an input.\");\n        }\n        if (isWorkerEnvironment()) self.sendStatusMessage(\"Loading Bzip2...\");\n        return new Promise((resolve, reject) => {\n            Bzip2().then(bzip2 => {\n                if (isWorkerEnvironment()) self.sendStatusMessage(\"Compressing data...\");\n                const inpArray = new Uint8Array(input);\n                const bzip2cc = bzip2.compressBZ2(inpArray, blockSize, workFactor);\n                if (bzip2cc.error !== 0) {\n                    reject(new OperationError(bzip2cc.error_msg));\n                } else {\n                    const output = bzip2cc.output;\n                    resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset));\n                }\n            });\n        });\n    }\n\n}\n\nexport default Bzip2Compress;\n"
  },
  {
    "path": "src/core/operations/Bzip2Decompress.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Bzip2 from \"libbzip2-wasm\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * Bzip2 Decompress operation\n */\nclass Bzip2Decompress extends Operation {\n\n    /**\n     * Bzip2Decompress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Bzip2 Decompress\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data using the Bzip2 algorithm.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bzip2\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Use low-memory, slower decompression algorithm\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n        this.checks = [\n            {\n                \"pattern\": \"^\\\\x42\\\\x5a\\\\x68\",\n                \"flags\": \"\",\n                \"args\": []\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [small] = args;\n        if (input.byteLength <= 0) {\n            throw new OperationError(\"Please provide an input.\");\n        }\n        if (isWorkerEnvironment()) self.sendStatusMessage(\"Loading Bzip2...\");\n        return new Promise((resolve, reject) => {\n            Bzip2().then(bzip2 => {\n                if (isWorkerEnvironment()) self.sendStatusMessage(\"Decompressing data...\");\n                const inpArray = new Uint8Array(input);\n                const bzip2cc = bzip2.decompressBZ2(inpArray, small ? 1 : 0);\n                if (bzip2cc.error !== 0) {\n                    reject(new OperationError(bzip2cc.error_msg));\n                } else {\n                    const output = bzip2cc.output;\n                    resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset));\n                }\n            });\n        });\n    }\n\n}\n\nexport default Bzip2Decompress;\n"
  },
  {
    "path": "src/core/operations/CBORDecode.mjs",
    "content": "/**\n * @author Danh4 [dan.h4@ncsc.gov.uk]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Cbor from \"cbor\";\n\n/**\n * CBOR Decode operation\n */\nclass CBORDecode extends Operation {\n\n    /**\n     * CBORDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CBOR Decode\";\n        this.module = \"Serialise\";\n        this.description = \"Concise Binary Object Representation (CBOR) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain name–value pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human readability. It is defined in IETF RFC 8949.\";\n        this.infoURL = \"https://wikipedia.org/wiki/CBOR\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        return Cbor.decodeFirstSync(Buffer.from(input).toString(\"hex\"));\n    }\n\n}\n\nexport default CBORDecode;\n"
  },
  {
    "path": "src/core/operations/CBOREncode.mjs",
    "content": "/**\n * @author Danh4 [dan.h4@ncsc.gov.uk]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Cbor from \"cbor\";\n\n/**\n * CBOR Encode operation\n */\nclass CBOREncode extends Operation {\n\n    /**\n     * CBOREncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CBOR Encode\";\n        this.module = \"Serialise\";\n        this.description = \"Concise Binary Object Representation (CBOR) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain name–value pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human readability. It is defined in IETF RFC 8949.\";\n        this.infoURL = \"https://wikipedia.org/wiki/CBOR\";\n        this.inputType = \"JSON\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        return new Uint8Array(Cbor.encodeCanonical(input)).buffer;\n    }\n\n}\n\nexport default CBOREncode;\n"
  },
  {
    "path": "src/core/operations/CMAC.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * CMAC operation\n */\nclass CMAC extends Operation {\n\n    /**\n     * CMAC constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CMAC\";\n        this.module = \"Crypto\";\n        this.description = \"CMAC is a block-cipher based message authentication code algorithm.<br><br>RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.<br>NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES.\";\n        this.infoURL = \"https://wikipedia.org/wiki/CMAC\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Encryption algorithm\",\n                \"type\": \"option\",\n                \"value\": [\"AES\", \"Triple DES\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option);\n        const algo = args[1];\n\n        const info = (function() {\n            switch (algo) {\n                case \"AES\":\n                    if (key.length !== 16 && key.length !== 24 && key.length !== 32) {\n                        throw new OperationError(\"The key for AES must be either 16, 24, or 32 bytes (currently \" + key.length + \" bytes)\");\n                    }\n                    return {\n                        \"algorithm\": \"AES-ECB\",\n                        \"key\": key,\n                        \"blockSize\": 16,\n                        \"Rb\": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]),\n                    };\n                case \"Triple DES\":\n                    if (key.length !== 16 && key.length !== 24) {\n                        throw new OperationError(\"The key for Triple DES must be 16 or 24 bytes (currently \" + key.length + \" bytes)\");\n                    }\n                    return {\n                        \"algorithm\": \"3DES-ECB\",\n                        \"key\": key.length === 16 ? key + key.substring(0, 8) : key,\n                        \"blockSize\": 8,\n                        \"Rb\": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]),\n                    };\n                default:\n                    throw new OperationError(\"Undefined encryption algorithm\");\n            }\n        })();\n\n        const xor = function(a, b, out) {\n            if (!out) out = new Uint8Array(a.length);\n            for (let i = 0; i < a.length; i++) {\n                out[i] = a[i] ^ b[i];\n            }\n            return out;\n        };\n\n        const leftShift1 = function(a) {\n            const out = new Uint8Array(a.length);\n            let carry = 0;\n            for (let i = a.length - 1; i >= 0; i--) {\n                out[i] = (a[i] << 1) | carry;\n                carry = a[i] >> 7;\n            }\n            return out;\n        };\n\n        const cipher = forge.cipher.createCipher(info.algorithm, info.key);\n        const encrypt = function(a, out) {\n            if (!out) out = new Uint8Array(a.length);\n            cipher.start();\n            cipher.update(forge.util.createBuffer(a));\n            cipher.finish();\n            const cipherText = cipher.output.getBytes();\n            for (let i = 0; i < a.length; i++) {\n                out[i] = cipherText.charCodeAt(i);\n            }\n            return out;\n        };\n\n        const L = encrypt(new Uint8Array(info.blockSize));\n        const K1 = leftShift1(L);\n        if (L[0] & 0x80) xor(K1, info.Rb, K1);\n        const K2 = leftShift1(K1);\n        if (K1[0] & 0x80) xor(K2, info.Rb, K2);\n\n        const n = Math.ceil(input.byteLength / info.blockSize);\n        const lastBlock = (function() {\n            if (n === 0) {\n                const data = new Uint8Array(K2);\n                data[0] ^= 0x80;\n                return data;\n            }\n            const inputLast = new Uint8Array(input, info.blockSize * (n - 1));\n            if (inputLast.length === info.blockSize) {\n                return xor(inputLast, K1, inputLast);\n            } else {\n                const data = new Uint8Array(info.blockSize);\n                data.set(inputLast, 0);\n                data[inputLast.length] = 0x80;\n                return xor(data, K2, data);\n            }\n        })();\n\n        const X = new Uint8Array(info.blockSize);\n        const Y = new Uint8Array(info.blockSize);\n        for (let i = 0; i < n - 1; i++) {\n            xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y);\n            encrypt(Y, X);\n        }\n        xor(lastBlock, X, Y);\n        const T = encrypt(Y);\n        return toHexFast(T);\n    }\n\n}\n\nexport default CMAC;\n"
  },
  {
    "path": "src/core/operations/CRCChecksum.mjs",
    "content": "/**\n * @author r4mos [2k95ljkhg@mozmail.com]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * CRC Checksum operation\n */\nclass CRCChecksum extends Operation {\n\n    /**\n     * CRCChecksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CRC Checksum\";\n        this.module = \"Default\";\n        this.description = \"A Cyclic Redundancy Check (<b>CRC</b>) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Cyclic_redundancy_check\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"Custom\",\n                        on: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-3/GSM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-3/ROHC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-4/G-704\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-4/INTERLAKEN\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-4/ITU\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-5/EPC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-5/EPC-C1G2\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-5/G-704\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-5/ITU\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-5/USB\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-6/CDMA2000-A\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-6/CDMA2000-B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-6/DARC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-6/G-704\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-6/GSM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-6/ITU\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-7/MMC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-7/ROHC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-7/UMTS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/8H2F\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/AES\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/AUTOSAR\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/BLUETOOTH\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/CDMA2000\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/DARC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/DVB-S2\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/EBU\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/GSM-A\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/GSM-B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/HITAG\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/I-432-1\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/I-CODE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/ITU\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/LTE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/MAXIM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/MAXIM-DOW\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/MIFARE-MAD\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/NRSC-5\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/OPENSAFETY\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/ROHC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/SAE-J1850\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/SAE-J1850-ZERO\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/SMBUS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/TECH-3250\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-8/WCDMA\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-10/ATM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-10/CDMA2000\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-10/GSM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-10/I-610\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-11/FLEXRAY\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-11/UMTS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-12/3GPP\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-12/CDMA2000\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-12/DECT\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-12/GSM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-12/UMTS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-13/BBC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-14/DARC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-14/GSM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-15/CAN\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-15/MPT1327\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/A\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/ACORN\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/ARC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/AUG-CCITT\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/AUTOSAR\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/BLUETOOTH\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/BUYPASS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/CCITT\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/CCITT-FALSE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/CCITT-TRUE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/CCITT-ZERO\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/CDMA2000\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/CMS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/DARC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/DDS-110\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/DECT-R\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/DECT-X\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/DNP\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/EN-13757\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/EPC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/EPC-C1G2\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/GENIBUS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/GSM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/I-CODE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/IBM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/IBM-3740\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/IBM-SDLC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/IEC-61158-2\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/ISO-HDLC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/ISO-IEC-14443-3-A\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/ISO-IEC-14443-3-B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/KERMIT\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/LHA\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/LJ1200\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/LTE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/M17\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/MAXIM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/MAXIM-DOW\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/MCRF4XX\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/MODBUS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/NRSC-5\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/OPENSAFETY-A\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/OPENSAFETY-B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/PROFIBUS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/RIELLO\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/SPI-FUJITSU\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/T10-DIF\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/TELEDISK\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/TMS37157\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/UMTS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/USB\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/V-41-LSB\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/V-41-MSB\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/VERIFONE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/X-25\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/XMODEM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-16/ZMODEM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-17/CAN-FD\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-21/CAN-FD\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/BLE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/FLEXRAY-A\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/FLEXRAY-B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/INTERLAKEN\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/LTE-A\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/LTE-B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/OPENPGP\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-24/OS-9\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-30/CDMA\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-31/PHILIPS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/AAL5\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/ADCCP\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/AIXM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/AUTOSAR\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/BASE91-C\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/BASE91-D\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/BZIP2\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/C\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/CASTAGNOLI\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/CD-ROM-EDC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/CKSUM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/D\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/DECT-B\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/INTERLAKEN\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/ISCSI\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/ISO-HDLC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/JAMCRC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/MEF\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/MPEG-2\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/NVME\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/PKZIP\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/POSIX\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/Q\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/SATA\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/V-42\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/XFER\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-32/XZ\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-40/GSM\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/ECMA-182\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/GO-ECMA\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/GO-ISO\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/MS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/NVME\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/REDIS\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/WE\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-64/XZ\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    },\n                    {\n                        name: \"CRC-82/DARC\",\n                        off: [1, 2, 3, 4, 5, 6]\n                    }\n                ]\n            },\n            {\n                name: \"Width (bits)\",\n                type: \"toggleString\",\n                value: \"0\",\n                toggleValues: [\"Decimal\"]\n            },\n            {\n                name: \"Polynomial\",\n                type: \"toggleString\",\n                value: \"0\",\n                toggleValues: [\"Hex\"]\n            },\n            {\n                name: \"Initialization\",\n                type: \"toggleString\",\n                value: \"0\",\n                toggleValues: [\"Hex\"]\n            },\n            {\n                name: \"Reflect input\",\n                type: \"option\",\n                value: [\"True\", \"False\"]\n            },\n            {\n                name: \"Reflect output\",\n                type: \"option\",\n                value: [\"True\", \"False\"]\n            },\n            {\n                name: \"Xor Output\",\n                type: \"toggleString\",\n                value: \"0\",\n                toggleValues: [\"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * Reverse the order of bits in a number\n     *\n     * @param {BigInt} data\n     * @param {BigInt} reflect\n     */\n    reflectData(data, reflect) {\n        let value = 0n;\n        for (let bit = 0n; bit < reflect; bit++) {\n            if ((data & 1n) === 1n) {\n                value |= (1n << ((reflect - 1n) - bit));\n            }\n            data >>= 1n;\n        }\n        return value;\n    }\n\n    /**\n     * Performs the CRC Checksum calculation bit per bit without acceleration\n     *\n     * @param {BigInt} width\n     * @param {ArrayBuffer} input\n     * @param {BigInt} poly\n     * @param {BigInt} remainder\n     * @param {boolean} reflectIn\n     * @param {boolean} reflectOut\n     * @param {BigInt} xorOut\n     */\n    calculateCrcBitPerBit(width, input, poly, remainder, reflectIn, reflectOut, xorOut) {\n        const TOP_BIT = 1n << (width - 1n);\n        const MASK = (1n << width) - 1n;\n\n        for (let byte of input) {\n            byte = BigInt(byte);\n            if (reflectIn) {\n                byte = this.reflectData(byte, 8n);\n            }\n\n            for (let i = 0x80n; i !== 0n; i >>= 1n) {\n                let bit = remainder & TOP_BIT;\n\n                remainder = (remainder << 1n) & MASK;\n\n                if ((byte & i) !== 0n) {\n                    bit ^= TOP_BIT;\n                }\n\n                if (bit !== 0n) {\n                    remainder ^= poly;\n                }\n            }\n        }\n\n        if (reflectOut) {\n            remainder = this.reflectData(remainder, width);\n        }\n\n        return remainder ^ xorOut;\n    }\n\n    /**\n     * Generates the necessary table to speed up the calculation\n     *\n     * @param {BigInt} width\n     * @param {BigInt} poly\n     * @param {BigInt} MASK\n     * @param {BigInt} TOP_BIT\n     */\n    generateTable(width, poly, MASK, TOP_BIT) {\n        const table = new Array(256n);\n        for (let byte = 0n; byte < 256n; byte++) {\n            let value = ((byte << width - 8n) & MASK);\n            for (let bit = 0n; bit < 8n; bit++) {\n                value = (value & TOP_BIT) === 0n ?\n                    ((value << 1n) & MASK) :\n                    ((value << 1n) & MASK) ^ poly;\n            }\n            table[byte] = value;\n        }\n        return table;\n    }\n\n    /**\n     * Performs the CRC Checksum calculation byte per byte using a computed table to accelerate it\n     *\n     * @param {BigInt} width\n     * @param {ArrayBuffer} input\n     * @param {BigInt} poly\n     * @param {BigInt} remainder\n     * @param {boolean} reflectIn\n     * @param {boolean} reflectOut\n     * @param {BigInt} xorOut\n     */\n    calculateCrcBytePerByte(width, input, poly, remainder, reflectIn, reflectOut, xorOut) {\n        const TOP_BIT = 1n << (width - 1n);\n        const MASK = (1n << width) - 1n;\n        const TABLE = this.generateTable(width, poly, MASK, TOP_BIT);\n\n        for (let byte of input) {\n            byte = BigInt(byte);\n            if (reflectIn) {\n                byte = this.reflectData(byte, 8n);\n            }\n            remainder ^= (byte << width - 8n) & MASK;\n\n            const INDEX = remainder >> width - 8n;\n            remainder = (remainder << 8n) & MASK;\n            remainder ^= TABLE[INDEX];\n        }\n\n        if (reflectOut) {\n            remainder = this.reflectData(remainder, width);\n        }\n        return remainder ^ xorOut;\n    }\n\n    /**\n     * Calculates the CRC Checksum using Bigint (https://developer.mozilla.org/en-US/docs/Glossary/BigInt)\n     *\n     * @param {BigInt} width\n     * @param {ArrayBuffer} input\n     * @param {BigInt} poly\n     * @param {BigInt} init\n     * @param {boolean} reflectIn\n     * @param {boolean} reflectOut\n     * @param {BigInt} xorOut\n     */\n    crc(width, input, poly, init, reflectIn, reflectOut, xorOut) {\n        const VALUE = width < 8n ?\n            this.calculateCrcBitPerBit(width, input, poly, init, reflectIn, reflectOut, xorOut) :\n            this.calculateCrcBytePerByte(width, input, poly, init, reflectIn, reflectOut, xorOut);\n\n        return VALUE.toString(16).padStart(Math.ceil(Number(width) / 4), \"0\");\n    }\n\n    /**\n     * Validates user input to perform a custom CRC\n     *\n     * @param {Object} widthObject\n     * @param {ArrayBuffer} input\n     * @param {Object} polyObject\n     * @param {Object} initObject\n     * @param {Object} reflectInObject\n     * @param {Object} reflectOutObject\n     * @param {Object} xorOutObject\n     */\n    custom(widthObject, input, polyObject, initObject, reflectInObject, reflectOutObject, xorOutObject) {\n        try {\n            const width = BigInt(widthObject.string);\n            const poly = BigInt(\"0x\" + polyObject.string);\n            const init = BigInt(\"0x\" + initObject.string);\n            const reflectIn = reflectInObject === \"True\";\n            const reflectOut = reflectOutObject === \"True\";\n            const xorOut = BigInt(\"0x\" + xorOutObject.string);\n\n            return this.crc(width, input, poly, init, reflectIn, reflectOut, xorOut);\n        } catch (error) {\n            throw new OperationError(\"Invalid custom CRC arguments\");\n        }\n    }\n\n    /**\n     * Calculation of all known CRCs. Names and constants extracted from https://reveng.sourceforge.io/crc-catalogue/all.htm\n     *\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const algorithm = args[0];\n        input = new Uint8Array(input);\n\n        switch (algorithm) {\n            case \"Custom\":                   return this.custom(args[1], input, args[2], args[3], args[4], args[5], args[6]);\n            case \"CRC-3/GSM\":                return this.crc(3n, input, 0x3n, 0x0n, false, false, 0x7n);\n            case \"CRC-3/ROHC\":               return this.crc(3n, input, 0x3n, 0x7n, true,  true,  0x0n);\n            case \"CRC-4/G-704\":              return this.crc(4n, input, 0x3n, 0x0n, true,  true,  0x0n);\n            case \"CRC-4/INTERLAKEN\":         return this.crc(4n, input, 0x3n, 0xFn, false, false, 0xFn);\n            case \"CRC-4/ITU\":                return this.crc(4n, input, 0x3n, 0x0n, true,  true,  0x0n);\n            case \"CRC-5/EPC\":                return this.crc(5n, input, 0x09n, 0x09n, false, false, 0x00n);\n            case \"CRC-5/EPC-C1G2\":           return this.crc(5n, input, 0x09n, 0x09n, false, false, 0x00n);\n            case \"CRC-5/G-704\":              return this.crc(5n, input, 0x15n, 0x00n, true,  true,  0x00n);\n            case \"CRC-5/ITU\":                return this.crc(5n, input, 0x15n, 0x00n, true,  true,  0x00n);\n            case \"CRC-5/USB\":                return this.crc(5n, input, 0x05n, 0x1Fn, true,  true,  0x1Fn);\n            case \"CRC-6/CDMA2000-A\":         return this.crc(6n, input, 0x27n, 0x3Fn, false, false, 0x00n);\n            case \"CRC-6/CDMA2000-B\":         return this.crc(6n, input, 0x07n, 0x3Fn, false, false, 0x00n);\n            case \"CRC-6/DARC\":               return this.crc(6n, input, 0x19n, 0x00n, true,  true,  0x00n);\n            case \"CRC-6/G-704\":              return this.crc(6n, input, 0x03n, 0x00n, true,  true,  0x00n);\n            case \"CRC-6/GSM\":                return this.crc(6n, input, 0x2Fn, 0x00n, false, false, 0x3Fn);\n            case \"CRC-6/ITU\":                return this.crc(6n, input, 0x03n, 0x00n, true,  true,  0x00n);\n            case \"CRC-7/MMC\":                return this.crc(7n, input, 0x09n, 0x00n, false, false, 0x00n);\n            case \"CRC-7/ROHC\":               return this.crc(7n, input, 0x4Fn, 0x7Fn, true,  true,  0x00n);\n            case \"CRC-7/UMTS\":               return this.crc(7n, input, 0x45n, 0x00n, false, false, 0x00n);\n            case \"CRC-8\":                    return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x00n);\n            case \"CRC-8/8H2F\":               return this.crc(8n, input, 0x2Fn, 0xFFn, false, false, 0xFFn);\n            case \"CRC-8/AES\":                return this.crc(8n, input, 0x1Dn, 0xFFn, true,  true,  0x00n);\n            case \"CRC-8/AUTOSAR\":            return this.crc(8n, input, 0x2Fn, 0xFFn, false, false, 0xFFn);\n            case \"CRC-8/BLUETOOTH\":          return this.crc(8n, input, 0xA7n, 0x00n, true,  true,  0x00n);\n            case \"CRC-8/CDMA2000\":           return this.crc(8n, input, 0x9Bn, 0xFFn, false, false, 0x00n);\n            case \"CRC-8/DARC\":               return this.crc(8n, input, 0x39n, 0x00n, true,  true,  0x00n);\n            case \"CRC-8/DVB-S2\":             return this.crc(8n, input, 0xD5n, 0x00n, false, false, 0x00n);\n            case \"CRC-8/EBU\":                return this.crc(8n, input, 0x1Dn, 0xFFn, true,  true,  0x00n);\n            case \"CRC-8/GSM-A\":              return this.crc(8n, input, 0x1Dn, 0x00n, false, false, 0x00n);\n            case \"CRC-8/GSM-B\":              return this.crc(8n, input, 0x49n, 0x00n, false, false, 0xFFn);\n            case \"CRC-8/HITAG\":              return this.crc(8n, input, 0x1Dn, 0xFFn, false, false, 0x00n);\n            case \"CRC-8/I-432-1\":            return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x55n);\n            case \"CRC-8/I-CODE\":             return this.crc(8n, input, 0x1Dn, 0xFDn, false, false, 0x00n);\n            case \"CRC-8/ITU\":                return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x55n);\n            case \"CRC-8/LTE\":                return this.crc(8n, input, 0x9Bn, 0x00n, false, false, 0x00n);\n            case \"CRC-8/MAXIM\":              return this.crc(8n, input, 0x31n, 0x00n, true,  true,  0x00n);\n            case \"CRC-8/MAXIM-DOW\":          return this.crc(8n, input, 0x31n, 0x00n, true,  true,  0x00n);\n            case \"CRC-8/MIFARE-MAD\":         return this.crc(8n, input, 0x1Dn, 0xC7n, false, false, 0x00n);\n            case \"CRC-8/NRSC-5\":             return this.crc(8n, input, 0x31n, 0xFFn, false, false, 0x00n);\n            case \"CRC-8/OPENSAFETY\":         return this.crc(8n, input, 0x2Fn, 0x00n, false, false, 0x00n);\n            case \"CRC-8/ROHC\":               return this.crc(8n, input, 0x07n, 0xFFn, true,  true,  0x00n);\n            case \"CRC-8/SAE-J1850\":          return this.crc(8n, input, 0x1Dn, 0xFFn, false, false, 0xFFn);\n            case \"CRC-8/SAE-J1850-ZERO\":     return this.crc(8n, input, 0x1Dn, 0x00n, false, false, 0x00n);\n            case \"CRC-8/SMBUS\":              return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x00n);\n            case \"CRC-8/TECH-3250\":          return this.crc(8n, input, 0x1Dn, 0xFFn, true,  true,  0x00n);\n            case \"CRC-8/WCDMA\":              return this.crc(8n, input, 0x9Bn, 0x00n, true,  true,  0x00n);\n            case \"CRC-10/ATM\":               return this.crc(10n, input, 0x233n, 0x000n, false, false, 0x000n);\n            case \"CRC-10/CDMA2000\":          return this.crc(10n, input, 0x3D9n, 0x3FFn, false, false, 0x000n);\n            case \"CRC-10/GSM\":               return this.crc(10n, input, 0x175n, 0x000n, false, false, 0x3FFn);\n            case \"CRC-10/I-610\":             return this.crc(10n, input, 0x233n, 0x000n, false, false, 0x000n);\n            case \"CRC-11/FLEXRAY\":           return this.crc(11n, input, 0x385n, 0x01An, false, false, 0x000n);\n            case \"CRC-11/UMTS\":              return this.crc(11n, input, 0x307n, 0x000n, false, false, 0x000n);\n            case \"CRC-12/3GPP\":              return this.crc(12n, input, 0x80Fn, 0x000n, false, true,  0x000n);\n            case \"CRC-12/CDMA2000\":          return this.crc(12n, input, 0xF13n, 0xFFFn, false, false, 0x000n);\n            case \"CRC-12/DECT\":              return this.crc(12n, input, 0x80Fn, 0x000n, false, false, 0x000n);\n            case \"CRC-12/GSM\":               return this.crc(12n, input, 0xD31n, 0x000n, false, false, 0xFFFn);\n            case \"CRC-12/UMTS\":              return this.crc(12n, input, 0x80Fn, 0x000n, false, true,  0x000n);\n            case \"CRC-13/BBC\":               return this.crc(13n, input, 0x1CF5n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-14/DARC\":              return this.crc(14n, input, 0x0805n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-14/GSM\":               return this.crc(14n, input, 0x202Dn, 0x0000n, false, false, 0x3FFFn);\n            case \"CRC-15/CAN\":               return this.crc(15n, input, 0x4599n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-15/MPT1327\":           return this.crc(15n, input, 0x6815n, 0x0000n, false, false, 0x0001n);\n            case \"CRC-16\":                   return this.crc(16n, input, 0x8005n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/A\":                 return this.crc(16n, input, 0x1021n, 0xC6C6n, true,  true,  0x0000n);\n            case \"CRC-16/ACORN\":             return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/ARC\":               return this.crc(16n, input, 0x8005n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/AUG-CCITT\":         return this.crc(16n, input, 0x1021n, 0x1D0Fn, false, false, 0x0000n);\n            case \"CRC-16/AUTOSAR\":           return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0x0000n);\n            case \"CRC-16/B\":                 return this.crc(16n, input, 0x1021n, 0xFFFFn, true,  true,  0xFFFFn);\n            case \"CRC-16/BLUETOOTH\":         return this.crc(16n, input, 0x1021n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/BUYPASS\":           return this.crc(16n, input, 0x8005n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/CCITT\":             return this.crc(16n, input, 0x1021n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/CCITT-FALSE\":       return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0x0000n);\n            case \"CRC-16/CCITT-TRUE\":        return this.crc(16n, input, 0x1021n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/CCITT-ZERO\":        return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/CDMA2000\":          return this.crc(16n, input, 0xC867n, 0xFFFFn, false, false, 0x0000n);\n            case \"CRC-16/CMS\":               return this.crc(16n, input, 0x8005n, 0xFFFFn, false, false, 0x0000n);\n            case \"CRC-16/DARC\":              return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn);\n            case \"CRC-16/DDS-110\":           return this.crc(16n, input, 0x8005n, 0x800Dn, false, false, 0x0000n);\n            case \"CRC-16/DECT-R\":            return this.crc(16n, input, 0x0589n, 0x0000n, false, false, 0x0001n);\n            case \"CRC-16/DECT-X\":            return this.crc(16n, input, 0x0589n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/DNP\":               return this.crc(16n, input, 0x3D65n, 0x0000n, true,  true,  0xFFFFn);\n            case \"CRC-16/EN-13757\":          return this.crc(16n, input, 0x3D65n, 0x0000n, false, false, 0xFFFFn);\n            case \"CRC-16/EPC\":               return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn);\n            case \"CRC-16/EPC-C1G2\":          return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn);\n            case \"CRC-16/GENIBUS\":           return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn);\n            case \"CRC-16/GSM\":               return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0xFFFFn);\n            case \"CRC-16/I-CODE\":            return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn);\n            case \"CRC-16/IBM\":               return this.crc(16n, input, 0x8005n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/IBM-3740\":          return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0x0000n);\n            case \"CRC-16/IBM-SDLC\":          return this.crc(16n, input, 0x1021n, 0xFFFFn, true,  true,  0xFFFFn);\n            case \"CRC-16/IEC-61158-2\":       return this.crc(16n, input, 0x1DCFn, 0xFFFFn, false, false, 0xFFFFn);\n            case \"CRC-16/ISO-HDLC\":          return this.crc(16n, input, 0x1021n, 0xFFFFn, true,  true,  0xFFFFn);\n            case \"CRC-16/ISO-IEC-14443-3-A\": return this.crc(16n, input, 0x1021n, 0xC6C6n, true,  true,  0x0000n);\n            case \"CRC-16/ISO-IEC-14443-3-B\": return this.crc(16n, input, 0x1021n, 0xFFFFn, true,  true,  0xFFFFn);\n            case \"CRC-16/KERMIT\":            return this.crc(16n, input, 0x1021n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/LHA\":               return this.crc(16n, input, 0x8005n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/LJ1200\":            return this.crc(16n, input, 0x6F63n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/LTE\":               return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/M17\":               return this.crc(16n, input, 0x5935n, 0xFFFFn, false, false, 0x0000n);\n            case \"CRC-16/MAXIM\":             return this.crc(16n, input, 0x8005n, 0x0000n, true,  true,  0xFFFFn);\n            case \"CRC-16/MAXIM-DOW\":         return this.crc(16n, input, 0x8005n, 0x0000n, true,  true,  0xFFFFn);\n            case \"CRC-16/MCRF4XX\":           return this.crc(16n, input, 0x1021n, 0xFFFFn, true,  true,  0x0000n);\n            case \"CRC-16/MODBUS\":            return this.crc(16n, input, 0x8005n, 0xFFFFn, true,  true,  0x0000n);\n            case \"CRC-16/NRSC-5\":            return this.crc(16n, input, 0x080Bn, 0xFFFFn, true,  true,  0x0000n);\n            case \"CRC-16/OPENSAFETY-A\":      return this.crc(16n, input, 0x5935n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/OPENSAFETY-B\":      return this.crc(16n, input, 0x755Bn, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/PROFIBUS\":          return this.crc(16n, input, 0x1DCFn, 0xFFFFn, false, false, 0xFFFFn);\n            case \"CRC-16/RIELLO\":            return this.crc(16n, input, 0x1021n, 0xB2AAn, true,  true,  0x0000n);\n            case \"CRC-16/SPI-FUJITSU\":       return this.crc(16n, input, 0x1021n, 0x1D0Fn, false, false, 0x0000n);\n            case \"CRC-16/T10-DIF\":           return this.crc(16n, input, 0x8BB7n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/TELEDISK\":          return this.crc(16n, input, 0xA097n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/TMS37157\":          return this.crc(16n, input, 0x1021n, 0x89ECn, true,  true,  0x0000n);\n            case \"CRC-16/UMTS\":              return this.crc(16n, input, 0x8005n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/USB\":               return this.crc(16n, input, 0x8005n, 0xFFFFn, true,  true,  0xFFFFn);\n            case \"CRC-16/V-41-LSB\":          return this.crc(16n, input, 0x1021n, 0x0000n, true,  true,  0x0000n);\n            case \"CRC-16/V-41-MSB\":          return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/VERIFONE\":          return this.crc(16n, input, 0x8005n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/X-25\":              return this.crc(16n, input, 0x1021n, 0xFFFFn, true,  true,  0xFFFFn);\n            case \"CRC-16/XMODEM\":            return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-16/ZMODEM\":            return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n);\n            case \"CRC-17/CAN-FD\":            return this.crc(17n, input, 0x1685Bn, 0x00000n, false, false, 0x00000n);\n            case \"CRC-21/CAN-FD\":            return this.crc(21n, input, 0x102899n, 0x000000n, false, false, 0x000000n);\n            case \"CRC-24/BLE\":               return this.crc(24n, input, 0x00065Bn, 0x555555n, true,  true,  0x000000n);\n            case \"CRC-24/FLEXRAY-A\":         return this.crc(24n, input, 0x5D6DCBn, 0xFEDCBAn, false, false, 0x000000n);\n            case \"CRC-24/FLEXRAY-B\":         return this.crc(24n, input, 0x5D6DCBn, 0xABCDEFn, false, false, 0x000000n);\n            case \"CRC-24/INTERLAKEN\":        return this.crc(24n, input, 0x328B63n, 0xFFFFFFn, false, false, 0xFFFFFFn);\n            case \"CRC-24/LTE-A\":             return this.crc(24n, input, 0x864CFBn, 0x000000n, false, false, 0x000000n);\n            case \"CRC-24/LTE-B\":             return this.crc(24n, input, 0x800063n, 0x000000n, false, false, 0x000000n);\n            case \"CRC-24/OPENPGP\":           return this.crc(24n, input, 0x864CFBn, 0xB704CEn, false, false, 0x000000n);\n            case \"CRC-24/OS-9\":              return this.crc(24n, input, 0x800063n, 0xFFFFFFn, false, false, 0xFFFFFFn);\n            case \"CRC-30/CDMA\":              return this.crc(30n, input, 0x2030B9C7n, 0x3FFFFFFFn, false, false, 0x3FFFFFFFn);\n            case \"CRC-31/PHILIPS\":           return this.crc(31n, input, 0x04C11DB7n, 0x7FFFFFFFn, false, false, 0x7FFFFFFFn);\n            case \"CRC-32\":                   return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/AAL5\":              return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0xFFFFFFFFn);\n            case \"CRC-32/ADCCP\":             return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/AIXM\":              return this.crc(32n, input, 0x814141ABn, 0x00000000n, false, false, 0x00000000n);\n            case \"CRC-32/AUTOSAR\":           return this.crc(32n, input, 0xF4ACFB13n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/BASE91-C\":          return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/BASE91-D\":          return this.crc(32n, input, 0xA833982Bn, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/BZIP2\":             return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0xFFFFFFFFn);\n            case \"CRC-32/C\":                 return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/CASTAGNOLI\":        return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/CD-ROM-EDC\":        return this.crc(32n, input, 0x8001801Bn, 0x00000000n, true,  true,  0x00000000n);\n            case \"CRC-32/CKSUM\":             return this.crc(32n, input, 0x04C11DB7n, 0x00000000n, false, false, 0xFFFFFFFFn);\n            case \"CRC-32/D\":                 return this.crc(32n, input, 0xA833982Bn, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/DECT-B\":            return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0xFFFFFFFFn);\n            case \"CRC-32/INTERLAKEN\":        return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/ISCSI\":             return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/ISO-HDLC\":          return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/JAMCRC\":            return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true,  true,  0x00000000n);\n            case \"CRC-32/MEF\":               return this.crc(32n, input, 0x741B8CD7n, 0xFFFFFFFFn, true,  true,  0x00000000n);\n            case \"CRC-32/MPEG-2\":            return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0x00000000n);\n            case \"CRC-32/NVME\":              return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/PKZIP\":             return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/POSIX\":             return this.crc(32n, input, 0x04C11DB7n, 0x00000000n, false, false, 0xFFFFFFFFn);\n            case \"CRC-32/Q\":                 return this.crc(32n, input, 0x814141ABn, 0x00000000n, false, false, 0x00000000n);\n            case \"CRC-32/SATA\":              return this.crc(32n, input, 0x04C11DB7n, 0x52325032n, false, false, 0x00000000n);\n            case \"CRC-32/V-42\":              return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-32/XFER\":              return this.crc(32n, input, 0x000000AFn, 0x00000000n, false, false, 0x00000000n);\n            case \"CRC-32/XZ\":                return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true,  true,  0xFFFFFFFFn);\n            case \"CRC-40/GSM\":               return this.crc(40n, input, 0x0004820009n, 0x0000000000n, false, false, 0xFFFFFFFFFFn);\n            case \"CRC-64/ECMA-182\":          return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0x0000000000000000n, false, false, 0x0000000000000000n);\n            case \"CRC-64/GO-ECMA\":           return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0xFFFFFFFFFFFFFFFFn, true,  true,  0xFFFFFFFFFFFFFFFFn);\n            case \"CRC-64/GO-ISO\":            return this.crc(64n, input, 0x000000000000001Bn, 0xFFFFFFFFFFFFFFFFn, true,  true,  0xFFFFFFFFFFFFFFFFn);\n            case \"CRC-64/MS\":                return this.crc(64n, input, 0x259C84CBA6426349n, 0xFFFFFFFFFFFFFFFFn, true,  true,  0x0000000000000000n);\n            case \"CRC-64/NVME\":              return this.crc(64n, input, 0xAD93D23594C93659n, 0xFFFFFFFFFFFFFFFFn, true,  true,  0xFFFFFFFFFFFFFFFFn);\n            case \"CRC-64/REDIS\":             return this.crc(64n, input, 0xAD93D23594C935A9n, 0x0000000000000000n, true,  true,  0x0000000000000000n);\n            case \"CRC-64/WE\":                return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0xFFFFFFFFFFFFFFFFn, false, false, 0xFFFFFFFFFFFFFFFFn);\n            case \"CRC-64/XZ\":                return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0xFFFFFFFFFFFFFFFFn, true,  true,  0xFFFFFFFFFFFFFFFFn);\n            case \"CRC-82/DARC\":              return this.crc(82n, input, 0x0308C0111011401440411n, 0x000000000000000000000n, true, true, 0x000000000000000000000n);\n            default:                         throw new OperationError(\"Unknown checksum algorithm\");\n        }\n    }\n\n}\n\nexport default CRCChecksum;\n"
  },
  {
    "path": "src/core/operations/CSSBeautify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport vkbeautify from \"vkbeautify\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * CSS Beautify operation\n */\nclass CSSBeautify extends Operation {\n\n    /**\n     * CSSBeautify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CSS Beautify\";\n        this.module = \"Code\";\n        this.description = \"Indents and prettifies Cascading Style Sheets (CSS) code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Indent string\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\t\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const indentStr = args[0];\n        return vkbeautify.css(input, indentStr);\n    }\n\n}\n\nexport default CSSBeautify;\n"
  },
  {
    "path": "src/core/operations/CSSMinify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport vkbeautify from \"vkbeautify\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * CSS Minify operation\n */\nclass CSSMinify extends Operation {\n\n    /**\n     * CSSMinify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CSS Minify\";\n        this.module = \"Code\";\n        this.description = \"Compresses Cascading Style Sheets (CSS) code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Preserve comments\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const preserveComments = args[0];\n        return vkbeautify.cssmin(input, preserveComments);\n    }\n\n}\n\nexport default CSSMinify;\n"
  },
  {
    "path": "src/core/operations/CSSSelector.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport xmldom from \"@xmldom/xmldom\";\nimport nwmatcher from \"nwmatcher\";\n\n/**\n * CSS selector operation\n */\nclass CSSSelector extends Operation {\n\n    /**\n     * CSSSelector constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CSS selector\";\n        this.module = \"Code\";\n        this.description = \"Extract information from an HTML document with a CSS selector\";\n        this.infoURL = \"https://wikipedia.org/wiki/Cascading_Style_Sheets#Selector\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"CSS selector\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\n\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [query, delimiter] = args,\n            parser = new xmldom.DOMParser();\n        let dom,\n            result;\n\n        if (!query.length || !input.length) {\n            return \"\";\n        }\n\n        try {\n            dom = parser.parseFromString(input);\n        } catch (err) {\n            throw new OperationError(\"Invalid input HTML.\");\n        }\n\n        try {\n            const matcher = nwmatcher({document: dom});\n            result = matcher.select(query, dom);\n        } catch (err) {\n            throw new OperationError(\"Invalid CSS Selector. Details:\\n\" + err.message);\n        }\n\n        const nodeToString = function(node) {\n            return node.toString();\n            /* xmldom does not return the outerHTML value.\n            switch (node.nodeType) {\n                case node.ELEMENT_NODE: return node.outerHTML;\n                case node.ATTRIBUTE_NODE: return node.value;\n                case node.TEXT_NODE: return node.wholeText;\n                case node.COMMENT_NODE: return node.data;\n                case node.DOCUMENT_NODE: return node.outerHTML;\n                default: throw new Error(\"Unknown Node Type: \" + node.nodeType);\n            }*/\n        };\n\n        return result\n            .map(nodeToString)\n            .join(delimiter);\n    }\n\n}\n\nexport default CSSSelector;\n"
  },
  {
    "path": "src/core/operations/CSVToJSON.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * CSV to JSON operation\n */\nclass CSVToJSON extends Operation {\n\n    /**\n     * CSVToJSON constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CSV to JSON\";\n        this.module = \"Default\";\n        this.description = \"Converts a CSV file to JSON format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Comma-separated_values\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.args = [\n            {\n                name: \"Cell delimiters\",\n                type: \"binaryShortString\",\n                value: \",\"\n            },\n            {\n                name: \"Row delimiters\",\n                type: \"binaryShortString\",\n                value: \"\\\\r\\\\n\"\n            },\n            {\n                name: \"Format\",\n                type: \"option\",\n                value: [\"Array of dictionaries\", \"Array of arrays\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        const [cellDelims, rowDelims, format] = args;\n        let json, header;\n\n        try {\n            json = Utils.parseCSV(input, cellDelims.split(\"\"), rowDelims.split(\"\"));\n        } catch (err) {\n            throw new OperationError(\"Unable to parse CSV: \" + err);\n        }\n\n        switch (format) {\n            case \"Array of dictionaries\":\n                header = json[0];\n                return json.slice(1).map(row => {\n                    const obj = {};\n                    header.forEach((h, i) => {\n                        obj[h] = row[i];\n                    });\n                    return obj;\n                });\n            case \"Array of arrays\":\n            default:\n                return json;\n        }\n    }\n\n}\n\nexport default CSVToJSON;\n"
  },
  {
    "path": "src/core/operations/CTPH.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport ctphjs from \"ctph.js\";\n\n/**\n * CTPH operation\n */\nclass CTPH extends Operation {\n\n    /**\n     * CTPH constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CTPH\";\n        this.module = \"Crypto\";\n        this.description = \"Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'.\";\n        this.infoURL = \"https://forensics.wiki/context_triggered_piecewise_hashing/\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return ctphjs.digest(input);\n    }\n\n}\n\nexport default CTPH;\n"
  },
  {
    "path": "src/core/operations/CaesarBoxCipher.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Caesar Box Cipher operation\n */\nclass CaesarBoxCipher extends Operation {\n\n    /**\n     * CaesarBoxCipher constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Caesar Box Cipher\";\n        this.module = \"Ciphers\";\n        this.description = \"Caesar Box is a transposition cipher used in the Roman Empire, in which letters of the message are written in rows in a square (or a rectangle) and then, read by column.\";\n        this.infoURL = \"https://www.dcode.fr/caesar-box-cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Box Height\",\n                type: \"number\",\n                value: 1\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const tableHeight = args[0];\n        const tableWidth = Math.ceil(input.length / tableHeight);\n        while (input.indexOf(\" \") !== -1)\n            input = input.replace(\" \", \"\");\n        for (let i = 0; i < (tableHeight * tableWidth) - input.length; i++) {\n            input += \"\\x00\";\n        }\n        let result = \"\";\n        for (let i = 0; i < tableHeight; i++) {\n            for (let j = i; j < input.length; j += tableHeight) {\n                if (input.charAt(j) !== \"\\x00\") {\n                    result += input.charAt(j);\n                }\n            }\n        }\n        return result;\n    }\n\n}\n\nexport default CaesarBoxCipher;\n"
  },
  {
    "path": "src/core/operations/CaretMdecode.mjs",
    "content": "/**\n * @author tedk [tedk@ted.do]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Caret/M-decode operation\n *\n * https://gist.githubusercontent.com/JaHIY/3c91bbf7bea5661e6abfbd1349ee81a2/raw/c7b480e9ff24bcb8f5287a8a8a2dcb9bf5628506/decode_m_notation.cpp\n */\nclass CaretMdecode extends Operation {\n\n    /**\n     * CaretMdecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Caret/M-decode\";\n        this.module = \"Default\";\n        this.description = \"Decodes caret or M-encoded strings, i.e. ^M turns into a newline, M-^] turns into 0x9d. Sources such as `cat -v`.\\n\\nPlease be aware that when using `cat -v` ^_ (caret-underscore) will not be encoded, but represents a valid encoding (namely that of 0x1f).\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/Caret_notation\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n\n        const bytes = [];\n\n        let prev = \"\";\n\n        for (let i = 0; i < input.length; i++) {\n\n            const charCode = input.charCodeAt(i);\n            const curChar = input.charAt(i);\n\n            if (prev === \"M-^\") {\n                if (charCode > 63 && charCode <= 95) {\n                    bytes.push(charCode + 64);\n                } else if (charCode === 63) {\n                    bytes.push(255);\n                } else {\n                    bytes.push(77, 45, 94, charCode);\n                }\n                prev = \"\";\n            } else if (prev === \"M-\") {\n                if (curChar === \"^\") {\n                    prev = prev + \"^\";\n                } else if (charCode >= 32 && charCode <= 126) {\n                    bytes.push(charCode + 128);\n                    prev = \"\";\n                } else {\n                    bytes.push(77, 45, charCode);\n                    prev = \"\";\n                }\n            } else if (prev === \"M\") {\n                if (curChar === \"-\") {\n                    prev = prev + \"-\";\n                } else {\n                    bytes.push(77, charCode);\n                    prev = \"\";\n                }\n            } else if (prev === \"^\") {\n                if (charCode > 63 && charCode <= 126) {\n                    bytes.push(charCode - 64);\n                } else if (charCode === 63) {\n                    bytes.push(127);\n                } else {\n                    bytes.push(94, charCode);\n                }\n                prev = \"\";\n            } else {\n                if (curChar === \"M\") {\n                    prev = \"M\";\n                } else if (curChar === \"^\") {\n                    prev = \"^\";\n                } else {\n                    bytes.push(charCode);\n                }\n            }\n\n        }\n        return bytes;\n    }\n\n}\n\nexport default CaretMdecode;\n"
  },
  {
    "path": "src/core/operations/CartesianProduct.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Set cartesian product operation\n */\nclass CartesianProduct extends Operation {\n\n    /**\n     * Cartesian Product constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Cartesian Product\";\n        this.module = \"Default\";\n        this.description = \"Calculates the cartesian product of multiple sets of data, returning all possible combinations.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Cartesian_product\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Sample delimiter\",\n                type: \"binaryString\",\n                value: \"\\\\n\\\\n\"\n            },\n            {\n                name: \"Item delimiter\",\n                type: \"binaryString\",\n                value: \",\"\n            },\n        ];\n    }\n\n    /**\n     * Validate input length\n     *\n     * @param {Object[]} sets\n     * @throws {OperationError} if fewer than 2 sets\n     */\n    validateSampleNumbers(sets) {\n        if (!sets || sets.length < 2) {\n            throw new OperationError(\"Incorrect number of sets, perhaps you\" +\n                \" need to modify the sample delimiter or add more samples?\");\n        }\n    }\n\n    /**\n     * Run the product operation\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     * @throws {OperationError}\n     */\n    run(input, args) {\n        [this.sampleDelim, this.itemDelimiter] = args;\n        const sets = input.split(this.sampleDelim);\n\n        this.validateSampleNumbers(sets);\n\n        return this.runCartesianProduct(...sets.map(s => s.split(this.itemDelimiter)));\n    }\n\n    /**\n    * Return the cartesian product of the two inputted sets.\n    *\n    * @param {Object[]} a\n    * @param {Object[]} b\n    * @param {Object[]} c\n    * @returns {string}\n    */\n    runCartesianProduct(a, b, ...c) {\n        /**\n         * https://stackoverflow.com/a/43053803/7200497\n         * @returns {Object[]}\n         */\n        const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));\n        /**\n         * https://stackoverflow.com/a/43053803/7200497\n         * @returns {Object[][]}\n         */\n        const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);\n\n        return cartesian(a, b, ...c)\n            .map(set => `(${set.join(\",\")})`)\n            .join(this.itemDelimiter);\n    }\n}\n\nexport default CartesianProduct;\n"
  },
  {
    "path": "src/core/operations/CetaceanCipherDecode.mjs",
    "content": "/**\n * @author dolphinOnKeys [robin@weird.io]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Cetacean Cipher Decode operation\n */\nclass CetaceanCipherDecode extends Operation {\n\n    /**\n     * CetaceanCipherDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Cetacean Cipher Decode\";\n        this.module = \"Ciphers\";\n        this.description = \"Decode Cetacean Cipher input. <br/><br/>e.g. <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code> becomes <code>hi</code>\";\n        this.infoURL = \"https://hitchhikers.fandom.com/wiki/Dolphins\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n\n        this.checks = [\n            {\n                pattern: \"^(?:[eE]{16,})(?: [eE]{16,})*$\",\n                flags: \"\",\n                args: []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const binaryArray = [];\n        for (const char of input) {\n            if (char === \" \") {\n                binaryArray.push(...[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]);\n            } else {\n                binaryArray.push(char === \"e\" ? 1 : 0);\n            }\n        }\n\n        const byteArray = [];\n\n        for (let i = 0;  i < binaryArray.length; i += 16) {\n            byteArray.push(binaryArray.slice(i, i + 16).join(\"\"));\n        }\n\n        return byteArray.map(byte =>\n            String.fromCharCode(parseInt(byte, 2))\n        ).join(\"\");\n    }\n}\n\nexport default CetaceanCipherDecode;\n"
  },
  {
    "path": "src/core/operations/CetaceanCipherEncode.mjs",
    "content": "/**\n * @author dolphinOnKeys [robin@weird.io]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {toBinary} from \"../lib/Binary.mjs\";\n\n/**\n * Cetacean Cipher Encode operation\n */\nclass CetaceanCipherEncode extends Operation {\n\n    /**\n     * CetaceanCipherEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Cetacean Cipher Encode\";\n        this.module = \"Ciphers\";\n        this.description = \"Converts any input into Cetacean Cipher. <br/><br/>e.g. <code>hi</code> becomes <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code>\";\n        this.infoURL = \"https://hitchhikers.fandom.com/wiki/Dolphins\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const result = [];\n        const charArray = input.split(\"\");\n\n        charArray.map(character => {\n            if (character === \" \") {\n                result.push(character);\n            } else {\n                const binaryArray = toBinary(character.charCodeAt(0), \"None\", 16).split(\"\");\n                result.push(binaryArray.map(str => str === \"1\" ? \"e\" : \"E\").join(\"\"));\n            }\n        });\n\n        return result.join(\"\");\n    }\n}\n\nexport default CetaceanCipherEncode;\n"
  },
  {
    "path": "src/core/operations/ChaCha.mjs",
    "content": "/**\n * @author joostrijneveld [joost@joostrijneveld.nl]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\n\n/**\n * Computes the ChaCha block function\n *\n * @param {byteArray} key\n * @param {byteArray} nonce\n * @param {byteArray} counter\n * @param {integer} rounds\n * @returns {byteArray}\n */\nfunction chacha(key, nonce, counter, rounds) {\n    const tau = \"expand 16-byte k\";\n    const sigma = \"expand 32-byte k\";\n\n    let state, c;\n    if (key.length === 16) {\n        c = Utils.strToByteArray(tau);\n        state = c.concat(key).concat(key);\n    } else {\n        c = Utils.strToByteArray(sigma);\n        state = c.concat(key);\n    }\n    state = state.concat(counter).concat(nonce);\n\n    const x = Array();\n    for (let i = 0; i < 64; i += 4) {\n        x.push(Utils.byteArrayToInt(state.slice(i, i + 4), \"little\"));\n    }\n    const a = [...x];\n\n    /**\n     * Macro to compute a 32-bit rotate-left operation\n     *\n     * @param {integer} x\n     * @param {integer} n\n     * @returns {integer}\n     */\n    function ROL32(x, n) {\n        return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));\n    }\n\n    /**\n     * Macro to compute a single ChaCha quarterround operation\n     *\n     * @param {integer} x\n     * @param {integer} a\n     * @param {integer} b\n     * @param {integer} c\n     * @param {integer} d\n     * @returns {integer}\n     */\n    function quarterround(x, a, b, c, d) {\n        x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16);\n        x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12);\n        x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8);\n        x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7);\n    }\n\n    for (let i = 0; i < rounds / 2; i++)  {\n        quarterround(x, 0, 4,  8, 12);\n        quarterround(x, 1, 5,  9, 13);\n        quarterround(x, 2, 6, 10, 14);\n        quarterround(x, 3, 7, 11, 15);\n        quarterround(x, 0, 5, 10, 15);\n        quarterround(x, 1, 6, 11, 12);\n        quarterround(x, 2, 7,  8, 13);\n        quarterround(x, 3, 4,  9, 14);\n    }\n\n    for (let i = 0; i < 16; i++) {\n        x[i] = (x[i] + a[i]) & 0xFFFFFFFF;\n    }\n\n    let output = Array();\n    for (let i = 0; i < 16; i++) {\n        output = output.concat(Utils.intToByteArray(x[i], 4, \"little\"));\n    }\n    return output;\n}\n\n/**\n * ChaCha operation\n */\nclass ChaCha extends Operation {\n\n    /**\n     * ChaCha constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ChaCha\";\n        this.module = \"Ciphers\";\n        this.description = \"ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.<br><br><b>Key:</b> ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).<br><br><b>Counter:</b> ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Salsa20#ChaCha_variant\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Nonce\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\", \"Integer\"]\n            },\n            {\n                \"name\": \"Counter\",\n                \"type\": \"number\",\n                \"value\": 0,\n                \"min\": 0\n            },\n            {\n                \"name\": \"Rounds\",\n                \"type\": \"option\",\n                \"value\": [\"20\", \"12\", \"8\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            nonceType = args[1].option,\n            rounds = parseInt(args[3], 10),\n            inputType = args[4],\n            outputType = args[5];\n\n        if (key.length !== 16 && key.length !== 32) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes.\n\nChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`);\n        }\n\n        let counter, nonce, counterLength;\n        if (nonceType === \"Integer\") {\n            nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, \"little\");\n            counterLength = 4;\n        } else {\n            nonce = Utils.convertToByteArray(args[1].string, args[1].option);\n            if (!(nonce.length === 12 || nonce.length === 8)) {\n                throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.\n\nChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);\n            }\n            counterLength = 16 - nonce.length;\n        }\n        counter = Utils.intToByteArray(args[2], counterLength, \"little\");\n\n        const output = [];\n        input = Utils.convertToByteArray(input, inputType);\n\n        let counterAsInt = Utils.byteArrayToInt(counter, \"little\");\n        for (let i = 0; i < input.length; i += 64) {\n            counter = Utils.intToByteArray(counterAsInt, counterLength, \"little\");\n            const stream = chacha(key, nonce, counter, rounds);\n            for (let j = 0; j < 64 && i + j < input.length; j++) {\n                output.push(input[i + j] ^ stream[j]);\n            }\n            counterAsInt++;\n        }\n\n        if (outputType === \"Hex\") {\n            return toHex(output);\n        } else {\n            return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);\n        }\n    }\n\n    /**\n     * Highlight ChaCha\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        const inputType = args[4],\n            outputType = args[5];\n        if (inputType === \"Raw\" && outputType === \"Raw\") {\n            return pos;\n        }\n    }\n\n    /**\n     * Highlight ChaCha in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        const inputType = args[4],\n            outputType = args[5];\n        if (inputType === \"Raw\" && outputType === \"Raw\") {\n            return pos;\n        }\n    }\n\n}\n\nexport default ChaCha;\n"
  },
  {
    "path": "src/core/operations/ChangeIPFormat.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {fromHex} from \"../lib/Hex.mjs\";\n\n/**\n * Change IP format operation\n */\nclass ChangeIPFormat extends Operation {\n\n    /**\n     * ChangeIPFormat constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Change IP format\";\n        this.module = \"Default\";\n        this.description = \"Convert an IP address from one format to another, e.g. <code>172.20.23.54</code> to <code>ac141736</code>\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"Dotted Decimal\", \"Decimal\", \"Octal\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output format\",\n                \"type\": \"option\",\n                \"value\": [\"Dotted Decimal\", \"Decimal\", \"Octal\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inFormat, outFormat] = args,\n            lines = input.split(\"\\n\");\n        let output = \"\",\n            j = 0;\n\n        for (let i = 0; i < lines.length; i++) {\n            if (lines[i] === \"\") continue;\n            let baIp = [];\n            let octets;\n\n            if (inFormat === outFormat) {\n                output += lines[i] + \"\\n\";\n                continue;\n            }\n\n            // Convert to byte array IP from input format\n            switch (inFormat) {\n                case \"Dotted Decimal\":\n                    octets = lines[i].split(\".\");\n                    for (j = 0; j < octets.length; j++) {\n                        baIp.push(parseInt(octets[j], 10));\n                    }\n                    break;\n                case \"Decimal\":\n                    baIp = this.fromNumber(lines[i].toString(), 10);\n                    break;\n                case \"Octal\":\n                    baIp = this.fromNumber(lines[i].toString(), 8);\n                    break;\n                case \"Hex\":\n                    baIp = fromHex(lines[i]);\n                    break;\n                default:\n                    throw new OperationError(\"Unsupported input IP format\");\n            }\n\n            let ddIp;\n            let decIp;\n            let hexIp;\n\n            // Convert byte array IP to output format\n            switch (outFormat) {\n                case \"Dotted Decimal\":\n                    ddIp = \"\";\n                    for (j = 0; j < baIp.length; j++) {\n                        ddIp += baIp[j] + \".\";\n                    }\n                    output += ddIp.slice(0, ddIp.length-1) + \"\\n\";\n                    break;\n                case \"Decimal\":\n                    decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0;\n                    output += decIp.toString() + \"\\n\";\n                    break;\n                case \"Octal\":\n                    decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0;\n                    output += \"0\" + decIp.toString(8) + \"\\n\";\n                    break;\n                case \"Hex\":\n                    hexIp = \"\";\n                    for (j = 0; j < baIp.length; j++) {\n                        hexIp += Utils.hex(baIp[j]);\n                    }\n                    output += hexIp + \"\\n\";\n                    break;\n                default:\n                    throw new OperationError(\"Unsupported output IP format\");\n            }\n        }\n\n        return output.slice(0, output.length-1);\n    }\n\n    /**\n     * Constructs an array of IP address octets from a numerical value.\n     * @param {string} value The value of the IP address\n     * @param {number} radix The numeral system to be used\n     * @returns {number[]}\n     */\n    fromNumber(value, radix) {\n        const decimal = parseInt(value, radix);\n        const baIp = [];\n        baIp.push(decimal >> 24 & 255);\n        baIp.push(decimal >> 16 & 255);\n        baIp.push(decimal >> 8 & 255);\n        baIp.push(decimal & 255);\n        return baIp;\n    }\n\n}\n\nexport default ChangeIPFormat;\n"
  },
  {
    "path": "src/core/operations/ChiSquare.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Chi Square operation\n */\nclass ChiSquare extends Operation {\n\n    /**\n     * ChiSquare constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Chi Square\";\n        this.module = \"Default\";\n        this.description = \"Calculates the Chi Square distribution of values.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Chi-squared_distribution\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"number\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        const data = new Uint8Array(input);\n        const distArray = new Array(256).fill(0);\n        let total = 0;\n\n        for (let i = 0; i < data.length; i++) {\n            distArray[data[i]]++;\n        }\n\n        for (let i = 0; i < distArray.length; i++) {\n            if (distArray[i] > 0) {\n                total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256);\n            }\n        }\n\n        return total;\n    }\n\n}\n\nexport default ChiSquare;\n"
  },
  {
    "path": "src/core/operations/CipherSaber2Decrypt.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { encode } from \"../lib/CipherSaber2.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * CipherSaber2 Decrypt operation\n */\nclass CipherSaber2Decrypt extends Operation {\n\n    /**\n     * CipherSaber2Decrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CipherSaber2 Decrypt\";\n        this.module = \"Crypto\";\n        this.description = \"CipherSaber is a simple symmetric encryption protocol based on the RC4 stream cipher. It gives reasonably strong protection of message confidentiality, yet it's designed to be simple enough that even novice programmers can memorize the algorithm and implement it from scratch.\";\n        this.infoURL = \"https://wikipedia.org/wiki/CipherSaber\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 20\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const result = [],\n            key = Utils.convertToByteArray(args[0].string, args[0].option),\n            rounds = args[1];\n\n        const tempIVP = input.slice(0, 10);\n        input = input.slice(10);\n        return new Uint8Array(result.concat(encode(tempIVP, key, rounds, input))).buffer;\n    }\n\n}\n\nexport default CipherSaber2Decrypt;\n"
  },
  {
    "path": "src/core/operations/CipherSaber2Encrypt.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport crypto from \"crypto\";\nimport { encode } from \"../lib/CipherSaber2.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * CipherSaber2 Encrypt operation\n */\nclass CipherSaber2Encrypt extends Operation {\n\n    /**\n     * CipherSaber2Encrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"CipherSaber2 Encrypt\";\n        this.module = \"Crypto\";\n        this.description = \"CipherSaber is a simple symmetric encryption protocol based on the RC4 stream cipher. It gives reasonably strong protection of message confidentiality, yet it's designed to be simple enough that even novice programmers can memorize the algorithm and implement it from scratch.\";\n        this.infoURL = \"https://wikipedia.org/wiki/CipherSaber\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 20\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const result = [],\n            key = Utils.convertToByteArray(args[0].string, args[0].option),\n            rounds = args[1];\n\n        // Assign into initialisation vector based on cipher mode.\n        const tempIVP = crypto.randomBytes(10);\n        for (let m = 0; m < 10; m++)\n            result.push(tempIVP[m]);\n\n        return new Uint8Array(result.concat(encode(tempIVP, key, rounds, input))).buffer;\n    }\n\n}\n\nexport default CipherSaber2Encrypt;\n"
  },
  {
    "path": "src/core/operations/CitrixCTX1Decode.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport cptable from \"codepage\";\n\n/**\n * Citrix CTX1 Decode operation\n */\nclass CitrixCTX1Decode extends Operation {\n\n    /**\n     * CitrixCTX1Decode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Citrix CTX1 Decode\";\n        this.module = \"Encodings\";\n        this.description = \"Decodes strings in a Citrix CTX1 password format to plaintext.\";\n        this.infoURL = \"https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        if (input.length % 4 !== 0) {\n            throw new OperationError(\"Incorrect hash length\");\n        }\n        const revinput = input.reverse();\n        const result = [];\n        let temp = 0;\n        for (let i = 0; i < revinput.length; i += 2) {\n            if (i + 2 >= revinput.length) {\n                temp = 0;\n            } else {\n                temp = ((revinput[i + 2] - 0x41) & 0xf) ^ (((revinput[i + 3]- 0x41) << 4) & 0xf0);\n            }\n            temp = (((revinput[i] - 0x41) & 0xf) ^ (((revinput[i + 1] - 0x41) << 4) & 0xf0)) ^ 0xa5 ^ temp;\n            result.push(temp);\n        }\n        // Decodes a utf-16le string\n        return cptable.utils.decode(1200, result.reverse());\n    }\n\n}\n\nexport default CitrixCTX1Decode;\n"
  },
  {
    "path": "src/core/operations/CitrixCTX1Encode.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport cptable from \"codepage\";\n\n/**\n * Citrix CTX1 Encode operation\n */\nclass CitrixCTX1Encode extends Operation {\n\n    /**\n     * CitrixCTX1Encode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Citrix CTX1 Encode\";\n        this.module = \"Encodings\";\n        this.description = \"Encodes strings to Citrix CTX1 password format.\";\n        this.infoURL = \"https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const utf16pass = Array.from(cptable.utils.encode(1200, input));\n        const result = [];\n        let temp = 0;\n        for (let i = 0; i < utf16pass.length; i++) {\n            temp = utf16pass[i] ^ 0xa5 ^ temp;\n            result.push(((temp >>> 4) & 0xf) + 0x41);\n            result.push((temp & 0xf) + 0x41);\n        }\n\n        return result;\n    }\n\n}\n\nexport default CitrixCTX1Encode;\n"
  },
  {
    "path": "src/core/operations/Colossus.mjs",
    "content": "/**\n * Emulation of Colossus.\n *\n * Tested against the Colossus Rebuild at Bletchley Park's TNMOC\n * using a variety of inputs and settings to confirm correctness.\n *\n * @author VirtualColossus [martin@virtualcolossus.co.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { ColossusComputer } from \"../lib/Colossus.mjs\";\nimport { SWITCHES, VALID_ITA2 } from \"../lib/Lorenz.mjs\";\n\n/**\n * Colossus operation\n */\nclass Colossus extends Operation {\n\n    /**\n     * Colossus constructor\n     */\n    constructor() {\n        super();\n        this.name = \"Colossus\";\n        this.module = \"Bletchley\";\n        this.description = \"Colossus is the name of the world's first electronic computer. Ten Colossi were designed by Tommy Flowers and built at the Post Office Research Labs at Dollis Hill in 1943 during World War 2. They assisted with the breaking of the German Lorenz cipher attachment, a machine created to encipher communications between Hitler and his generals on the front lines.<br><br>To learn more, Virtual Colossus, an online, browser based simulation of a Colossus computer is available at <a href='https://virtualcolossus.co.uk' target='_blank'>virtualcolossus.co.uk</a>.<br><br>A more detailed description of this operation can be found <a href='https://github.com/gchq/CyberChef/wiki/Colossus' target='_blank'>here</a>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Colossus_computer\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Input\",\n                type: \"label\"\n            },\n            {\n                name: \"Pattern\",\n                type: \"option\",\n                value: [\"KH Pattern\", \"ZMUG Pattern\", \"BREAM Pattern\"]\n            },\n            {\n                name: \"QBusZ\",\n                type: \"option\",\n                value: [\"\", \"Z\", \"ΔZ\"]\n            },\n            {\n                name: \"QBusΧ\",\n                type: \"option\",\n                value: [\"\", \"Χ\", \"ΔΧ\"]\n            },\n            {\n                name: \"QBusΨ\",\n                type: \"option\",\n                value: [\"\", \"Ψ\", \"ΔΨ\"]\n            },\n            {\n                name: \"Limitation\",\n                type: \"option\",\n                value: [\"None\", \"Χ2\", \"Χ2 + P5\", \"X2 + Ψ1\", \"X2 + Ψ1 + P5\"]\n            },\n            {\n                name: \"K Rack Option\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"Select Program\",\n                        on: [7],\n                        off: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]\n                    },\n                    {\n                        name: \"Top Section - Conditional\",\n                        on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],\n                        off: [7, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]\n                    },\n                    {\n                        name: \"Bottom Section - Addition\",\n                        on: [31, 32, 33, 34, 35, 36, 37, 38, 39, 40],\n                        off: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n                    },\n                    {\n                        name: \"Advanced\",\n                        on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40],\n                        off: [7]\n                    }\n                ]\n            },\n            {\n                name: \"Program to run\",\n                type: \"option\",\n                value: [\"\", \"Letter Count\", \"1+2=. (1+2 Break In, Find X1,X2)\", \"4=5=/1=2 (Given X1,X2 find X4,X5)\", \"/,5,U (Count chars to find X3)\"]\n            },\n            {\n                name: \"K Rack: Conditional\",\n                type: \"label\"\n            },\n            {\n                name: \"R1-Q1\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R1-Q2\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R1-Q3\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R1-Q4\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R1-Q5\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R1-Negate\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"R1-Counter\",\n                type: \"option\",\n                value: [\"\", \"1\", \"2\", \"3\", \"4\", \"5\"]\n            },\n            {\n                name: \"R2-Q1\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R2-Q2\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R2-Q3\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R2-Q4\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R2-Q5\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R2-Negate\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"R2-Counter\",\n                type: \"option\",\n                value: [\"\", \"1\", \"2\", \"3\", \"4\", \"5\"]\n            },\n            {\n                name: \"R3-Q1\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R3-Q2\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R3-Q3\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R3-Q4\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R3-Q5\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"R3-Negate\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"R3-Counter\",\n                type: \"option\",\n                value: [\"\", \"1\", \"2\", \"3\", \"4\", \"5\"]\n            },\n            {\n                name: \"Negate All\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"K Rack: Addition\",\n                type: \"label\"\n            },\n            {\n                name: \"Add-Q1\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Add-Q2\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Add-Q3\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Add-Q4\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Add-Q5\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Add-Equals\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"Add-Counter1\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Add Negate All\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Total Motor\",\n                type: \"editableOptionShort\",\n                value: SWITCHES,\n                defaultIndex: 1\n            },\n            {\n                name: \"Master Control Panel\",\n                type: \"label\"\n            },\n            {\n                name: \"Set Total\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Fast Step\",\n                type: \"option\",\n                value: [\"\", \"X1\", \"X2\", \"X3\", \"X4\", \"X5\", \"M37\", \"M61\", \"S1\", \"S2\", \"S3\", \"S4\", \"S5\"]\n            },\n            {\n                name: \"Slow Step\",\n                type: \"option\",\n                value: [\"\", \"X1\", \"X2\", \"X3\", \"X4\", \"X5\", \"M37\", \"M61\", \"S1\", \"S2\", \"S3\", \"S4\", \"S5\"]\n            },\n            {\n                name: \"Start Χ1\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Χ2\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Χ3\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Χ4\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Χ5\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start M61\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start M37\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Ψ1\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Ψ2\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Ψ3\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Ψ4\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Start Ψ5\",\n                type: \"number\",\n                value: 1\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {Object}\n     */\n    run(input, args) {\n        input = input.toUpperCase();\n        for (const character of input) {\n            if (VALID_ITA2.indexOf(character) === -1) {\n                let errltr = character;\n                if (errltr === \"\\n\") errltr = \"Carriage Return\";\n                if (errltr === \" \") errltr = \"Space\";\n                throw new OperationError(\"Invalid ITA2 character : \" + errltr);\n            }\n        }\n\n        const pattern = args[1];\n        const qbusin = {\n            \"Z\": args[2],\n            \"Chi\": args[3],\n            \"Psi\": args[4],\n        };\n\n        const limitation = args[5];\n        const lm = [false, false, false];\n        if (limitation.includes(\"Χ2\")) lm[0] = true;\n        if (limitation.includes(\"Ψ1\")) lm[1] = true;\n        if (limitation.includes(\"P5\")) lm[2] = true;\n        const limit = {\n            X2: lm[0], S1: lm[1], P5: lm[2]\n        };\n\n        const KRackOpt = args[6];\n        const setProgram = args[7];\n\n        if (KRackOpt === \"Select Program\" && setProgram !== \"\") {\n            args = this.selectProgram(setProgram, args);\n        }\n\n        const re = new RegExp(\"^$|^[.x]$\");\n        for (let qr=0;qr<3;qr++) {\n            for (let a=0;a<5;a++) {\n                if (!re.test(args[((qr*7)+(a+9))]))\n                    throw new OperationError(\"Switch R\"+(qr+1)+\"-Q\"+(a+1)+\" can only be set to blank, . or x\");\n            }\n        }\n\n        if (!re.test(args[37])) throw new OperationError(\"Switch Add-Equals can only be set to blank, . or x\");\n        if (!re.test(args[40])) throw new OperationError(\"Switch Total Motor can only be set to blank, . or x\");\n\n        // Q1,Q2,Q3,Q4,Q5,negate,counter1\n        const qbusswitches = {\n            condition: [\n                {Qswitches: [args[9], args[10], args[11], args[12], args[13]], Negate: args[14], Counter: args[15]},\n                {Qswitches: [args[16], args[17], args[18], args[19], args[20]], Negate: args[21], Counter: args[22]},\n                {Qswitches: [args[23], args[24], args[25], args[26], args[27]], Negate: args[28], Counter: args[29]}\n            ],\n            condNegateAll: args[30],\n            addition: [\n                {Qswitches: [args[32], args[33], args[34], args[35], args[36]], Equals: args[37], C1: args[38]}\n            ],\n            addNegateAll: args[39],\n            totalMotor: args[40]\n        };\n\n        const settotal = parseInt(args[42], 10);\n        if (settotal < 0 || settotal > 9999)\n            throw new OperationError(\"Set Total must be between 0000 and 9999\");\n\n        // null|fast|slow for each of S1-5,M1-2,X1-5\n        const control = {\n            fast: args[43],\n            slow: args[44]\n        };\n\n        // Start positions\n        if (args[52]<1 || args[52]>43) throw new OperationError(\"Ψ1 start must be between 1 and 43\");\n        if (args[53]<1 || args[53]>47) throw new OperationError(\"Ψ2 start must be between 1 and 47\");\n        if (args[54]<1 || args[54]>51) throw new OperationError(\"Ψ3 start must be between 1 and 51\");\n        if (args[55]<1 || args[55]>53) throw new OperationError(\"Ψ4 start must be between 1 and 53\");\n        if (args[56]<1 || args[57]>59) throw new OperationError(\"Ψ5 start must be between 1 and 59\");\n        if (args[51]<1 || args[51]>37) throw new OperationError(\"Μ37 start must be between 1 and 37\");\n        if (args[50]<1 || args[50]>61) throw new OperationError(\"Μ61 start must be between 1 and 61\");\n        if (args[45]<1 || args[45]>41) throw new OperationError(\"Χ1 start must be between 1 and 41\");\n        if (args[46]<1 || args[46]>31) throw new OperationError(\"Χ2 start must be between 1 and 31\");\n        if (args[47]<1 || args[47]>29) throw new OperationError(\"Χ3 start must be between 1 and 29\");\n        if (args[48]<1 || args[48]>26) throw new OperationError(\"Χ4 start must be between 1 and 26\");\n        if (args[49]<1 || args[49]>23) throw new OperationError(\"Χ5 start must be between 1 and 23\");\n\n        const starts = {\n            X1: args[45], X2: args[46], X3: args[47], X4: args[48], X5: args[49],\n            M61: args[50], M37: args[51],\n            S1: args[52], S2: args[53], S3: args[54], S4: args[55], S5: args[56]\n        };\n\n        const colossus = new ColossusComputer(input, pattern, qbusin, qbusswitches, control, starts, settotal, limit);\n        const result = colossus.run();\n\n        return result;\n    }\n\n    /**\n     * Select Program\n     *\n     * @param {string} progname\n     * @param {Object[]} args\n     * @returns {Object[]}\n     */\n    selectProgram(progname, args) {\n\n        // Basic Letter Count\n        if (progname === \"Letter Count\") {\n            // Set Conditional R1 : count every character into counter 1\n            args[9] = \"\";\n            args[10] = \"\";\n            args[11] = \"\";\n            args[12] = \"\";\n            args[13] = \"\";\n            args[14] = false;\n            args[15] = \"1\";\n            // clear Conditional R2 & R3\n            args[22] = \"\";\n            args[29] = \"\";\n            // Clear Negate result\n            args[30] = false;\n            // Clear Addition row counter\n            args[38] = false;\n        }\n\n        // Bill Tutte's 1+2 Break In\n        if (progname === \"1+2=. (1+2 Break In, Find X1,X2)\") {\n            // Clear any other counters\n            args[15] = \"\"; // Conditional R1\n            args[22] = \"\"; // Conditional R2\n            args[29] = \"\"; // Conditional R3\n            // Set Add Q1+Q2=. into Counter 1\n            args[32] = true;\n            args[33] = true;\n            args[34] = false;\n            args[35] = false;\n            args[36] = false;\n            args[37] = \".\";\n            args[38] = true;\n        }\n\n        // 4=3=/1=2 : Find X4 & X5 where X1 & X2 are known\n        if (progname === \"4=5=/1=2 (Given X1,X2 find X4,X5)\") {\n            // Set Conditional R1 : Match NOT ..?.. into counter 1\n            args[9] = \".\";\n            args[10] = \".\";\n            args[11] = \"\";\n            args[12] = \".\";\n            args[13] = \".\";\n            args[14] = true;\n            args[15] = \"1\";\n            // Set Conditional R2 : AND Match NOT xx?xx into counter 1\n            args[16] = \"x\";\n            args[17] = \"x\";\n            args[18] = \"\";\n            args[19] = \"x\";\n            args[20] = \"x\";\n            args[21] = true;\n            args[22] = \"1\";\n            // clear Conditional R3\n            args[29] = \"\";\n            // Negate result, giving NOT(NOT Q1 AND NOT Q2) which is equivalent to Q1 OR Q2\n            args[30] = true;\n            // Clear Addition row counter\n            args[38] = false;\n        }\n\n        // /,5,U : Count number of matches of /, 5 & U to find X3\n        if (progname === \"/,5,U (Count chars to find X3)\") {\n            // Set Conditional R1 : Match / char, ITA2 = ..... into counter 1\n            args[9] = \".\";\n            args[10] = \".\";\n            args[11] = \".\";\n            args[12] = \".\";\n            args[13] = \".\";\n            args[14] = false;\n            args[15] = \"1\";\n            // Set Conditional R2 : Match 5 char, ITA2 = xx.xx into counter 2\n            args[16] = \"x\";\n            args[17] = \"x\";\n            args[18] = \".\";\n            args[19] = \"x\";\n            args[20] = \"x\";\n            args[21] = false;\n            args[22] = \"2\";\n            // Set Conditional R3 : Match U char, ITA2 = xxx.. into counter 3\n            args[23] = \"x\";\n            args[24] = \"x\";\n            args[25] = \"x\";\n            args[26] = \".\";\n            args[27] = \".\";\n            args[28] = false;\n            args[29] = \"3\";\n            // Clear Negate result\n            args[30] = false;\n            // Clear Addition row counter\n            args[38] = false;\n        }\n\n        return args;\n    }\n\n    /**\n     * Displays Colossus results in an HTML table\n     *\n     * @param {Object} output\n     * @param {Object[]} output.counters\n     * @returns {html}\n     */\n    present(output) {\n        let html = \"Colossus Printer\\n\\n\";\n        html += output.printout + \"\\n\\n\";\n        html += \"Colossus Counters\\n\\n\";\n        html += \"<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>C1</th>  <th>C2</th>  <th>C3</th>  <th>C4</th>  <th>C5</th></tr>\\n\";\n        html += \"<tr>\";\n        for (const ct of output.counters) {\n            html += `<td>${ct}</td>\\n`;\n        }\n        html += \"</tr>\";\n        html += \"</table>\";\n        return html;\n    }\n\n}\n\nexport default Colossus;\n"
  },
  {
    "path": "src/core/operations/Comment.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Comment operation\n */\nclass Comment extends Operation {\n\n    /**\n     * Comment constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Comment\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"Provides a place to write comments within the flow of the recipe. This operation has no computational effect.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @returns {Object} The updated state of the recipe.\n     */\n    run(state) {\n        return state;\n    }\n\n}\n\nexport default Comment;\n"
  },
  {
    "path": "src/core/operations/CompareCTPHHashes.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {HASH_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport ctphjs from \"ctph.js\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Compare CTPH hashes operation\n */\nclass CompareCTPHHashes extends Operation {\n\n    /**\n     * CompareCTPHHashes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Compare CTPH hashes\";\n        this.module = \"Crypto\";\n        this.description = \"Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100.\";\n        this.infoURL = \"https://forensics.wiki/context_triggered_piecewise_hashing/\";\n        this.inputType = \"string\";\n        this.outputType = \"Number\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": HASH_DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {Number}\n     */\n    run(input, args) {\n        const samples = input.split(Utils.charRep(args[0]));\n        if (samples.length !== 2) throw new OperationError(\"Incorrect number of samples.\");\n        return ctphjs.similarity(samples[0], samples[1]);\n    }\n\n}\n\nexport default CompareCTPHHashes;\n"
  },
  {
    "path": "src/core/operations/CompareSSDEEPHashes.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {HASH_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport ssdeepjs from \"ssdeep.js\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Compare SSDEEP hashes operation\n */\nclass CompareSSDEEPHashes extends Operation {\n\n    /**\n     * CompareSSDEEPHashes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Compare SSDEEP hashes\";\n        this.module = \"Crypto\";\n        this.description = \"Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.\";\n        this.infoURL = \"https://forensics.wiki/ssdeep/\";\n        this.inputType = \"string\";\n        this.outputType = \"Number\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": HASH_DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {Number}\n     */\n    run(input, args) {\n        const samples = input.split(Utils.charRep(args[0]));\n        if (samples.length !== 2) throw new OperationError(\"Incorrect number of samples.\");\n        return ssdeepjs.similarity(samples[0], samples[1]);\n    }\n\n}\n\nexport default CompareSSDEEPHashes;\n"
  },
  {
    "path": "src/core/operations/ConditionalJump.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Dish from \"../Dish.mjs\";\nimport { getLabelIndex } from \"../lib/FlowControl.mjs\";\n\n/**\n * Conditional Jump operation\n */\nclass ConditionalJump extends Operation {\n\n    /**\n     * ConditionalJump constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Conditional Jump\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"Conditionally jump forwards or backwards to the specified Label  based on whether the data matches the specified regular expression.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Match (regex)\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Invert match\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Label name\",\n                \"type\": \"shortString\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Maximum jumps (if jumping backwards)\",\n                \"type\": \"number\",\n                \"value\": 10\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @param {number} state.numJumps - The number of jumps taken so far.\n     * @returns {Object} The updated state of the recipe.\n     */\n    async run(state) {\n        const ings   = state.opList[state.progress].ingValues,\n            dish     = state.dish,\n            [regexStr, invert, label, maxJumps] = ings,\n            jmpIndex = getLabelIndex(label, state);\n\n        if (state.numJumps >= maxJumps || jmpIndex === -1) {\n            state.numJumps = 0;\n            return state;\n        }\n\n        if (regexStr !== \"\") {\n            const str = await dish.get(Dish.STRING);\n            const strMatch = str.search(regexStr) > -1;\n            if (!invert && strMatch || invert && !strMatch) {\n                state.progress = jmpIndex;\n                state.numJumps++;\n            } else {\n                state.numJumps = 0;\n            }\n        }\n\n        return state;\n    }\n\n}\n\nexport default ConditionalJump;\n"
  },
  {
    "path": "src/core/operations/ContainImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport {\n    Jimp,\n    JimpMime,\n    ResizeStrategy,\n    HorizontalAlign,\n    VerticalAlign,\n} from \"jimp\";\n\n/**\n * Contain Image operation\n */\nclass ContainImage extends Operation {\n    /**\n     * ContainImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Contain Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Scales an image to the specified width and height, maintaining the aspect ratio. The image may be letterboxed.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Width\",\n                type: \"number\",\n                value: 100,\n                min: 1,\n            },\n            {\n                name: \"Height\",\n                type: \"number\",\n                value: 100,\n                min: 1,\n            },\n            {\n                name: \"Horizontal align\",\n                type: \"option\",\n                value: [\"Left\", \"Center\", \"Right\"],\n                defaultIndex: 1,\n            },\n            {\n                name: \"Vertical align\",\n                type: \"option\",\n                value: [\"Top\", \"Middle\", \"Bottom\"],\n                defaultIndex: 1,\n            },\n            {\n                name: \"Resizing algorithm\",\n                type: \"option\",\n                value: [\n                    \"Nearest Neighbour\",\n                    \"Bilinear\",\n                    \"Bicubic\",\n                    \"Hermite\",\n                    \"Bezier\",\n                ],\n                defaultIndex: 1,\n            },\n            {\n                name: \"Opaque background\",\n                type: \"boolean\",\n                value: true,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [width, height, hAlign, vAlign, alg, opaqueBg] = args;\n\n        const resizeMap = {\n            \"Nearest Neighbour\": ResizeStrategy.NEAREST_NEIGHBOR,\n            Bilinear: ResizeStrategy.BILINEAR,\n            Bicubic: ResizeStrategy.BICUBIC,\n            Hermite: ResizeStrategy.HERMITE,\n            Bezier: ResizeStrategy.BEZIER,\n        };\n\n        const alignMap = {\n            Left: HorizontalAlign.LEFT,\n            Center: HorizontalAlign.CENTER,\n            Right: HorizontalAlign.RIGHT,\n            Top: VerticalAlign.TOP,\n            Middle: VerticalAlign.MIDDLE,\n            Bottom: VerticalAlign.BOTTOM,\n        };\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        const originalMime = image.mime;\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Containing image...\");\n            image.contain({\n                w: width,\n                h: height,\n                align: alignMap[hAlign] | alignMap[vAlign],\n                mode: resizeMap[alg],\n            });\n\n            if (opaqueBg) {\n                const newImage = new Jimp({\n                    width,\n                    height,\n                    color: 0x000000ff,\n                });\n                image = newImage.blit({\n                    src: image,\n                    x: 0,\n                    y: 0,\n                });\n            }\n\n            let imageBuffer;\n            if (originalMime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(originalMime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error containing image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the contained image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default ContainImage;\n"
  },
  {
    "path": "src/core/operations/ConvertArea.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Convert area operation\n */\nclass ConvertArea extends Operation {\n\n    /**\n     * ConvertArea constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert area\";\n        this.module = \"Default\";\n        this.description = \"Converts a unit of area to another format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Orders_of_magnitude_(area)\";\n        this.inputType = \"BigNumber\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Input units\",\n                \"type\": \"option\",\n                \"value\": AREA_UNITS\n            },\n            {\n                \"name\": \"Output units\",\n                \"type\": \"option\",\n                \"value\": AREA_UNITS\n            }\n        ];\n    }\n\n    /**\n     * @param {BigNumber} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const [inputUnits, outputUnits] = args;\n\n        input = input.times(AREA_FACTOR[inputUnits]);\n        return input.div(AREA_FACTOR[outputUnits]);\n    }\n\n}\n\n\nconst AREA_UNITS = [\n    \"[Metric]\", \"Square metre (sq m)\", \"Square kilometre (sq km)\", \"Centiare (ca)\", \"Deciare (da)\", \"Are (a)\", \"Decare (daa)\", \"Hectare (ha)\", \"[/Metric]\",\n    \"[Imperial]\", \"Square inch (sq in)\", \"Square foot (sq ft)\", \"Square yard (sq yd)\", \"Square mile (sq mi)\", \"Perch (sq per)\", \"Rood (ro)\", \"International acre (ac)\", \"[/Imperial]\",\n    \"[US customary units]\", \"US survey acre (ac)\", \"US survey square mile (sq mi)\", \"US survey township\", \"[/US customary units]\",\n    \"[Nuclear physics]\", \"Yoctobarn (yb)\", \"Zeptobarn (zb)\", \"Attobarn (ab)\", \"Femtobarn (fb)\", \"Picobarn (pb)\", \"Nanobarn (nb)\", \"Microbarn (μb)\", \"Millibarn (mb)\", \"Barn (b)\", \"Kilobarn (kb)\", \"Megabarn (Mb)\", \"Outhouse\", \"Shed\", \"Planck area\", \"[/Nuclear physics]\",\n    \"[Comparisons]\", \"Washington D.C.\", \"Isle of Wight\", \"Wales\", \"Texas\", \"[/Comparisons]\",\n];\n\nconst AREA_FACTOR = { // Multiples of a square metre\n    // Metric\n    \"Square metre (sq m)\":      1,\n    \"Square kilometre (sq km)\": 1e6,\n\n    \"Centiare (ca)\":            1,\n    \"Deciare (da)\":             10,\n    \"Are (a)\":                  100,\n    \"Decare (daa)\":             1e3,\n    \"Hectare (ha)\":             1e4,\n\n    // Imperial\n    \"Square inch (sq in)\":      0.00064516,\n    \"Square foot (sq ft)\":      0.09290304,\n    \"Square yard (sq yd)\":      0.83612736,\n    \"Square mile (sq mi)\":      2589988.110336,\n    \"Perch (sq per)\":           42.21,\n    \"Rood (ro)\":                1011,\n    \"International acre (ac)\":  4046.8564224,\n\n    // US customary units\n    \"US survey acre (ac)\":      4046.87261,\n    \"US survey square mile (sq mi)\": 2589998.470305239,\n    \"US survey township\":       93239944.9309886,\n\n    // Nuclear physics\n    \"Yoctobarn (yb)\":           1e-52,\n    \"Zeptobarn (zb)\":           1e-49,\n    \"Attobarn (ab)\":            1e-46,\n    \"Femtobarn (fb)\":           1e-43,\n    \"Picobarn (pb)\":            1e-40,\n    \"Nanobarn (nb)\":            1e-37,\n    \"Microbarn (μb)\":           1e-34,\n    \"Millibarn (mb)\":           1e-31,\n    \"Barn (b)\":                 1e-28,\n    \"Kilobarn (kb)\":            1e-25,\n    \"Megabarn (Mb)\":            1e-22,\n\n    \"Planck area\":              2.6e-70,\n    \"Shed\":                     1e-52,\n    \"Outhouse\":                 1e-34,\n\n    // Comparisons\n    \"Washington D.C.\":          176119191.502848,\n    \"Isle of Wight\":            380000000,\n    \"Wales\":                    20779000000,\n    \"Texas\":                    696241000000,\n};\n\n\nexport default ConvertArea;\n"
  },
  {
    "path": "src/core/operations/ConvertCoordinateFormat.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {FORMATS, convertCoordinates} from \"../lib/ConvertCoordinates.mjs\";\n\n/**\n * Convert co-ordinate format operation\n */\nclass ConvertCoordinateFormat extends Operation {\n\n    /**\n     * ConvertCoordinateFormat constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert co-ordinate format\";\n        this.module = \"Hashing\";\n        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.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Geographic_coordinate_conversion\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Input Format\",\n                \"type\": \"option\",\n                \"value\": [\"Auto\"].concat(FORMATS)\n            },\n            {\n                \"name\": \"Input Delimiter\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Auto\",\n                    \"Direction Preceding\",\n                    \"Direction Following\",\n                    \"\\\\n\",\n                    \"Comma\",\n                    \"Semi-colon\",\n                    \"Colon\"\n                ]\n            },\n            {\n                \"name\": \"Output Format\",\n                \"type\": \"option\",\n                \"value\": FORMATS\n            },\n            {\n                \"name\": \"Output Delimiter\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Space\",\n                    \"\\\\n\",\n                    \"Comma\",\n                    \"Semi-colon\",\n                    \"Colon\"\n                ]\n            },\n            {\n                \"name\": \"Include Compass Directions\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"None\",\n                    \"Before\",\n                    \"After\"\n                ]\n            },\n            {\n                \"name\": \"Precision\",\n                \"type\": \"number\",\n                \"value\": 3\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (input.replace(/[\\s+]/g, \"\") !== \"\") {\n            const [inFormat, inDelim, outFormat, outDelim, incDirection, precision] = args;\n            const result = convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision);\n            return result;\n        } else {\n            return input;\n        }\n    }\n}\n\nexport default ConvertCoordinateFormat;\n"
  },
  {
    "path": "src/core/operations/ConvertDataUnits.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Convert data units operation\n */\nclass ConvertDataUnits extends Operation {\n\n    /**\n     * ConvertDataUnits constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert data units\";\n        this.module = \"Default\";\n        this.description = \"Converts a unit of data to another format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Orders_of_magnitude_(data)\";\n        this.inputType = \"BigNumber\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Input units\",\n                \"type\": \"option\",\n                \"value\": DATA_UNITS\n            },\n            {\n                \"name\": \"Output units\",\n                \"type\": \"option\",\n                \"value\": DATA_UNITS\n            }\n        ];\n    }\n\n    /**\n     * @param {BigNumber} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const [inputUnits, outputUnits] = args;\n\n        input = input.times(DATA_FACTOR[inputUnits]);\n        return input.div(DATA_FACTOR[outputUnits]);\n    }\n\n}\n\nconst DATA_UNITS = [\n    \"Bits (b)\", \"Nibbles\", \"Octets\", \"Bytes (B)\",\n    \"[Binary bits (2^n)]\", \"Kibibits (Kib)\", \"Mebibits (Mib)\", \"Gibibits (Gib)\", \"Tebibits (Tib)\", \"Pebibits (Pib)\", \"Exbibits (Eib)\", \"Zebibits (Zib)\", \"Yobibits (Yib)\", \"[/Binary bits (2^n)]\",\n    \"[Decimal bits (10^n)]\", \"Decabits\", \"Hectobits\", \"Kilobits (Kb)\", \"Megabits (Mb)\", \"Gigabits (Gb)\", \"Terabits (Tb)\", \"Petabits (Pb)\", \"Exabits (Eb)\", \"Zettabits (Zb)\", \"Yottabits (Yb)\", \"[/Decimal bits (10^n)]\",\n    \"[Binary bytes (8 x 2^n)]\", \"Kibibytes (KiB)\", \"Mebibytes (MiB)\", \"Gibibytes (GiB)\", \"Tebibytes (TiB)\", \"Pebibytes (PiB)\", \"Exbibytes (EiB)\", \"Zebibytes (ZiB)\", \"Yobibytes (YiB)\", \"[/Binary bytes (8 x 2^n)]\",\n    \"[Decimal bytes (8 x 10^n)]\", \"Kilobytes (KB)\", \"Megabytes (MB)\", \"Gigabytes (GB)\", \"Terabytes (TB)\", \"Petabytes (PB)\", \"Exabytes (EB)\", \"Zettabytes (ZB)\", \"Yottabytes (YB)\", \"[/Decimal bytes (8 x 10^n)]\"\n];\n\nconst DATA_FACTOR = { // Multiples of a bit\n    \"Bits (b)\":        1,\n    \"Nibbles\":         4,\n    \"Octets\":          8,\n    \"Bytes (B)\":       8,\n\n    // Binary bits (2^n)\n    \"Kibibits (Kib)\":  1024,\n    \"Mebibits (Mib)\":  1048576,\n    \"Gibibits (Gib)\":  1073741824,\n    \"Tebibits (Tib)\":  1099511627776,\n    \"Pebibits (Pib)\":  1125899906842624,\n    \"Exbibits (Eib)\":  1152921504606846976,\n    \"Zebibits (Zib)\":  1180591620717411303424,\n    \"Yobibits (Yib)\":  1208925819614629174706176,\n\n    // Decimal bits (10^n)\n    \"Decabits\":        10,\n    \"Hectobits\":       100,\n    \"Kilobits (Kb)\":   1e3,\n    \"Megabits (Mb)\":   1e6,\n    \"Gigabits (Gb)\":   1e9,\n    \"Terabits (Tb)\":   1e12,\n    \"Petabits (Pb)\":   1e15,\n    \"Exabits (Eb)\":    1e18,\n    \"Zettabits (Zb)\":  1e21,\n    \"Yottabits (Yb)\":  1e24,\n\n    // Binary bytes (8 x 2^n)\n    \"Kibibytes (KiB)\": 8192,\n    \"Mebibytes (MiB)\": 8388608,\n    \"Gibibytes (GiB)\": 8589934592,\n    \"Tebibytes (TiB)\": 8796093022208,\n    \"Pebibytes (PiB)\": 9007199254740992,\n    \"Exbibytes (EiB)\": 9223372036854775808,\n    \"Zebibytes (ZiB)\": 9444732965739290427392,\n    \"Yobibytes (YiB)\": 9671406556917033397649408,\n\n    // Decimal bytes (8 x 10^n)\n    \"Kilobytes (KB)\":  8e3,\n    \"Megabytes (MB)\":  8e6,\n    \"Gigabytes (GB)\":  8e9,\n    \"Terabytes (TB)\":  8e12,\n    \"Petabytes (PB)\":  8e15,\n    \"Exabytes (EB)\":   8e18,\n    \"Zettabytes (ZB)\": 8e21,\n    \"Yottabytes (YB)\": 8e24,\n};\n\n\nexport default ConvertDataUnits;\n"
  },
  {
    "path": "src/core/operations/ConvertDistance.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Convert distance operation\n */\nclass ConvertDistance extends Operation {\n\n    /**\n     * ConvertDistance constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert distance\";\n        this.module = \"Default\";\n        this.description = \"Converts a unit of distance to another format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Orders_of_magnitude_(length)\";\n        this.inputType = \"BigNumber\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Input units\",\n                \"type\": \"option\",\n                \"value\": DISTANCE_UNITS\n            },\n            {\n                \"name\": \"Output units\",\n                \"type\": \"option\",\n                \"value\": DISTANCE_UNITS\n            }\n        ];\n    }\n\n    /**\n     * @param {BigNumber} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const [inputUnits, outputUnits] = args;\n\n        input = input.times(DISTANCE_FACTOR[inputUnits]);\n        return input.div(DISTANCE_FACTOR[outputUnits]);\n    }\n\n}\n\nconst DISTANCE_UNITS = [\n    \"[Metric]\", \"Nanometres (nm)\", \"Micrometres (µm)\", \"Millimetres (mm)\", \"Centimetres (cm)\", \"Metres (m)\", \"Kilometers (km)\", \"[/Metric]\",\n    \"[Imperial]\", \"Thou (th)\", \"Inches (in)\", \"Feet (ft)\", \"Yards (yd)\", \"Chains (ch)\", \"Furlongs (fur)\", \"Miles (mi)\", \"Leagues (lea)\", \"[/Imperial]\",\n    \"[Maritime]\", \"Fathoms (ftm)\", \"Cables\", \"Nautical miles\", \"[/Maritime]\",\n    \"[Comparisons]\", \"Cars (4m)\", \"Buses (8.4m)\", \"American football fields (91m)\", \"Football pitches (105m)\", \"[/Comparisons]\",\n    \"[Astronomical]\", \"Earth-to-Moons\", \"Earth's equators\", \"Astronomical units (au)\", \"Light-years (ly)\", \"Parsecs (pc)\", \"[/Astronomical]\",\n];\n\nconst DISTANCE_FACTOR = { // Multiples of a metre\n    \"Nanometres (nm)\":         1e-9,\n    \"Micrometres (µm)\":        1e-6,\n    \"Millimetres (mm)\":        1e-3,\n    \"Centimetres (cm)\":        1e-2,\n    \"Metres (m)\":              1,\n    \"Kilometers (km)\":         1e3,\n\n    \"Thou (th)\":               0.0000254,\n    \"Inches (in)\":             0.0254,\n    \"Feet (ft)\":               0.3048,\n    \"Yards (yd)\":              0.9144,\n    \"Chains (ch)\":             20.1168,\n    \"Furlongs (fur)\":          201.168,\n    \"Miles (mi)\":              1609.344,\n    \"Leagues (lea)\":           4828.032,\n\n    \"Fathoms (ftm)\":           1.853184,\n    \"Cables\":                  185.3184,\n    \"Nautical miles\":          1853.184,\n\n    \"Cars (4m)\":               4,\n    \"Buses (8.4m)\":            8.4,\n    \"American football fields (91m)\": 91,\n    \"Football pitches (105m)\": 105,\n\n    \"Earth-to-Moons\":          380000000,\n    \"Earth's equators\":        40075016.686,\n    \"Astronomical units (au)\": 149597870700,\n    \"Light-years (ly)\":        9460730472580800,\n    \"Parsecs (pc)\":            3.0856776e16\n};\n\n\nexport default ConvertDistance;\n"
  },
  {
    "path": "src/core/operations/ConvertImageFormat.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { Jimp, JimpMime, PNGFilterType } from \"jimp\";\n\n/**\n * Convert Image Format operation\n */\nclass ConvertImageFormat extends Operation {\n    /**\n     * ConvertImageFormat constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert Image Format\";\n        this.module = \"Image\";\n        this.description =\n            \"Converts an image between different formats. Supported formats:<br><ul><li>Joint Photographic Experts Group (JPEG)</li><li>Portable Network Graphics (PNG)</li><li>Bitmap (BMP)</li><li>Tagged Image File Format (TIFF)</li></ul><br>Note: GIF files are supported for input, but cannot be outputted.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Image_file_formats\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Output Format\",\n                type: \"option\",\n                value: [\"JPEG\", \"PNG\", \"BMP\", \"TIFF\"],\n            },\n            {\n                name: \"JPEG Quality\",\n                type: \"number\",\n                value: 80,\n                min: 1,\n                max: 100,\n            },\n            {\n                name: \"PNG Filter Type\",\n                type: \"option\",\n                value: [\"Auto\", \"None\", \"Sub\", \"Up\", \"Average\", \"Paeth\"],\n            },\n            {\n                name: \"PNG Deflate Level\",\n                type: \"number\",\n                value: 9,\n                min: 0,\n                max: 9,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;\n        const formatMap = {\n            JPEG: JimpMime.jpeg,\n            PNG: JimpMime.png,\n            BMP: JimpMime.bmp,\n            TIFF: JimpMime.tiff,\n        };\n\n        const pngFilterMap = {\n            Auto: PNGFilterType.AUTO,\n            None: PNGFilterType.NONE,\n            Sub: PNGFilterType.SUB,\n            Up: PNGFilterType.UP,\n            Average: PNGFilterType.AVERAGE,\n            Paeth: PNGFilterType.PATH,\n        };\n\n        const mime = formatMap[format];\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file format.\");\n        }\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error opening image file. (${err})`);\n        }\n        try {\n            let buffer;\n            switch (mime) {\n                case JimpMime.jpeg:\n                    buffer = await image.getBuffer(mime, {\n                        quality: jpegQuality,\n                    });\n                    break;\n                case JimpMime.png:\n                    buffer = await image.getBuffer(mime, {\n                        filterType: pngFilterMap[pngFilterType],\n                        deflateLevel: pngDeflateLevel,\n                    });\n                    break;\n                default:\n                    buffer = await image.getBuffer(mime);\n                    break;\n            }\n\n            return buffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error converting image format. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the converted image using HTML for web apps\n     *\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default ConvertImageFormat;\n"
  },
  {
    "path": "src/core/operations/ConvertLeetSpeak.mjs",
    "content": "/**\n * @author bartblaze []\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Convert Leet Speak operation\n */\nclass ConvertLeetSpeak extends Operation {\n    /**\n     * ConvertLeetSpeak constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert Leet Speak\";\n        this.module = \"Default\";\n        this.description = \"Converts to and from Leet Speak.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Leet\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Direction\",\n                type: \"option\",\n                value: [\"To Leet Speak\", \"From Leet Speak\"],\n                defaultIndex: 0\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const direction = args[0];\n\n        if (direction === \"To Leet Speak\") {\n            return input.replace(/[a-z]/gi, char => {\n                const leetChar = toLeetMap[char.toLowerCase()] || char;\n                return char === char.toUpperCase() ? leetChar.toUpperCase() : leetChar;\n            });\n        } else if (direction === \"From Leet Speak\") {\n            return input.replace(/[48cd3f6h1jklmn0pqr57uvwxyz]/gi, char => {\n                const normalChar = fromLeetMap[char] || char;\n                return normalChar;\n            });\n        }\n    }\n}\n\nconst toLeetMap = {\n    \"a\": \"4\",\n    \"b\": \"b\",\n    \"c\": \"c\",\n    \"d\": \"d\",\n    \"e\": \"3\",\n    \"f\": \"f\",\n    \"g\": \"g\",\n    \"h\": \"h\",\n    \"i\": \"1\",\n    \"j\": \"j\",\n    \"k\": \"k\",\n    \"l\": \"l\",\n    \"m\": \"m\",\n    \"n\": \"n\",\n    \"o\": \"0\",\n    \"p\": \"p\",\n    \"q\": \"q\",\n    \"r\": \"r\",\n    \"s\": \"5\",\n    \"t\": \"7\",\n    \"u\": \"u\",\n    \"v\": \"v\",\n    \"w\": \"w\",\n    \"x\": \"x\",\n    \"y\": \"y\",\n    \"z\": \"z\"\n};\n\nconst fromLeetMap = {\n    \"4\": \"a\",\n    \"b\": \"b\",\n    \"c\": \"c\",\n    \"d\": \"d\",\n    \"3\": \"e\",\n    \"f\": \"f\",\n    \"g\": \"g\",\n    \"h\": \"h\",\n    \"1\": \"i\",\n    \"j\": \"j\",\n    \"k\": \"k\",\n    \"l\": \"l\",\n    \"m\": \"m\",\n    \"n\": \"n\",\n    \"0\": \"o\",\n    \"p\": \"p\",\n    \"q\": \"q\",\n    \"r\": \"r\",\n    \"5\": \"s\",\n    \"7\": \"t\",\n    \"u\": \"u\",\n    \"v\": \"v\",\n    \"w\": \"w\",\n    \"x\": \"x\",\n    \"y\": \"y\",\n    \"z\": \"z\"\n};\n\nexport default ConvertLeetSpeak;\n\n"
  },
  {
    "path": "src/core/operations/ConvertMass.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Convert mass operation\n */\nclass ConvertMass extends Operation {\n\n    /**\n     * ConvertMass constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert mass\";\n        this.module = \"Default\";\n        this.description = \"Converts a unit of mass to another format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Orders_of_magnitude_(mass)\";\n        this.inputType = \"BigNumber\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Input units\",\n                \"type\": \"option\",\n                \"value\": MASS_UNITS\n            },\n            {\n                \"name\": \"Output units\",\n                \"type\": \"option\",\n                \"value\": MASS_UNITS\n            }\n        ];\n    }\n\n    /**\n     * @param {BigNumber} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const [inputUnits, outputUnits] = args;\n\n        input = input.times(MASS_FACTOR[inputUnits]);\n        return input.div(MASS_FACTOR[outputUnits]);\n    }\n\n}\n\n\nconst MASS_UNITS = [\n    \"[Metric]\", \"Yoctogram (yg)\", \"Zeptogram (zg)\", \"Attogram (ag)\", \"Femtogram (fg)\", \"Picogram (pg)\", \"Nanogram (ng)\", \"Microgram (μg)\", \"Milligram (mg)\", \"Centigram (cg)\", \"Decigram (dg)\", \"Gram (g)\", \"Decagram (dag)\", \"Hectogram (hg)\", \"Kilogram (kg)\", \"Megagram (Mg)\", \"Tonne (t)\", \"Gigagram (Gg)\", \"Teragram (Tg)\", \"Petagram (Pg)\", \"Exagram (Eg)\", \"Zettagram (Zg)\", \"Yottagram (Yg)\", \"[/Metric]\",\n    \"[Imperial Avoirdupois]\", \"Grain (gr)\", \"Dram (dr)\", \"Ounce (oz)\", \"Pound (lb)\", \"Nail\", \"Stone (st)\", \"Quarter (gr)\", \"Tod\", \"US hundredweight (cwt)\", \"Imperial hundredweight (cwt)\", \"US ton (t)\", \"Imperial ton (t)\", \"[/Imperial Avoirdupois]\",\n    \"[Imperial Troy]\", \"Grain (gr)\", \"Pennyweight (dwt)\", \"Troy dram (dr t)\", \"Troy ounce (oz t)\", \"Troy pound (lb t)\", \"Mark\", \"[/Imperial Troy]\",\n    \"[Archaic]\", \"Wey\", \"Wool wey\", \"Suffolk wey\", \"Wool sack\", \"Coal sack\", \"Load\", \"Last\", \"Flax or feather last\", \"Gunpowder last\", \"Picul\", \"Rice last\", \"[/Archaic]\",\n    \"[Comparisons]\", \"Big Ben (14 tonnes)\", \"Blue whale (180 tonnes)\", \"International Space Station (417 tonnes)\", \"Space Shuttle (2,041 tonnes)\", \"RMS Titanic (52,000 tonnes)\", \"Great Pyramid of Giza (6,000,000 tonnes)\", \"Earth's oceans (1.4 yottagrams)\", \"[/Comparisons]\",\n    \"[Astronomical]\", \"A teaspoon of neutron star (5,500 million tonnes)\", \"Lunar mass (ML)\", \"Earth mass (M⊕)\", \"Jupiter mass (MJ)\", \"Solar mass (M☉)\", \"Sagittarius A* (7.5 x 10^36 kgs-ish)\", \"Milky Way galaxy (1.2 x 10^42 kgs)\", \"The observable universe (1.45 x 10^53 kgs)\", \"[/Astronomical]\",\n];\n\nconst MASS_FACTOR = { // Multiples of a gram\n    // Metric\n    \"Yoctogram (yg)\":     1e-24,\n    \"Zeptogram (zg)\":     1e-21,\n    \"Attogram (ag)\":      1e-18,\n    \"Femtogram (fg)\":     1e-15,\n    \"Picogram (pg)\":      1e-12,\n    \"Nanogram (ng)\":      1e-9,\n    \"Microgram (μg)\":     1e-6,\n    \"Milligram (mg)\":     1e-3,\n    \"Centigram (cg)\":     1e-2,\n    \"Decigram (dg)\":      1e-1,\n    \"Gram (g)\":           1,\n    \"Decagram (dag)\":     10,\n    \"Hectogram (hg)\":     100,\n    \"Kilogram (kg)\":      1000,\n    \"Megagram (Mg)\":      1e6,\n    \"Tonne (t)\":          1e6,\n    \"Gigagram (Gg)\":      1e9,\n    \"Teragram (Tg)\":      1e12,\n    \"Petagram (Pg)\":      1e15,\n    \"Exagram (Eg)\":       1e18,\n    \"Zettagram (Zg)\":     1e21,\n    \"Yottagram (Yg)\":     1e24,\n\n    // Imperial Avoirdupois\n    \"Grain (gr)\":         64.79891e-3,\n    \"Dram (dr)\":          1.7718451953125,\n    \"Ounce (oz)\":         28.349523125,\n    \"Pound (lb)\":         453.59237,\n    \"Nail\":               3175.14659,\n    \"Stone (st)\":         6.35029318e3,\n    \"Quarter (gr)\":       12700.58636,\n    \"Tod\":                12700.58636,\n    \"US hundredweight (cwt)\": 45.359237e3,\n    \"Imperial hundredweight (cwt)\": 50.80234544e3,\n    \"US ton (t)\":         907.18474e3,\n    \"Imperial ton (t)\":   1016.0469088e3,\n\n    // Imperial Troy\n    \"Pennyweight (dwt)\":  1.55517384,\n    \"Troy dram (dr t)\":   3.8879346,\n    \"Troy ounce (oz t)\":  31.1034768,\n    \"Troy pound (lb t)\":  373.2417216,\n    \"Mark\":               248.8278144,\n\n    // Archaic\n    \"Wey\":                76.5e3,\n    \"Wool wey\":           101.7e3,\n    \"Suffolk wey\":        161.5e3,\n    \"Wool sack\":          153000,\n    \"Coal sack\":          50.80234544e3,\n    \"Load\":               918000,\n    \"Last\":               1836000,\n    \"Flax or feather last\": 770e3,\n    \"Gunpowder last\":     1090e3,\n    \"Picul\":              60.478982e3,\n    \"Rice last\":          1200e3,\n\n    // Comparisons\n    \"Big Ben (14 tonnes)\": 14e6,\n    \"Blue whale (180 tonnes)\": 180e6,\n    \"International Space Station (417 tonnes)\": 417e6,\n    \"Space Shuttle (2,041 tonnes)\": 2041e6,\n    \"RMS Titanic (52,000 tonnes)\": 52000e6,\n    \"Great Pyramid of Giza (6,000,000 tonnes)\": 6e12,\n    \"Earth's oceans (1.4 yottagrams)\": 1.4e24,\n\n    // Astronomical\n    \"A teaspoon of neutron star (5,500 million tonnes)\": 5.5e15,\n    \"Lunar mass (ML)\":    7.342e25,\n    \"Earth mass (M⊕)\":    5.97219e27,\n    \"Jupiter mass (MJ)\":  1.8981411476999997e30,\n    \"Solar mass (M☉)\":    1.98855e33,\n    \"Sagittarius A* (7.5 x 10^36 kgs-ish)\": 7.5e39,\n    \"Milky Way galaxy (1.2 x 10^42 kgs)\": 1.2e45,\n    \"The observable universe (1.45 x 10^53 kgs)\": 1.45e56,\n};\n\n\nexport default ConvertMass;\n"
  },
  {
    "path": "src/core/operations/ConvertSpeed.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Convert speed operation\n */\nclass ConvertSpeed extends Operation {\n\n    /**\n     * ConvertSpeed constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert speed\";\n        this.module = \"Default\";\n        this.description = \"Converts a unit of speed to another format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Orders_of_magnitude_(speed)\";\n        this.inputType = \"BigNumber\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Input units\",\n                \"type\": \"option\",\n                \"value\": SPEED_UNITS\n            },\n            {\n                \"name\": \"Output units\",\n                \"type\": \"option\",\n                \"value\": SPEED_UNITS\n            }\n        ];\n    }\n\n    /**\n     * @param {BigNumber} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const [inputUnits, outputUnits] = args;\n\n        input = input.times(SPEED_FACTOR[inputUnits]);\n        return input.div(SPEED_FACTOR[outputUnits]);\n    }\n\n}\n\nconst SPEED_UNITS = [\n    \"[Metric]\", \"Metres per second (m/s)\", \"Kilometres per hour (km/h)\", \"[/Metric]\",\n    \"[Imperial]\", \"Miles per hour (mph)\", \"Knots (kn)\", \"[/Imperial]\",\n    \"[Comparisons]\", \"Human hair growth rate\", \"Bamboo growth rate\", \"World's fastest snail\", \"Usain Bolt's top speed\", \"Jet airliner cruising speed\", \"Concorde\", \"SR-71 Blackbird\", \"Space Shuttle\", \"International Space Station\", \"[/Comparisons]\",\n    \"[Scientific]\", \"Sound in standard atmosphere\", \"Sound in water\", \"Lunar escape velocity\", \"Earth escape velocity\", \"Earth's solar orbit\", \"Solar system's Milky Way orbit\", \"Milky Way relative to the cosmic microwave background\", \"Solar escape velocity\", \"Neutron star escape velocity (0.3c)\", \"Light in a diamond (0.4136c)\", \"Signal in an optical fibre (0.667c)\", \"Light (c)\", \"[/Scientific]\",\n];\n\nconst SPEED_FACTOR = { // Multiples of m/s\n    // Metric\n    \"Metres per second (m/s)\":           1,\n    \"Kilometres per hour (km/h)\":        0.2778,\n\n    // Imperial\n    \"Miles per hour (mph)\":              0.44704,\n    \"Knots (kn)\":                        0.5144,\n\n    // Comparisons\n    \"Human hair growth rate\":            4.8e-9,\n    \"Bamboo growth rate\":                1.4e-5,\n    \"World's fastest snail\":             0.00275,\n    \"Usain Bolt's top speed\":            12.42,\n    \"Jet airliner cruising speed\":       250,\n    \"Concorde\":                          603,\n    \"SR-71 Blackbird\":                   981,\n    \"Space Shuttle\":                     1400,\n    \"International Space Station\":       7700,\n\n    // Scientific\n    \"Sound in standard atmosphere\":      340.3,\n    \"Sound in water\":                    1500,\n    \"Lunar escape velocity\":             2375,\n    \"Earth escape velocity\":             11200,\n    \"Earth's solar orbit\":               29800,\n    \"Solar system's Milky Way orbit\":    200000,\n    \"Milky Way relative to the cosmic microwave background\": 552000,\n    \"Solar escape velocity\":             617700,\n    \"Neutron star escape velocity (0.3c)\": 100000000,\n    \"Light in a diamond (0.4136c)\":      124000000,\n    \"Signal in an optical fibre (0.667c)\": 200000000,\n    \"Light (c)\":                         299792458,\n};\n\n\nexport default ConvertSpeed;\n"
  },
  {
    "path": "src/core/operations/ConvertToNATOAlphabet.mjs",
    "content": "/**\n * @author MarvinJWendt [git@marvinjwendt.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Convert to NATO alphabet operation\n */\nclass ConvertToNATOAlphabet extends Operation {\n    /**\n     * ConvertToNATOAlphabet constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Convert to NATO alphabet\";\n        this.module = \"Default\";\n        this.description = \"Converts characters to their representation in the NATO phonetic alphabet.\";\n        this.infoURL = \"https://wikipedia.org/wiki/NATO_phonetic_alphabet\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return input.replace(/[a-z0-9,/.]/ig, letter => {\n            return lookup[letter.toUpperCase()];\n        });\n    }\n}\n\nconst lookup = {\n    \"A\": \"Alfa \",\n    \"B\": \"Bravo \",\n    \"C\": \"Charlie \",\n    \"D\": \"Delta \",\n    \"E\": \"Echo \",\n    \"F\": \"Foxtrot \",\n    \"G\": \"Golf \",\n    \"H\": \"Hotel \",\n    \"I\": \"India \",\n    \"J\": \"Juliett \",\n    \"K\": \"Kilo \",\n    \"L\": \"Lima \",\n    \"M\": \"Mike \",\n    \"N\": \"November \",\n    \"O\": \"Oscar \",\n    \"P\": \"Papa \",\n    \"Q\": \"Quebec \",\n    \"R\": \"Romeo \",\n    \"S\": \"Sierra \",\n    \"T\": \"Tango \",\n    \"U\": \"Uniform \",\n    \"V\": \"Victor \",\n    \"W\": \"Whiskey \",\n    \"X\": \"X-ray \",\n    \"Y\": \"Yankee \",\n    \"Z\": \"Zulu \",\n    \"0\": \"Zero \",\n    \"1\": \"One \",\n    \"2\": \"Two \",\n    \"3\": \"Three \",\n    \"4\": \"Four \",\n    \"5\": \"Five \",\n    \"6\": \"Six \",\n    \"7\": \"Seven \",\n    \"8\": \"Eight \",\n    \"9\": \"Nine \",\n    \",\": \"Comma \",\n    \"/\": \"Fraction bar \",\n    \".\": \"Full stop \",\n};\n\nexport default ConvertToNATOAlphabet;\n"
  },
  {
    "path": "src/core/operations/CountOccurrences.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Count occurrences operation\n */\nclass CountOccurrences extends Operation {\n\n    /**\n     * CountOccurrences constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Count occurrences\";\n        this.module = \"Default\";\n        this.description = \"Counts the number of times the provided string occurs in the input.\";\n        this.inputType = \"string\";\n        this.outputType = \"number\";\n        this.args = [\n            {\n                \"name\": \"Search string\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Regex\", \"Extended (\\\\n, \\\\t, \\\\x...)\", \"Simple string\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        let search = args[0].string;\n        const type = args[0].option;\n\n        if (type === \"Regex\" && search) {\n            try {\n                const regex = new RegExp(search, \"gi\"),\n                    matches = input.match(regex);\n                return matches.length;\n            } catch (err) {\n                return 0;\n            }\n        } else if (search) {\n            if (type.indexOf(\"Extended\") === 0) {\n                search = Utils.parseEscapedChars(search);\n            }\n            return input.count(search);\n        } else {\n            return 0;\n        }\n    }\n\n}\n\nexport default CountOccurrences;\n"
  },
  {
    "path": "src/core/operations/CoverImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport {\n    Jimp,\n    JimpMime,\n    ResizeStrategy,\n    HorizontalAlign,\n    VerticalAlign,\n} from \"jimp\";\n\n/**\n * Cover Image operation\n */\nclass CoverImage extends Operation {\n    /**\n     * CoverImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Cover Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Scales the image to the given width and height, keeping the aspect ratio. The image may be clipped.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Width\",\n                type: \"number\",\n                value: 100,\n                min: 1,\n            },\n            {\n                name: \"Height\",\n                type: \"number\",\n                value: 100,\n                min: 1,\n            },\n            {\n                name: \"Horizontal align\",\n                type: \"option\",\n                value: [\"Left\", \"Center\", \"Right\"],\n                defaultIndex: 1,\n            },\n            {\n                name: \"Vertical align\",\n                type: \"option\",\n                value: [\"Top\", \"Middle\", \"Bottom\"],\n                defaultIndex: 1,\n            },\n            {\n                name: \"Resizing algorithm\",\n                type: \"option\",\n                value: [\n                    \"Nearest Neighbour\",\n                    \"Bilinear\",\n                    \"Bicubic\",\n                    \"Hermite\",\n                    \"Bezier\",\n                ],\n                defaultIndex: 1,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [width, height, hAlign, vAlign, alg] = args;\n\n        const resizeMap = {\n            \"Nearest Neighbour\": ResizeStrategy.NEAREST_NEIGHBOR,\n            Bilinear: ResizeStrategy.BILINEAR,\n            Bicubic: ResizeStrategy.BICUBIC,\n            Hermite: ResizeStrategy.HERMITE,\n            Bezier: ResizeStrategy.BEZIER,\n        };\n\n        const alignMap = {\n            Left: HorizontalAlign.LEFT,\n            Center: HorizontalAlign.CENTER,\n            Right: HorizontalAlign.RIGHT,\n            Top: VerticalAlign.TOP,\n            Middle: VerticalAlign.MIDDLE,\n            Bottom: VerticalAlign.BOTTOM,\n        };\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Covering image...\");\n            image.cover({\n                w: width,\n                h: height,\n                align: alignMap[hAlign] | alignMap[vAlign],\n                mode: resizeMap[alg],\n            });\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error covering image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the covered image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default CoverImage;\n"
  },
  {
    "path": "src/core/operations/CropImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Crop Image operation\n */\nclass CropImage extends Operation {\n    /**\n     * CropImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Crop Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Crops an image to the specified region, or automatically crops edges.<br><br><b><u>Autocrop</u></b><br>Automatically crops same-colour borders from the image.<br><br><u>Autocrop tolerance</u><br>A percentage value for the tolerance of colour difference between pixels.<br><br><u>Only autocrop frames</u><br>Only crop real frames (all sides must have the same border)<br><br><u>Symmetric autocrop</u><br>Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)<br><br><u>Autocrop keep border</u><br>The number of pixels of border to leave around the image.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Cropping_(image)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"X Position\",\n                type: \"number\",\n                value: 0,\n                min: 0,\n            },\n            {\n                name: \"Y Position\",\n                type: \"number\",\n                value: 0,\n                min: 0,\n            },\n            {\n                name: \"Width\",\n                type: \"number\",\n                value: 10,\n                min: 1,\n            },\n            {\n                name: \"Height\",\n                type: \"number\",\n                value: 10,\n                min: 1,\n            },\n            {\n                name: \"Autocrop\",\n                type: \"boolean\",\n                value: false,\n            },\n            {\n                name: \"Autocrop tolerance (%)\",\n                type: \"number\",\n                value: 0.02,\n                min: 0,\n                max: 100,\n                step: 0.01,\n            },\n            {\n                name: \"Only autocrop frames\",\n                type: \"boolean\",\n                value: true,\n            },\n            {\n                name: \"Symmetric autocrop\",\n                type: \"boolean\",\n                value: false,\n            },\n            {\n                name: \"Autocrop keep border (px)\",\n                type: \"number\",\n                value: 0,\n                min: 0,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [\n            xPos,\n            yPos,\n            width,\n            height,\n            autocrop,\n            autoTolerance,\n            autoFrames,\n            autoSymmetric,\n            autoBorder,\n        ] = args;\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Cropping image...\");\n            if (autocrop) {\n                image.autocrop({\n                    tolerance: autoTolerance / 100,\n                    cropOnlyFrames: autoFrames,\n                    cropSymmetric: autoSymmetric,\n                    leaveBorder: autoBorder,\n                });\n            } else {\n                image.crop({\n                    x: xPos,\n                    y: yPos,\n                    w: width,\n                    h: height,\n                });\n            }\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error cropping image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the cropped image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default CropImage;\n"
  },
  {
    "path": "src/core/operations/DESDecrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\n\n/**\n * DES Decrypt operation\n */\nclass DESDecrypt extends Operation {\n\n    /**\n     * DESDecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"DES Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Data_Encryption_Standard\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\", \"CBC/NoPadding\", \"ECB/NoPadding\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            mode = args[2].substring(0, 3),\n            noPadding = args[2].endsWith(\"NoPadding\"),\n            [,,, inputType, outputType] = args;\n\n        if (key.length !== 8) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nDES uses a key length of 8 bytes (64 bits).`);\n        }\n        if (iv.length !== 8 && mode !== \"ECB\") {\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nDES uses an IV length of 8 bytes (64 bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        const decipher = forge.cipher.createDecipher(\"DES-\" + mode, key);\n\n        /* Allow for a \"no padding\" mode */\n        if (noPadding) {\n            decipher.mode.unpad = function(output, options) {\n                return true;\n            };\n        }\n\n        decipher.start({iv: iv});\n        decipher.update(forge.util.createBuffer(input));\n        const result = decipher.finish();\n\n        if (result) {\n            return outputType === \"Hex\" ? decipher.output.toHex() : decipher.output.getBytes();\n        } else {\n            throw new OperationError(\"Unable to decrypt input with these parameters.\");\n        }\n    }\n\n}\n\nexport default DESDecrypt;\n"
  },
  {
    "path": "src/core/operations/DESEncrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\n\n/**\n * DES Encrypt operation\n */\nclass DESEncrypt extends Operation {\n\n    /**\n     * DESEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"DES Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Data_Encryption_Standard\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            [,, mode, inputType, outputType] = args;\n\n        if (key.length !== 8) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nDES uses a key length of 8 bytes (64 bits).`);\n        }\n        if (iv.length !== 8 && mode !== \"ECB\") {\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nDES uses an IV length of 8 bytes (64 bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        const cipher = forge.cipher.createCipher(\"DES-\" + mode, key);\n        cipher.start({iv: iv});\n        cipher.update(forge.util.createBuffer(input));\n        cipher.finish();\n\n        return outputType === \"Hex\" ? cipher.output.toHex() : cipher.output.getBytes();\n    }\n\n}\n\nexport default DESEncrypt;\n"
  },
  {
    "path": "src/core/operations/DNSOverHTTPS.mjs",
    "content": "/**\n * @author h345983745\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * DNS over HTTPS operation\n */\nclass DNSOverHTTPS extends Operation {\n\n    /**\n     * DNSOverHTTPS constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"DNS over HTTPS\";\n        this.module = \"Default\";\n        this.description = [\n            \"Takes a single domain name and performs a DNS lookup using DNS over HTTPS.\",\n            \"<br><br>\",\n            \"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.\",\n            \"<br><br>\",\n            \"Can be used with any service that supports the GET parameters <code>name</code> and <code>type</code>.\"\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/DNS_over_HTTPS\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.manualBake = true;\n        this.args = [\n            {\n                name: \"Resolver\",\n                type: \"editableOption\",\n                value: [\n                    {\n                        name: \"Google\",\n                        value: \"https://dns.google.com/resolve\"\n                    },\n                    {\n                        name: \"Cloudflare\",\n                        value: \"https://cloudflare-dns.com/dns-query\"\n                    }\n                ]\n            },\n            {\n                name: \"Request Type\",\n                type: \"option\",\n                value: [\n                    \"A\",\n                    \"AAAA\",\n                    \"ANAME\",\n                    \"CERT\",\n                    \"CNAME\",\n                    \"DNSKEY\",\n                    \"HTTPS\",\n                    \"IPSECKEY\",\n                    \"LOC\",\n                    \"MX\",\n                    \"NS\",\n                    \"OPENPGPKEY\",\n                    \"PTR\",\n                    \"RRSIG\",\n                    \"SIG\",\n                    \"SOA\",\n                    \"SPF\",\n                    \"SRV\",\n                    \"SSHFP\",\n                    \"TA\",\n                    \"TXT\",\n                    \"URI\",\n                    \"ANY\"\n                ]\n            },\n            {\n                name: \"Answer Data Only\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Disable DNSSEC validation\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        const [resolver, requestType, justAnswer, DNSSEC] = args;\n        let url = URL;\n        try {\n            url = new URL(resolver);\n        } catch (error) {\n            throw new OperationError(error.toString() +\n            \"\\n\\nThis error could be caused by one of the following:\\n\" +\n            \" - An invalid Resolver URL\\n\");\n        }\n        const params = {name: input, type: requestType, cd: DNSSEC};\n\n        url.search = new URLSearchParams(params);\n\n        return fetch(url, {headers: {\"accept\": \"application/dns-json\"}}).then(response => {\n            return response.json();\n        }).then(data => {\n            if (justAnswer) {\n                return extractData(data.Answer);\n            }\n            return data;\n        }).catch(e => {\n            throw new OperationError(`Error making request to ${url}\\n${e.toString()}`);\n        });\n\n    }\n}\n\n/**\n * Construct an array of just data from a DNS Answer section\n *\n * @private\n * @param {JSON} data\n * @returns {JSON}\n */\nfunction extractData(data) {\n    if (typeof(data) == \"undefined\") {\n        return [];\n    } else {\n        const dataValues = [];\n        data.forEach(element => {\n            dataValues.push(element.data);\n        });\n        return dataValues;\n    }\n}\n\nexport default DNSOverHTTPS;\n"
  },
  {
    "path": "src/core/operations/DateTimeDelta.mjs",
    "content": "/**\n * @author tomgond [tom.gonda@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport moment from \"moment-timezone\";\nimport {DATETIME_FORMATS, FORMAT_EXAMPLES} from \"../lib/DateTime.mjs\";\n\n/**\n * DateTime Delta operation\n */\nclass DateTimeDelta extends Operation {\n\n    /**\n     * DateTimeDelta constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"DateTime Delta\";\n        this.module = \"Default\";\n        this.description = \"Calculates a new DateTime value given an input DateTime value and a time difference (delta) from the input DateTime value.\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Built in formats\",\n                \"type\": \"populateOption\",\n                \"value\": DATETIME_FORMATS,\n                \"target\": 1\n            },\n            {\n                \"name\": \"Input format string\",\n                \"type\": \"binaryString\",\n                \"value\": \"DD/MM/YYYY HH:mm:ss\"\n            },\n            {\n                \"name\": \"Time Operation\",\n                \"type\": \"option\",\n                \"value\": [\"Add\", \"Subtract\"]\n            },\n            {\n                \"name\": \"Days\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Hours\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Minutes\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Seconds\",\n                \"type\": \"number\",\n                \"value\": 0\n            }\n\n        ];\n    }\n\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const inputTimezone = \"UTC\";\n        const inputFormat = args[1];\n        const operationType = args[2];\n        const daysDelta = args[3];\n        const hoursDelta = args[4];\n        const minutesDelta = args[5];\n        const secondsDelta = args[6];\n        let date = \"\";\n\n        try {\n            date = moment.tz(input, inputFormat, inputTimezone);\n            if (!date || date.format() === \"Invalid date\") throw Error;\n        } catch (err) {\n            return `Invalid format.\\n\\n${FORMAT_EXAMPLES}`;\n        }\n        let newDate;\n        if (operationType === \"Add\") {\n            newDate = date.add(daysDelta, \"days\")\n                .add(hoursDelta, \"hours\")\n                .add(minutesDelta, \"minutes\")\n                .add(secondsDelta, \"seconds\");\n\n        } else {\n            newDate = date.add(-daysDelta, \"days\")\n                .add(-hoursDelta, \"hours\")\n                .add(-minutesDelta, \"minutes\")\n                .add(-secondsDelta, \"seconds\");\n        }\n        return newDate.tz(inputTimezone).format(inputFormat.replace(/[<>]/g, \"\"));\n    }\n}\n\nexport default DateTimeDelta;\n"
  },
  {
    "path": "src/core/operations/DechunkHTTPResponse.mjs",
    "content": "/**\n * @author sevzero [sevzero@protonmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Dechunk HTTP response operation\n */\nclass DechunkHTTPResponse extends Operation {\n\n    /**\n     * DechunkHTTPResponse constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Dechunk HTTP response\";\n        this.module = \"Default\";\n        this.description = \"Parses an HTTP response transferred using Transfer-Encoding: Chunked\";\n        this.infoURL = \"https://wikipedia.org/wiki/Chunked_transfer_encoding\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern:  \"^[0-9A-F]+\\r\\n\",\n                flags:  \"i\",\n                args:   []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const chunks = [];\n        let chunkSizeEnd = input.indexOf(\"\\n\") + 1;\n        const lineEndings = input.charAt(chunkSizeEnd - 2) === \"\\r\" ? \"\\r\\n\" : \"\\n\";\n        const lineEndingsLength = lineEndings.length;\n        let chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);\n        while (!isNaN(chunkSize)) {\n            chunks.push(input.slice(chunkSizeEnd, chunkSize + chunkSizeEnd));\n            input = input.slice(chunkSizeEnd + chunkSize + lineEndingsLength);\n            chunkSizeEnd = input.indexOf(lineEndings) + lineEndingsLength;\n            chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);\n        }\n        return chunks.join(\"\") + input;\n    }\n\n}\n\nexport default DechunkHTTPResponse;\n"
  },
  {
    "path": "src/core/operations/DecodeNetBIOSName.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Decode NetBIOS Name operation\n */\nclass DecodeNetBIOSName extends Operation {\n\n    /**\n     * DecodeNetBIOSName constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Decode NetBIOS Name\";\n        this.module = \"Default\";\n        this.description = \"NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.<br><br>There are two levels of encoding. The first level maps a NetBIOS name into a domain system name.  The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.<br><br>This operation decodes the first level of encoding. See RFC 1001 for full details.\";\n        this.infoURL = \"https://wikipedia.org/wiki/NetBIOS\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Offset\",\n                \"type\": \"number\",\n                \"value\": 65\n            }\n        ];\n        this.checks = [\n            {\n                pattern:  \"^\\\\s*\\\\S{32}$\",\n                flags:  \"\",\n                args:   [65]\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const output = [],\n            offset = args[0];\n\n        if (input.length <= 32 && (input.length % 2) === 0) {\n            for (let i = 0; i < input.length; i += 2) {\n                output.push((((input[i] & 0xff) - offset) << 4) |\n                            (((input[i + 1] & 0xff) - offset) & 0xf));\n            }\n            for (let i = output.length - 1; i > 0; i--) {\n                if (output[i] === 32) output.splice(i, i);\n                else break;\n            }\n        }\n\n        return output;\n    }\n\n}\n\nexport default DecodeNetBIOSName;\n"
  },
  {
    "path": "src/core/operations/DecodeText.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport cptable from \"codepage\";\nimport {CHR_ENC_CODE_PAGES} from \"../lib/ChrEnc.mjs\";\n\n/**\n * Decode text operation\n */\nclass DecodeText extends Operation {\n\n    /**\n     * DecodeText constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Decode text\";\n        this.module = \"Encodings\";\n        this.description = [\n            \"Decodes text from the chosen character encoding.\",\n            \"<br><br>\",\n            \"Supported charsets are:\",\n            \"<ul>\",\n            Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join(\"\\n\"),\n            \"</ul>\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Character_encoding\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Encoding\",\n                \"type\": \"option\",\n                \"value\": Object.keys(CHR_ENC_CODE_PAGES)\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const format = CHR_ENC_CODE_PAGES[args[0]];\n        return cptable.utils.decode(format, new Uint8Array(input));\n    }\n\n}\n\nexport default DecodeText;\n"
  },
  {
    "path": "src/core/operations/DefangIPAddresses.mjs",
    "content": "/**\n * @author h345983745\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n\n/**\n * Defang IP Addresses operation\n */\nclass DefangIPAddresses extends Operation {\n\n    /**\n     * DefangIPAddresses constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Defang IP Addresses\";\n        this.module = \"Default\";\n        this.description = \"Takes a IPv4 or IPv6 address and 'Defangs' it, meaning the IP becomes invalid, removing the risk of accidentally utilising it as an IP address.\";\n        this.infoURL = \"https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern: \"^\\\\s*(([0-9]{1,3}\\\\.){3}[0-9]{1,3}|([0-9a-f]{4}:){7}[0-9a-f]{4})\\\\s*$\",\n                flags: \"i\",\n                args: [],\n                output: {\n                    pattern: \"^\\\\s*(([0-9]{1,3}\\\\[\\\\.\\\\]){3}[0-9]{1,3}|([0-9a-f]{4}\\\\[\\\\:\\\\]){7}[0-9a-f]{4})\\\\s*$\",\n                    flags: \"i\"\n                }\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = input.replace(IPV4_REGEX, x => {\n            return x.replace(/\\./g, \"[.]\");\n        });\n\n        input = input.replace(IPV6_REGEX, x => {\n            return x.replace(/:/g, \"[:]\");\n        });\n\n        return input;\n    }\n}\n\nexport default DefangIPAddresses;\n\n\n/**\n * IPV4 regular expression\n */\nconst IPV4_REGEX = new RegExp(\"(?:(?:\\\\d|[01]?\\\\d\\\\d|2[0-4]\\\\d|25[0-5])\\\\.){3}(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d\\\\d|\\\\d)(?:\\\\/\\\\d{1,2})?\", \"g\");\n\n\n/**\n * IPV6 regular expression\n */\nconst IPV6_REGEX = new RegExp(\"((?=.*::)(?!.*::.+::)(::)?([\\\\dA-Fa-f]{1,4}:(:|\\\\b)|){5}|([\\\\dA-Fa-f]{1,4}:){6})((([\\\\dA-Fa-f]{1,4}((?!\\\\3)::|:\\\\b|(?![\\\\dA-Fa-f])))|(?!\\\\2\\\\3)){2}|(((2[0-4]|1\\\\d|[1-9])?\\\\d|25[0-5])\\\\.?\\\\b){4})\", \"g\");\n"
  },
  {
    "path": "src/core/operations/DefangURL.mjs",
    "content": "/**\n * @author arnydo [arnydo@protonmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {URL_REGEX, DOMAIN_REGEX} from \"../lib/Extract.mjs\";\n\n/**\n * DefangURL operation\n */\nclass DefangURL extends Operation {\n\n    /**\n     * DefangURL constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Defang URL\";\n        this.module = \"Default\";\n        this.description = \"Takes a Universal Resource Locator (URL) and 'Defangs' it; meaning the URL becomes invalid, neutralising the risk of accidentally clicking on a malicious link.<br><br>This is often used when dealing with malicious links or IOCs.<br><br>Works well when combined with the 'Extract URLs' operation.\";\n        this.infoURL = \"https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Escape dots\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Escape http\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Escape ://\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Process\",\n                type: \"option\",\n                value: [\"Valid domains and full URLs\", \"Only full URLs\", \"Everything\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [dots, http, slashes, process] = args;\n\n        switch (process) {\n            case \"Valid domains and full URLs\":\n                input = input.replace(URL_REGEX, x => {\n                    return defangURL(x, dots, http, slashes);\n                });\n                input = input.replace(DOMAIN_REGEX, x => {\n                    return defangURL(x, dots, http, slashes);\n                });\n                break;\n            case \"Only full URLs\":\n                input = input.replace(URL_REGEX, x => {\n                    return defangURL(x, dots, http, slashes);\n                });\n                break;\n            case \"Everything\":\n                input = defangURL(input, dots, http, slashes);\n                break;\n        }\n\n        return input;\n    }\n\n}\n\n\n/**\n * Defangs a given URL\n *\n * @param {string} url\n * @param {boolean} dots\n * @param {boolean} http\n * @param {boolean} slashes\n * @returns {string}\n */\nfunction defangURL(url, dots, http, slashes) {\n    if (dots) url = url.replace(/\\./g, \"[.]\");\n    if (http) url = url.replace(/http/gi, \"hxxp\");\n    if (slashes) url = url.replace(/:\\/\\//g, \"[://]\");\n\n    return url;\n}\n\nexport default DefangURL;\n"
  },
  {
    "path": "src/core/operations/DeriveEVPKey.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport CryptoJS from \"crypto-js\";\n\n/**\n * Derive EVP key operation\n */\nclass DeriveEVPKey extends Operation {\n\n    /**\n     * DeriveEVPKey constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Derive EVP key\";\n        this.module = \"Ciphers\";\n        this.description = \"This operation performs a password-based key derivation function (PBKDF) used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.<br><br>A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.<br><br>If you leave the salt argument empty, a random salt will be generated.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Key_derivation_function\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Passphrase\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"UTF8\", \"Latin1\", \"Hex\", \"Base64\"]\n            },\n            {\n                \"name\": \"Key size\",\n                \"type\": \"number\",\n                \"value\": 128\n            },\n            {\n                \"name\": \"Iterations\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"Hashing function\",\n                \"type\": \"option\",\n                \"value\": [\"SHA1\", \"SHA256\", \"SHA384\", \"SHA512\", \"MD5\"]\n            },\n            {\n                \"name\": \"Salt\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const passphrase = CryptoJS.enc.Latin1.parse(\n                Utils.convertToByteString(args[0].string, args[0].option)),\n            keySize = args[1] / 32,\n            iterations = args[2],\n            hasher = args[3],\n            salt = CryptoJS.enc.Latin1.parse(\n                Utils.convertToByteString(args[4].string, args[4].option)),\n            key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash]\n                keySize: keySize,\n                hasher: CryptoJS.algo[hasher],\n                iterations: iterations,\n            });\n\n        return key.toString(CryptoJS.enc.Hex);\n    }\n\n}\n\nexport default DeriveEVPKey;\n\n/**\n * Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a\n * salt in.\n\n * @param {string} password - The password to derive from.\n * @param {number} keySize - The size in words of the key to generate.\n * @param {number} ivSize - The size in words of the IV to generate.\n * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be\n *                 generated randomly. If set to false, no salt will be added.\n *\n * @returns {CipherParams} A cipher params object with the key, IV, and salt.\n *\n * @static\n *\n * @example\n * // Randomly generates a salt\n * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32);\n * // Uses the salt 'saltsalt'\n * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt');\n * // Does not use a salt\n * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false);\n */\nCryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) {\n    // Generate random salt if no salt specified and not set to false\n    // This line changed from `if (!salt) {` to the following\n    if (salt === undefined || salt === null) {\n        salt = CryptoJS.lib.WordArray.random(64/8);\n    }\n\n    // Derive key and IV\n    const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt);\n\n    // Separate key and IV\n    const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4);\n    key.sigBytes = keySize * 4;\n\n    // Return params\n    return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });\n};\n\n\n/**\n * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse\n * the hex string.\n *\n * @param {string} hexStr\n * @returns {CryptoJS.lib.WordArray}\n */\nCryptoJS.enc.Hex.parse = function (hexStr) {\n    // Remove whitespace\n    hexStr = hexStr.replace(/\\s/g, \"\");\n\n    // Shortcut\n    const hexStrLength = hexStr.length;\n\n    // Convert\n    const words = [];\n    for (let i = 0; i < hexStrLength; i += 2) {\n        words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);\n    }\n\n    return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);\n};\n"
  },
  {
    "path": "src/core/operations/DeriveHKDFKey.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport CryptoApi from \"crypto-api/src/crypto-api.mjs\";\n\n/**\n * Derive HKDF Key operation\n */\nclass DeriveHKDFKey extends Operation {\n\n    /**\n     * DeriveHKDFKey constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Derive HKDF key\";\n        this.module = \"Crypto\";\n        this.description = \"A simple Hashed Message Authenticaton Code (HMAC)-based key derivation function (HKDF), defined in RFC5869.\";\n        this.infoURL = \"https://wikipedia.org/wiki/HKDF\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Salt\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"Decimal\", \"Base64\", \"UTF8\", \"Latin1\"]\n            },\n            {\n                \"name\": \"Info\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"Decimal\", \"Base64\", \"UTF8\", \"Latin1\"]\n            },\n            {\n                \"name\": \"Hashing function\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"MD2\",\n                    \"MD4\",\n                    \"MD5\",\n                    \"SHA0\",\n                    \"SHA1\",\n                    \"SHA224\",\n                    \"SHA256\",\n                    \"SHA384\",\n                    \"SHA512\",\n                    \"SHA512/224\",\n                    \"SHA512/256\",\n                    \"RIPEMD128\",\n                    \"RIPEMD160\",\n                    \"RIPEMD256\",\n                    \"RIPEMD320\",\n                    \"HAS160\",\n                    \"Whirlpool\",\n                    \"Whirlpool-0\",\n                    \"Whirlpool-T\",\n                    \"Snefru\"\n                ],\n                \"defaultIndex\": 6\n            },\n            {\n                \"name\": \"Extract mode\",\n                \"type\": \"argSelector\",\n                \"value\": [\n                    {\n                        \"name\": \"with salt\",\n                        \"on\": [0]\n                    },\n                    {\n                        \"name\": \"no salt\",\n                        \"off\": [0]\n                    },\n                    {\n                        \"name\": \"skip\",\n                        \"off\": [0]\n                    }\n                ]\n            },\n            {\n                \"name\": \"L (number of output octets)\",\n                \"type\": \"number\",\n                \"value\": 16,\n                \"min\": 0\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const argSalt = Utils.convertToByteString(args[0].string || \"\", args[0].option),\n            info = Utils.convertToByteString(args[1].string || \"\", args[1].option),\n            hashFunc = args[2].toLowerCase(),\n            extractMode = args[3],\n            L = args[4],\n            IKM = Utils.arrayBufferToStr(input, false),\n            hasher = CryptoApi.getHasher(hashFunc),\n            HashLen = hasher.finalize().length;\n\n        if (L < 0) {\n            throw new OperationError(\"L must be non-negative\");\n        }\n        if (L > 255 * HashLen) {\n            throw new OperationError(\"L too large (maximum length for \" + args[2] + \" is \" + (255 * HashLen) + \")\");\n        }\n\n        const hmacHash = function(key, data) {\n            hasher.reset();\n            const mac = CryptoApi.getHmac(key, hasher);\n            mac.update(data);\n            return mac.finalize();\n        };\n        const salt = extractMode === \"with salt\" ? argSalt : \"\\0\".repeat(HashLen);\n        const PRK = extractMode === \"skip\" ? IKM : hmacHash(salt, IKM);\n        let T = \"\";\n        let result = \"\";\n        for (let i = 1; i <= 255 && result.length < L; i++) {\n            const TNext = hmacHash(PRK, T + info + String.fromCharCode(i));\n            result += TNext;\n            T = TNext;\n        }\n        return CryptoApi.encoder.toHex(result.substring(0, L));\n    }\n\n}\n\nexport default DeriveHKDFKey;\n"
  },
  {
    "path": "src/core/operations/DerivePBKDF2Key.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\n\n/**\n * Derive PBKDF2 key operation\n */\nclass DerivePBKDF2Key extends Operation {\n\n    /**\n     * DerivePBKDF2Key constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Derive PBKDF2 key\";\n        this.module = \"Ciphers\";\n        this.description = \"PBKDF2 is a password-based key derivation function. It is part of RSA Laboratories' Public-Key Cryptography Standards (PKCS) series, specifically PKCS #5 v2.0, also published as Internet Engineering Task Force's RFC 2898.<br><br>In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.<br><br>A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.<br><br>If you leave the salt argument empty, a random salt will be generated.\";\n        this.infoURL = \"https://wikipedia.org/wiki/PBKDF2\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Passphrase\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"UTF8\", \"Latin1\", \"Hex\", \"Base64\"]\n            },\n            {\n                \"name\": \"Key size\",\n                \"type\": \"number\",\n                \"value\": 128\n            },\n            {\n                \"name\": \"Iterations\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"Hashing function\",\n                \"type\": \"option\",\n                \"value\": [\"SHA1\", \"SHA256\", \"SHA384\", \"SHA512\", \"MD5\"]\n            },\n            {\n                \"name\": \"Salt\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const passphrase = Utils.convertToByteString(args[0].string, args[0].option),\n            keySize = args[1],\n            iterations = args[2],\n            hasher = args[3],\n            salt = Utils.convertToByteString(args[4].string, args[4].option) ||\n                forge.random.getBytesSync(keySize),\n            derivedKey = forge.pkcs5.pbkdf2(passphrase, salt, iterations, keySize / 8, hasher.toLowerCase());\n\n        return forge.util.bytesToHex(derivedKey);\n    }\n\n}\n\nexport default DerivePBKDF2Key;\n"
  },
  {
    "path": "src/core/operations/DetectFileType.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {detectFileType} from \"../lib/FileType.mjs\";\nimport {FILE_SIGNATURES} from \"../lib/FileSignatures.mjs\";\n\n// Concat all supported extensions into a single flat list\nconst exts = [].concat.apply([], Object.keys(FILE_SIGNATURES).map(cat =>\n    [].concat.apply([], FILE_SIGNATURES[cat].map(sig =>\n        sig.extension.split(\",\")\n    ))\n)).unique().sort().join(\", \");\n\n/**\n * Detect File Type operation\n */\nclass DetectFileType extends Operation {\n\n    /**\n     * DetectFileType constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Detect File Type\";\n        this.module = \"Default\";\n        this.description = \"Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>Currently supports the following file types: \" +\n            exts + \".\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_file_signatures\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = Object.keys(FILE_SIGNATURES).map(cat => {\n            return {\n                name: cat,\n                type: \"boolean\",\n                value: true\n            };\n        });\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const data = new Uint8Array(input),\n            categories = [];\n\n        args.forEach((cat, i) => {\n            if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);\n        });\n\n        const types = detectFileType(data, categories);\n\n        if (!types.length) {\n            return \"Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?\";\n        } else {\n            const results = types.map(type => {\n                let output = `File type:   ${type.name}\nExtension:   ${type.extension}\nMIME type:   ${type.mime}\\n`;\n\n                if (type?.description?.length) {\n                    output += `Description: ${type.description}\\n`;\n                }\n\n                return output;\n            });\n\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default DetectFileType;\n"
  },
  {
    "path": "src/core/operations/Diff.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport * as JsDiff from \"diff\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Diff operation\n */\nclass Diff extends Operation {\n\n    /**\n     * Diff constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Diff\";\n        this.module = \"Diff\";\n        this.description = \"Compares two inputs (separated by the specified delimiter) and highlights the differences between them.\";\n        this.infoURL = \"https://wikipedia.org/wiki/File_comparison\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Sample delimiter\",\n                \"type\": \"binaryString\",\n                \"value\": \"\\\\n\\\\n\"\n            },\n            {\n                \"name\": \"Diff by\",\n                \"type\": \"option\",\n                \"value\": [\"Character\", \"Word\", \"Line\", \"Sentence\", \"CSS\", \"JSON\"]\n            },\n            {\n                \"name\": \"Show added\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Show removed\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Show subtraction\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Ignore whitespace\",\n                \"type\": \"boolean\",\n                \"value\": false,\n                \"hint\": \"Relevant for word and line\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const [\n                sampleDelim,\n                diffBy,\n                showAdded,\n                showRemoved,\n                showSubtraction,\n                ignoreWhitespace\n            ] = args,\n            samples = input.split(sampleDelim);\n        let output = \"\",\n            diff;\n\n        // Node and Webpack load modules slightly differently\n        const jsdiff = JsDiff.default ? JsDiff.default : JsDiff;\n\n        if (!samples || samples.length !== 2) {\n            throw new OperationError(\"Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?\");\n        }\n\n        switch (diffBy) {\n            case \"Character\":\n                diff = jsdiff.diffChars(samples[0], samples[1]);\n                break;\n            case \"Word\":\n                if (ignoreWhitespace) {\n                    diff = jsdiff.diffWords(samples[0], samples[1]);\n                } else {\n                    diff = jsdiff.diffWordsWithSpace(samples[0], samples[1]);\n                }\n                break;\n            case \"Line\":\n                if (ignoreWhitespace) {\n                    diff = jsdiff.diffTrimmedLines(samples[0], samples[1]);\n                } else {\n                    diff = jsdiff.diffLines(samples[0], samples[1]);\n                }\n                break;\n            case \"Sentence\":\n                diff = jsdiff.diffSentences(samples[0], samples[1]);\n                break;\n            case \"CSS\":\n                diff = jsdiff.diffCss(samples[0], samples[1]);\n                break;\n            case \"JSON\":\n                diff = jsdiff.diffJson(samples[0], samples[1]);\n                break;\n            default:\n                throw new OperationError(\"Invalid 'Diff by' option.\");\n        }\n\n        for (let i = 0; i < diff.length; i++) {\n            if (diff[i].added) {\n                if (showAdded) output += \"<ins>\" + Utils.escapeHtml(diff[i].value) + \"</ins>\";\n            } else if (diff[i].removed) {\n                if (showRemoved) output += \"<del>\" + Utils.escapeHtml(diff[i].value) + \"</del>\";\n            } else if (!showSubtraction) {\n                output += Utils.escapeHtml(diff[i].value);\n            }\n        }\n\n        return output;\n    }\n\n}\n\nexport default Diff;\n"
  },
  {
    "path": "src/core/operations/DisassembleARM.mjs",
    "content": "/**\n * @author MedjedThomasXM\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport cs from \"@alexaltea/capstone-js/dist/capstone.min.js\";\n\n/**\n * Disassemble ARM operation\n */\nclass DisassembleARM extends Operation {\n\n    /**\n     * DisassembleARM constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Disassemble ARM\";\n        this.module = \"Shellcode\";\n        this.description = \"Disassembles ARM machine code into assembly language.<br><br>Supports ARM (32-bit), Thumb, and ARM64 (AArch64) architectures using the Capstone disassembly framework.<br><br>Input should be in hexadecimal.\";\n        this.infoURL = \"https://wikipedia.org/wiki/ARM_architecture_family\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Architecture\",\n                \"type\": \"option\",\n                \"value\": [\"ARM (32-bit)\", \"ARM64 (AArch64)\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"ARM\", \"Thumb\", \"Thumb + Cortex-M\", \"ARMv8\"]\n            },\n            {\n                \"name\": \"Endianness\",\n                \"type\": \"option\",\n                \"value\": [\"Little Endian\", \"Big Endian\"]\n            },\n            {\n                \"name\": \"Starting address (hex)\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Show instruction hex\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Show instruction position\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [\n            architecture,\n            mode,\n            endianness,\n            startAddress,\n            showHex,\n            showPosition\n        ] = args;\n\n        // Remove whitespace from input\n        const hexInput = input.replace(/\\s/g, \"\");\n\n        // Validate hex input\n        if (!/^[0-9a-fA-F]*$/.test(hexInput)) {\n            throw new OperationError(\"Invalid hexadecimal input. Please provide valid hex characters only.\");\n        }\n\n        if (hexInput.length === 0) {\n            return \"\";\n        }\n\n        if (hexInput.length % 2 !== 0) {\n            throw new OperationError(\"Invalid hexadecimal input. Length must be even.\");\n        }\n\n        // Convert hex string to byte array\n        const bytes = [];\n        for (let i = 0; i < hexInput.length; i += 2) {\n            bytes.push(parseInt(hexInput.substr(i, 2), 16));\n        }\n\n        // Determine architecture constant\n        let arch;\n        if (architecture === \"ARM64 (AArch64)\") {\n            arch = cs.ARCH_ARM64;\n        } else {\n            arch = cs.ARCH_ARM;\n        }\n\n        // Determine mode constant\n        let modeValue = cs.MODE_LITTLE_ENDIAN;\n\n        if (architecture === \"ARM (32-bit)\") {\n            switch (mode) {\n                case \"ARM\":\n                    modeValue = cs.MODE_ARM;\n                    break;\n                case \"Thumb\":\n                    modeValue = cs.MODE_THUMB;\n                    break;\n                case \"Thumb + Cortex-M\":\n                    modeValue = cs.MODE_THUMB | cs.MODE_MCLASS;\n                    break;\n                case \"ARMv8\":\n                    modeValue = cs.MODE_ARM | cs.MODE_V8;\n                    break;\n                default:\n                    modeValue = cs.MODE_ARM;\n            }\n        } else {\n            // ARM64 only has one mode (ARM mode is default for ARM64)\n            modeValue = cs.MODE_ARM;\n        }\n\n        // Add endianness\n        if (endianness === \"Big Endian\") {\n            modeValue |= cs.MODE_BIG_ENDIAN;\n        }\n\n        if (isWorkerEnvironment()) {\n            self.sendStatusMessage(\"Disassembling...\");\n        }\n\n        let disassembler;\n        try {\n            disassembler = new cs.Capstone(arch, modeValue);\n        } catch (e) {\n            throw new OperationError(`Failed to initialise Capstone disassembler: ${e}`);\n        }\n\n        let instructions;\n        try {\n            instructions = disassembler.disasm(bytes, startAddress);\n        } catch (e) {\n            disassembler.close();\n            // Check if it's a \"no valid instructions\" error (code 0 means OK but nothing decoded)\n            if (e && e.includes && e.includes(\"code 0:\")) {\n                throw new OperationError(`No valid ${architecture} instructions found in input. The bytes may be for a different architecture or mode.`);\n            }\n            throw new OperationError(`Disassembly failed: ${e}`);\n        }\n\n        // Format output\n        const output = [];\n        for (const insn of instructions) {\n            let line = \"\";\n\n            if (showPosition) {\n                // Format address as hex with 0x prefix\n                const addrHex = \"0x\" + insn.address.toString(16).padStart(8, \"0\");\n                line += addrHex + \"  \";\n            }\n\n            if (showHex) {\n                // Format instruction bytes as hex\n                const bytesHex = insn.bytes.map(b => b.toString(16).padStart(2, \"0\")).join(\"\");\n                line += bytesHex.padEnd(16, \" \") + \"  \";\n            }\n\n            line += insn.mnemonic;\n            if (insn.op_str) {\n                line += \" \" + insn.op_str;\n            }\n\n            output.push(line);\n        }\n\n        disassembler.close();\n\n        return output.join(\"\\n\");\n    }\n\n}\n\nexport default DisassembleARM;\n"
  },
  {
    "path": "src/core/operations/DisassembleX86.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport * as disassemble from \"../vendor/DisassembleX86-64.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Disassemble x86 operation\n */\nclass DisassembleX86 extends Operation {\n\n    /**\n     * DisassembleX86 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Disassemble x86\";\n        this.module = \"Shellcode\";\n        this.description = \"Disassembly is the process of translating machine language into assembly language.<br><br>This operation supports 64-bit, 32-bit and 16-bit code written for Intel or AMD x86 processors. It is particularly useful for reverse engineering shellcode.<br><br>Input should be in hexadecimal.\";\n        this.infoURL = \"https://wikipedia.org/wiki/X86\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Bit mode\",\n                \"type\": \"option\",\n                \"value\": [\"64\", \"32\", \"16\"]\n            },\n            {\n                \"name\": \"Compatibility\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Full x86 architecture\",\n                    \"Knights Corner\",\n                    \"Larrabee\",\n                    \"Cyrix\",\n                    \"Geode\",\n                    \"Centaur\",\n                    \"X86/486\"\n                ]\n            },\n            {\n                \"name\": \"Code Segment (CS)\",\n                \"type\": \"number\",\n                \"value\": 16\n            },\n            {\n                \"name\": \"Offset (IP)\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Show instruction hex\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Show instruction position\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if invalid mode value\n     */\n    run(input, args) {\n        const [\n            mode,\n            compatibility,\n            codeSegment,\n            offset,\n            showInstructionHex,\n            showInstructionPos\n        ] = args;\n\n        switch (mode) {\n            case \"64\":\n                disassemble.setBitMode(2);\n                break;\n            case \"32\":\n                disassemble.setBitMode(1);\n                break;\n            case \"16\":\n                disassemble.setBitMode(0);\n                break;\n            default:\n                throw new OperationError(\"Invalid mode value\");\n        }\n\n        switch (compatibility) {\n            case \"Full x86 architecture\":\n                disassemble.CompatibilityMode(0);\n                break;\n            case \"Knights Corner\":\n                disassemble.CompatibilityMode(1);\n                break;\n            case \"Larrabee\":\n                disassemble.CompatibilityMode(2);\n                break;\n            case \"Cyrix\":\n                disassemble.CompatibilityMode(3);\n                break;\n            case \"Geode\":\n                disassemble.CompatibilityMode(4);\n                break;\n            case \"Centaur\":\n                disassemble.CompatibilityMode(5);\n                break;\n            case \"X86/486\":\n                disassemble.CompatibilityMode(6);\n                break;\n        }\n\n        disassemble.SetBasePosition(codeSegment + \":\" + offset);\n        disassemble.setShowInstructionHex(showInstructionHex);\n        disassemble.setShowInstructionPos(showInstructionPos);\n        disassemble.LoadBinCode(input.replace(/\\s/g, \"\"));\n        return disassemble.LDisassemble();\n    }\n\n}\n\nexport default DisassembleX86;\n"
  },
  {
    "path": "src/core/operations/DitherImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Image Dither operation\n */\nclass DitherImage extends Operation {\n    /**\n     * DitherImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Dither Image\";\n        this.module = \"Image\";\n        this.description = \"Apply a dither effect to an image.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Dither\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Applying dither to image...\");\n            image.dither();\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(\n                `Error applying dither to image. (${err})`,\n            );\n        }\n    }\n\n    /**\n     * Displays the dithered image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default DitherImage;\n"
  },
  {
    "path": "src/core/operations/Divide.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport BigNumber from \"bignumber.js\";\nimport Operation from \"../Operation.mjs\";\nimport { div, createNumArray } from \"../lib/Arithmetic.mjs\";\nimport { ARITHMETIC_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n\n/**\n * Divide operation\n */\nclass Divide extends Operation {\n\n    /**\n     * Divide constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Divide\";\n        this.module = \"Default\";\n        this.description = \"Divides a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>2.5</code>\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": ARITHMETIC_DELIM_OPTIONS,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const val = div(createNumArray(input, args[0]));\n        return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);\n    }\n\n}\n\nexport default Divide;\n"
  },
  {
    "path": "src/core/operations/DropBytes.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Drop bytes operation\n */\nclass DropBytes extends Operation {\n\n    /**\n     * DropBytes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Drop bytes\";\n        this.module = \"Default\";\n        this.description = \"Cuts a slice of the specified number of bytes out of the data. Negative values are allowed.\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Start\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Length\",\n                \"type\": \"number\",\n                \"value\": 5\n            },\n            {\n                \"name\": \"Apply to each line\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     *\n     * @throws {OperationError} if invalid input\n     */\n    run(input, args) {\n        let start = args[0],\n            length = args[1];\n        const applyToEachLine = args[2];\n\n        if (!applyToEachLine) {\n            if (start < 0) { // Take from the end\n                start = input.byteLength + start;\n            }\n\n            if (length < 0) { // Flip start point\n                start = start + length;\n                if (start < 0) {\n                    start = input.byteLength + start;\n                    length = start - length;\n                } else {\n                    length = -length;\n                }\n            }\n\n            const left = input.slice(0, start),\n                right = input.slice(start + length, input.byteLength);\n            const result = new Uint8Array(left.byteLength + right.byteLength);\n            result.set(new Uint8Array(left), 0);\n            result.set(new Uint8Array(right), left.byteLength);\n            return result.buffer;\n        }\n\n        // Split input into lines\n        const data = new Uint8Array(input);\n        const lines = [];\n        let line = [],\n            i;\n\n        for (i = 0; i < data.length; i++) {\n            if (data[i] === 0x0a) {\n                lines.push(line);\n                line = [];\n            } else {\n                line.push(data[i]);\n            }\n        }\n        lines.push(line);\n\n        let output = [],\n            s = start,\n            l = length;\n        for (i = 0; i < lines.length; i++) {\n            if (s < 0) { // Take from the end\n                s = lines[i].length + s;\n            }\n\n            if (l < 0) { // Flip start point\n                s = s + l;\n                if (s < 0) {\n                    s = lines[i].length + s;\n                    l = s - l;\n                } else {\n                    l = -l;\n                }\n            }\n\n            output = output.concat(lines[i].slice(0, s).concat(lines[i].slice(s+l, lines[i].length)));\n            output.push(0x0a);\n            s = start;\n            l = length;\n        }\n        return new Uint8Array(output.slice(0, output.length-1)).buffer;\n    }\n\n}\n\nexport default DropBytes;\n"
  },
  {
    "path": "src/core/operations/DropNthBytes.mjs",
    "content": "/**\n * @author Oshawk [oshawk@protonmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Drop nth bytes operation\n */\nclass DropNthBytes extends Operation {\n\n    /**\n     * DropNthBytes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Drop nth bytes\";\n        this.module = \"Default\";\n        this.description = \"Drops every nth byte starting with a given byte.\";\n        this.infoURL = \"\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Drop every\",\n                type: \"number\",\n                value: 4\n            },\n            {\n                name: \"Starting at\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Apply to each line\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const n = args[0];\n        const start = args[1];\n        const eachLine = args[2];\n\n        if (parseInt(n, 10) !== n || n <= 0) {\n            throw new OperationError(\"'Drop every' must be a positive integer.\");\n        }\n        if (parseInt(start, 10) !== start || start < 0) {\n            throw new OperationError(\"'Starting at' must be a positive or zero integer.\");\n        }\n\n        let offset = 0;\n        const output = [];\n        for (let i = 0; i < input.length; i++) {\n            if (eachLine && input[i] === 0x0a) {\n                output.push(0x0a);\n                offset = i + 1;\n            } else if (i - offset < start || (i - (start + offset)) % n !== 0) {\n                output.push(input[i]);\n            }\n        }\n\n        return output;\n    }\n\n}\n\nexport default DropNthBytes;\n"
  },
  {
    "path": "src/core/operations/ECDSASign.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { fromHex } from \"../lib/Hex.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport r from \"jsrsasign\";\n\n/**\n * ECDSA Sign operation\n */\nclass ECDSASign extends Operation {\n\n    /**\n     * ECDSASign constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ECDSA Sign\";\n        this.module = \"Ciphers\";\n        this.description = \"Sign a plaintext message with a PEM encoded EC key.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"ECDSA Private Key (PEM)\",\n                type: \"text\",\n                value: \"-----BEGIN EC PRIVATE KEY-----\"\n            },\n            {\n                name: \"Message Digest Algorithm\",\n                type: \"option\",\n                value: [\n                    \"SHA-256\",\n                    \"SHA-384\",\n                    \"SHA-512\",\n                    \"SHA-1\",\n                    \"MD5\"\n                ]\n            },\n            {\n                name: \"Output Format\",\n                type: \"option\",\n                value: [\n                    \"ASN.1 HEX\",\n                    \"P1363 HEX\",\n                    \"JSON Web Signature\",\n                    \"Raw JSON\"\n                ]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [keyPem, mdAlgo, outputFormat] = args;\n\n        if (keyPem.replace(\"-----BEGIN EC PRIVATE KEY-----\", \"\").length === 0) {\n            throw new OperationError(\"Please enter a private key.\");\n        }\n\n        const internalAlgorithmName = mdAlgo.replace(\"-\", \"\") + \"withECDSA\";\n        const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });\n        const key = r.KEYUTIL.getKey(keyPem);\n        if (key.type !== \"EC\") {\n            throw new OperationError(\"Provided key is not an EC key.\");\n        }\n        if (!key.isPrivate) {\n            throw new OperationError(\"Provided key is not a private key.\");\n        }\n        sig.init(key);\n        const signatureASN1Hex = sig.signString(input);\n\n        let result;\n        switch (outputFormat) {\n            case \"ASN.1 HEX\":\n                result = signatureASN1Hex;\n                break;\n            case \"P1363 HEX\":\n                result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);\n                break;\n            case \"JSON Web Signature\":\n                result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);\n                result = toBase64(fromHex(result), \"A-Za-z0-9-_\");  // base64url\n                break;\n            case \"Raw JSON\": {\n                const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);\n                result = JSON.stringify(signatureRS);\n                break;\n            }\n        }\n\n        return result;\n    }\n}\n\nexport default ECDSASign;\n"
  },
  {
    "path": "src/core/operations/ECDSASignatureConversion.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { fromBase64, toBase64 } from \"../lib/Base64.mjs\";\nimport { fromHex, toHexFast } from \"../lib/Hex.mjs\";\nimport r from \"jsrsasign\";\n\n/**\n * ECDSA Sign operation\n */\nclass ECDSASignatureConversion extends Operation {\n\n    /**\n     * ECDSASignatureConversion constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ECDSA Signature Conversion\";\n        this.module = \"Ciphers\";\n        this.description = \"Convert an ECDSA signature between hex, asn1 and json.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input Format\",\n                type: \"option\",\n                value: [\n                    \"Auto\",\n                    \"ASN.1 HEX\",\n                    \"P1363 HEX\",\n                    \"JSON Web Signature\",\n                    \"Raw JSON\"\n                ]\n            },\n            {\n                name: \"Output Format\",\n                type: \"option\",\n                value: [\n                    \"ASN.1 HEX\",\n                    \"P1363 HEX\",\n                    \"JSON Web Signature\",\n                    \"Raw JSON\"\n                ]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let inputFormat = args[0];\n        const outputFormat = args[1];\n\n        // detect input format\n        let inputJson;\n        if (inputFormat === \"Auto\") {\n            try {\n                inputJson = JSON.parse(input);\n                if (typeof(inputJson) === \"object\") {\n                    inputFormat = \"Raw JSON\";\n                }\n            } catch {}\n        }\n\n        if (inputFormat === \"Auto\") {\n            const hexRegex = /^[a-f\\d]{2,}$/gi;\n            if (hexRegex.test(input)) {\n                if (input.substring(0, 2) === \"30\" && r.ASN1HEX.isASN1HEX(input)) {\n                    inputFormat = \"ASN.1 HEX\";\n                } else {\n                    inputFormat = \"P1363 HEX\";\n                }\n            }\n        }\n\n        let inputBase64;\n        if (inputFormat === \"Auto\") {\n            try {\n                inputBase64 = fromBase64(input, \"A-Za-z0-9-_\", false);\n                inputFormat = \"JSON Web Signature\";\n            } catch {}\n        }\n\n        // convert input to ASN.1 hex\n        let signatureASN1Hex;\n        switch (inputFormat) {\n            case \"Auto\":\n                throw new OperationError(\"Signature format could not be detected\");\n            case \"ASN.1 HEX\":\n                signatureASN1Hex = input;\n                break;\n            case \"P1363 HEX\":\n                signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);\n                break;\n            case \"JSON Web Signature\":\n                if (!inputBase64) inputBase64 = fromBase64(input, \"A-Za-z0-9-_\");\n                signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));\n                break;\n            case \"Raw JSON\": {\n                if (!inputJson) inputJson = JSON.parse(input);\n                if (!inputJson.r) {\n                    throw new OperationError('No \"r\" value in the signature JSON');\n                }\n                if (!inputJson.s) {\n                    throw new OperationError('No \"s\" value in the signature JSON');\n                }\n                signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);\n                break;\n            }\n        }\n\n        // convert ASN.1 hex to output format\n        let result;\n        switch (outputFormat) {\n            case \"ASN.1 HEX\":\n                result = signatureASN1Hex;\n                break;\n            case \"P1363 HEX\":\n                result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);\n                break;\n            case \"JSON Web Signature\":\n                result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);\n                result = toBase64(fromHex(result), \"A-Za-z0-9-_\");  // base64url\n                break;\n            case \"Raw JSON\": {\n                const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);\n                result = JSON.stringify(signatureRS);\n                break;\n            }\n        }\n\n        return result;\n    }\n}\n\nexport default ECDSASignatureConversion;\n"
  },
  {
    "path": "src/core/operations/ECDSAVerify.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { fromBase64 } from \"../lib/Base64.mjs\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\nimport r from \"jsrsasign\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * ECDSA Verify operation\n */\nclass ECDSAVerify extends Operation {\n\n    /**\n     * ECDSAVerify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ECDSA Verify\";\n        this.module = \"Ciphers\";\n        this.description = \"Verify a message against a signature and a public PEM encoded EC key.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input Format\",\n                type: \"option\",\n                value: [\n                    \"Auto\",\n                    \"ASN.1 HEX\",\n                    \"P1363 HEX\",\n                    \"JSON Web Signature\",\n                    \"Raw JSON\"\n                ]\n            },\n            {\n                name: \"Message Digest Algorithm\",\n                type: \"option\",\n                value: [\n                    \"SHA-256\",\n                    \"SHA-384\",\n                    \"SHA-512\",\n                    \"SHA-1\",\n                    \"MD5\"\n                ]\n            },\n            {\n                name: \"ECDSA Public Key (PEM)\",\n                type: \"text\",\n                value: \"-----BEGIN PUBLIC KEY-----\"\n            },\n            {\n                name: \"Message\",\n                type: \"text\",\n                value: \"\"\n            },\n            {\n                name: \"Message format\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\", \"Base64\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let inputFormat = args[0];\n        const [, mdAlgo, keyPem, msg, msgFormat] = args;\n\n        if (keyPem.replace(\"-----BEGIN PUBLIC KEY-----\", \"\").length === 0) {\n            throw new OperationError(\"Please enter a public key.\");\n        }\n\n        // detect input format\n        let inputJson;\n        if (inputFormat === \"Auto\") {\n            try {\n                inputJson = JSON.parse(input);\n                if (typeof(inputJson) === \"object\") {\n                    inputFormat = \"Raw JSON\";\n                }\n            } catch {}\n        }\n\n        if (inputFormat === \"Auto\") {\n            const hexRegex = /^[a-f\\d]{2,}$/gi;\n            if (hexRegex.test(input)) {\n                if (input.substring(0, 2) === \"30\" && r.ASN1HEX.isASN1HEX(input)) {\n                    inputFormat = \"ASN.1 HEX\";\n                } else {\n                    inputFormat = \"P1363 HEX\";\n                }\n            }\n        }\n\n        let inputBase64;\n        if (inputFormat === \"Auto\") {\n            try {\n                inputBase64 = fromBase64(input, \"A-Za-z0-9-_\", false);\n                inputFormat = \"JSON Web Signature\";\n            } catch {}\n        }\n\n        // convert to ASN.1 signature\n        let signatureASN1Hex;\n        switch (inputFormat) {\n            case \"Auto\":\n                throw new OperationError(\"Signature format could not be detected\");\n            case \"ASN.1 HEX\":\n                signatureASN1Hex = input;\n                break;\n            case \"P1363 HEX\":\n                signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);\n                break;\n            case \"JSON Web Signature\":\n                if (!inputBase64) inputBase64 = fromBase64(input, \"A-Za-z0-9-_\");\n                signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));\n                break;\n            case \"Raw JSON\": {\n                if (!inputJson) inputJson = JSON.parse(input);\n                if (!inputJson.r) {\n                    throw new OperationError('No \"r\" value in the signature JSON');\n                }\n                if (!inputJson.s) {\n                    throw new OperationError('No \"s\" value in the signature JSON');\n                }\n                signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);\n                break;\n            }\n        }\n\n        // verify signature\n        const internalAlgorithmName = mdAlgo.replace(\"-\", \"\") + \"withECDSA\";\n        const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });\n        const key = r.KEYUTIL.getKey(keyPem);\n        if (key.type !== \"EC\") {\n            throw new OperationError(\"Provided key is not an EC key.\");\n        }\n        if (!key.isPublic) {\n            throw new OperationError(\"Provided key is not a public key.\");\n        }\n        sig.init(key);\n        const messageStr = Utils.convertToByteString(msg, msgFormat);\n        sig.updateString(messageStr);\n        const result = sig.verify(signatureASN1Hex);\n        return result ? \"Verified OK\" : \"Verification Failure\";\n    }\n}\n\nexport default ECDSAVerify;\n"
  },
  {
    "path": "src/core/operations/ELFInfo.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * ELF Info operation\n */\nclass ELFInfo extends Operation {\n\n    /**\n     * ELFInfo constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ELF Info\";\n        this.module = \"Default\";\n        this.description = \"Implements readelf-like functionality. This operation will extract the ELF Header, Program Headers, Section Headers and Symbol Table for an ELF file.\";\n        this.infoURL = \"https://www.wikipedia.org/wiki/Executable_and_Linkable_Format\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let phoff = 0;\n        let phEntries = 0;\n        let shoff = 0;\n        let shEntries = 0;\n        let shentSize = 0;\n        let entry = 0;\n        let format = 0;\n        let endianness = \"\";\n        let shstrtab = 0;\n\n        let namesOffset = 0;\n\n        let symtabOffset = 0;\n        let symtabSize = 0;\n        let symtabEntSize = 0;\n\n        let strtabOffset = 0;\n        const align = 30;\n\n        /**\n         * This function reads characters until it hits a null terminator.\n         *\n         * @param {stream} stream\n         * @param {integer} namesOffset\n         * @param {integer} nameOffset\n         * @returns {string}\n         */\n        function readString(stream, namesOffset, nameOffset) {\n            const preMove = stream.position;\n            stream.moveTo(namesOffset + nameOffset);\n\n            const nameResult = stream.readString();\n            stream.moveTo(preMove);\n            return nameResult;\n        }\n\n        /**\n         * This function parses and extracts relevant information from the ELF Header.\n         *\n         * @param {stream} stream\n         * @returns {string}\n         */\n        function elfHeader(stream) {\n            /**\n             * The ELF Header is comprised of the following structures depending on the binary's format.\n             *\n             * e_ident     - The Magic Number 0x7F,0x45,0x4c,0x46\n             *             - Byte set to 1 or 2 to signify 32-bit or 64-bit format, respectively.\n             *             - Byte set to 1 or 2 to signify little of big endianness, respectively.\n             *             - Byte set to 1 for the version of ELF.\n             *             - Byte identifying the target OS ABI.\n             *             - Byte further identifying the OS ABI Version.\n             *             - 7 Padding Bytes.\n             * e_type      - 2 bytes identifying the object file type.\n             * e_machine   - 2 bytes identifying the instruction set architecture.\n             * e_version   - Byte set to 1 for the version of ELF.\n             *\n             * 32-bit:\n             *      e_entry     - 4 Bytes specifying the entry point.\n             *      e_phoff     - 4 Bytes specifying the offset of the Program Header Table.\n             *      e_shoff     - 4 Bytes specifying the offset of the Section Header Table.\n             *\n             * 64-bit:\n             *      e_entry     - 8 Bytes specifying the entry point.\n             *      e_phoff     - 8 Bytes specifying the offset of the Program Header Table.\n             *      e_shoff     - 8 Bytes specifying the offset of the Section Header Table.\n             *\n             * e_flags     - 4 Bytes specifying processor specific flags.\n             * e_ehsize    - 2 Bytes specifying the size of the ELF Header.\n             * e_phentsize - 2 Bytes specifying the size of a Program Header Table Entry.\n             * e_phnum     - 2 Bytes specifying the number of entries in the Program Header Table.\n             * e_shentsize - 2 Bytes specifying the size of a Section Header Table Entry.\n             * e_shnum     - 2 Bytes specifying the number of entries in the Section Header Table.\n             * e_shstrndx  - 2 Bytes specifying the index of the section containing the section names in the Section Header Table.\n             */\n            const ehResult = [];\n\n            const magic = stream.getBytes(4);\n            if (magic.join(\"\") !== [0x7f, 0x45, 0x4c, 0x46].join(\"\"))\n                throw new OperationError(\"Invalid ELF\");\n\n            ehResult.push(\"Magic:\".padEnd(align) + `${Utils.byteArrayToChars(magic)}`);\n\n            format = stream.readInt(1);\n            ehResult.push(\"Format:\".padEnd(align) + `${format === 1 ? \"32-bit\" : \"64-bit\"}`);\n\n            endianness = stream.readInt(1) === 1 ? \"le\" : \"be\";\n            ehResult.push(\"Endianness:\".padEnd(align) + `${endianness === \"le\" ? \"Little\" : \"Big\"}`);\n\n            ehResult.push(\"Version:\".padEnd(align) + `${stream.readInt(1).toString()}`);\n\n            let ABI = \"\";\n            switch (stream.readInt(1)) {\n                case 0x00:\n                    ABI = \"System V\";\n                    break;\n                case 0x01:\n                    ABI = \"HP-UX\";\n                    break;\n                case 0x02:\n                    ABI = \"NetBSD\";\n                    break;\n                case 0x03:\n                    ABI = \"Linux\";\n                    break;\n                case 0x04:\n                    ABI = \"GNU Hurd\";\n                    break;\n                case 0x06:\n                    ABI = \"Solaris\";\n                    break;\n                case 0x07:\n                    ABI = \"AIX\";\n                    break;\n                case 0x08:\n                    ABI = \"IRIX\";\n                    break;\n                case 0x09:\n                    ABI = \"FreeBSD\";\n                    break;\n                case 0x0A:\n                    ABI = \"Tru64\";\n                    break;\n                case 0x0B:\n                    ABI = \"Novell Modesto\";\n                    break;\n                case 0x0C:\n                    ABI = \"OpenBSD\";\n                    break;\n                case 0x0D:\n                    ABI = \"OpenVMS\";\n                    break;\n                case 0x0E:\n                    ABI = \"NonStop Kernel\";\n                    break;\n                case 0x0F:\n                    ABI = \"AROS\";\n                    break;\n                case 0x10:\n                    ABI = \"Fenix OS\";\n                    break;\n                case 0x11:\n                    ABI = \"CloudABI\";\n                    break;\n                case 0x12:\n                    ABI = \"Stratus Technologies OpenVOS\";\n                    break;\n                default:\n                    break;\n            }\n            ehResult.push(\"ABI:\".padEnd(align) + ABI);\n\n            // Linux Kernel does not use ABI Version.\n            const abiVersion = stream.readInt(1).toString();\n            if (ABI !== \"Linux\")\n                ehResult.push(\"ABI Version:\".padEnd(align) + abiVersion);\n\n            stream.moveForwardsBy(7);\n\n            let eType = \"\";\n            switch (stream.readInt(2, endianness)) {\n                case 0x0000:\n                    eType = \"Unknown\";\n                    break;\n                case 0x0001:\n                    eType = \"Relocatable File\";\n                    break;\n                case 0x0002:\n                    eType = \"Executable File\";\n                    break;\n                case 0x0003:\n                    eType = \"Shared Object\";\n                    break;\n                case 0x0004:\n                    eType = \"Core File\";\n                    break;\n                case 0xFE00:\n                    eType = \"LOOS\";\n                    break;\n                case 0xFEFF:\n                    eType = \"HIOS\";\n                    break;\n                case 0xFF00:\n                    eType = \"LOPROC\";\n                    break;\n                case 0xFFFF:\n                    eType = \"HIPROC\";\n                    break;\n                default:\n                    break;\n            }\n            ehResult.push(\"Type:\".padEnd(align) + eType);\n\n            let ISA = \"\";\n            switch (stream.readInt(2, endianness)) {\n                case 0x0000:\n                    ISA = \"No specific instruction set\";\n                    break;\n                case 0x0001:\n                    ISA = \"AT&T WE 32100\";\n                    break;\n                case 0x0002:\n                    ISA = \"SPARC\";\n                    break;\n                case 0x0003:\n                    ISA = \"x86\";\n                    break;\n                case 0x0004:\n                    ISA = \"Motorola 68000 (M68k)\";\n                    break;\n                case 0x0005:\n                    ISA = \"Motorola 88000 (M88k)\";\n                    break;\n                case 0x0006:\n                    ISA = \"Intel MCU\";\n                    break;\n                case 0x0007:\n                    ISA = \"Intel 80860\";\n                    break;\n                case 0x0008:\n                    ISA = \"MIPS\";\n                    break;\n                case 0x0009:\n                    ISA = \"IBM System/370\";\n                    break;\n                case 0x000A:\n                    ISA = \"MIPS RS3000 Little-endian\";\n                    break;\n                case 0x000B:\n                case 0x000C:\n                case 0x000D:\n                case 0x000E:\n                case 0x0018:\n                case 0x0019:\n                case 0x001A:\n                case 0x001B:\n                case 0x001C:\n                case 0x001D:\n                case 0x001E:\n                case 0x001F:\n                case 0x0020:\n                case 0x0021:\n                case 0x0022:\n                case 0x0023:\n                    ISA = \"Reserved for future use\";\n                    break;\n                case 0x000F:\n                    ISA = \"Hewlett-Packard PA-RISC\";\n                    break;\n                case 0x0011:\n                    ISA = \"Fujitsu VPP500\";\n                    break;\n                case 0x0012:\n                    ISA = \"Enhanced instruction set SPARC\";\n                    break;\n                case 0x0013:\n                    ISA = \"Intel 80960\";\n                    break;\n                case 0x0014:\n                    ISA = \"PowerPC\";\n                    break;\n                case 0x0015:\n                    ISA = \"PowerPC (64-bit)\";\n                    break;\n                case 0x0016:\n                    ISA = \"S390, including S390\";\n                    break;\n                case 0x0017:\n                    ISA = \"IBM SPU/SPC\";\n                    break;\n                case 0x0024:\n                    ISA = \"NEC V800\";\n                    break;\n                case 0x0025:\n                    ISA = \"Fujitsu FR20\";\n                    break;\n                case 0x0026:\n                    ISA = \"TRW RH-32\";\n                    break;\n                case 0x0027:\n                    ISA = \"Motorola RCE\";\n                    break;\n                case 0x0028:\n                    ISA = \"ARM (up to ARMv7/Aarch32)\";\n                    break;\n                case 0x0029:\n                    ISA = \"Digital Alpha\";\n                    break;\n                case 0x002A:\n                    ISA = \"SuperH\";\n                    break;\n                case 0x002B:\n                    ISA = \"SPARC Version 9\";\n                    break;\n                case 0x002C:\n                    ISA = \"Siemens TriCore embedded processor\";\n                    break;\n                case 0x002D:\n                    ISA = \"Argonaut RISC Core\";\n                    break;\n                case 0x002E:\n                    ISA = \"Hitachi H8/300\";\n                    break;\n                case 0x002F:\n                    ISA = \"Hitachi H8/300H\";\n                    break;\n                case 0x0030:\n                    ISA = \"Hitachi H8S\";\n                    break;\n                case 0x0031:\n                    ISA = \"Hitachi H8/500\";\n                    break;\n                case 0x0032:\n                    ISA = \"IA-64\";\n                    break;\n                case 0x0033:\n                    ISA = \"Standford MIPS-X\";\n                    break;\n                case 0x0034:\n                    ISA = \"Motorola ColdFire\";\n                    break;\n                case 0x0035:\n                    ISA = \"Motorola M68HC12\";\n                    break;\n                case 0x0036:\n                    ISA = \"Fujitsu MMA Multimedia Accelerator\";\n                    break;\n                case 0x0037:\n                    ISA = \"Siemens PCP\";\n                    break;\n                case 0x0038:\n                    ISA = \"Sony nCPU embedded RISC processor\";\n                    break;\n                case 0x0039:\n                    ISA = \"Denso NDR1 microprocessor\";\n                    break;\n                case 0x003A:\n                    ISA = \"Motorola Star*Core processor\";\n                    break;\n                case 0x003B:\n                    ISA = \"Toyota ME16 processor\";\n                    break;\n                case 0x003C:\n                    ISA = \"STMicroelectronics ST100 processor\";\n                    break;\n                case 0x003D:\n                    ISA = \"Advanced Logic Corp. TinyJ embedded processor family\";\n                    break;\n                case 0x003E:\n                    ISA = \"AMD x86-64\";\n                    break;\n                case 0x003F:\n                    ISA = \"Sony DSP Processor\";\n                    break;\n                case 0x0040:\n                    ISA = \"Digital Equipment Corp. PDP-10\";\n                    break;\n                case 0x0041:\n                    ISA = \"Digital Equipment Corp. PDP-11\";\n                    break;\n                case 0x0042:\n                    ISA = \"Siemens FX66 microcontroller\";\n                    break;\n                case 0x0043:\n                    ISA = \"STMicroelectronics ST9+ 8/16 bit microcontroller\";\n                    break;\n                case 0x0044:\n                    ISA = \"STMicroelectronics ST7 8-bit microcontroller\";\n                    break;\n                case 0x0045:\n                    ISA = \"Motorola MC68HC16 Microcontroller\";\n                    break;\n                case 0x0046:\n                    ISA = \"Motorola MC68HC11 Microcontroller\";\n                    break;\n                case 0x0047:\n                    ISA = \"Motorola MC68HC08 Microcontroller\";\n                    break;\n                case 0x0048:\n                    ISA = \"Motorola MC68HC05 Microcontroller\";\n                    break;\n                case 0x0049:\n                    ISA = \"Silicon Graphics SVx\";\n                    break;\n                case 0x004A:\n                    ISA = \"STMicroelectronics ST19 8-bit microcontroller\";\n                    break;\n                case 0x004B:\n                    ISA = \"Digital VAX\";\n                    break;\n                case 0x004C:\n                    ISA = \"Axis Communications 32-bit embedded processor\";\n                    break;\n                case 0x004D:\n                    ISA = \"Infineon Technologies 32-bit embedded processor\";\n                    break;\n                case 0x004E:\n                    ISA = \"Element 14 64-bit DSP Processor\";\n                    break;\n                case 0x004F:\n                    ISA = \"LSI Logic 16-bit DSP Processor\";\n                    break;\n                case 0x0050:\n                    ISA = \"Donald Knuth's educational 64-bit processor\";\n                    break;\n                case 0x0051:\n                    ISA = \"Harvard University machine-independent object files\";\n                    break;\n                case 0x0052:\n                    ISA = \"SiTera Prism\";\n                    break;\n                case 0x0053:\n                    ISA = \"Atmel AVR 8-bit microcontroller\";\n                    break;\n                case 0x0054:\n                    ISA = \"Fujitsu FR30\";\n                    break;\n                case 0x0055:\n                    ISA = \"Mitsubishi D10V\";\n                    break;\n                case 0x0056:\n                    ISA = \"Mitsubishi D30V\";\n                    break;\n                case 0x0057:\n                    ISA = \"NEC v850\";\n                    break;\n                case 0x0058:\n                    ISA = \"Mitsubishi M32R\";\n                    break;\n                case 0x0059:\n                    ISA = \"Matsushita MN10300\";\n                    break;\n                case 0x005A:\n                    ISA = \"Matsushita MN10200\";\n                    break;\n                case 0x005B:\n                    ISA = \"picoJava\";\n                    break;\n                case 0x005C:\n                    ISA = \"OpenRISC 32-bit embedded processor\";\n                    break;\n                case 0x005D:\n                    ISA = \"ARC Cores Tangent-A5\";\n                    break;\n                case 0x005E:\n                    ISA = \"Tensilica Xtensa Architecture\";\n                    break;\n                case 0x005F:\n                    ISA = \"Alphamosaic VideoCore processor\";\n                    break;\n                case 0x0060:\n                    ISA = \"Thompson Multimedia General Purpose Processor\";\n                    break;\n                case 0x0061:\n                    ISA = \"National Semiconductor 32000 series\";\n                    break;\n                case 0x0062:\n                    ISA = \"Tenor Network TPC processor\";\n                    break;\n                case 0x0063:\n                    ISA = \"Trebia SNP 1000 processor\";\n                    break;\n                case 0x0064:\n                    ISA = \"STMicroelectronics (www.st.com) ST200 microcontroller\";\n                    break;\n                case 0x008C:\n                    ISA = \"TMS320C6000 Family\";\n                    break;\n                case 0x00AF:\n                    ISA = \"MCST Elbrus e2k\";\n                    break;\n                case 0x00B7:\n                    ISA = \"ARM 64-bits (ARMv8/Aarch64)\";\n                    break;\n                case 0x00F3:\n                    ISA = \"RISC-V\";\n                    break;\n                case 0x00F7:\n                    ISA = \"Berkeley Packet Filter\";\n                    break;\n                case 0x0101:\n                    ISA = \"WDC 65C816\";\n                    break;\n                default:\n                    ISA = \"Unimplemented\";\n                    break;\n            }\n            ehResult.push(\"Instruction Set Architecture:\".padEnd(align) + ISA);\n\n            ehResult.push(\"ELF Version:\".padEnd(align) + `${stream.readInt(4, endianness)}`);\n\n            const readSize = format === 1 ? 4 : 8;\n            entry = stream.readInt(readSize, endianness);\n            phoff = stream.readInt(readSize, endianness);\n            shoff = stream.readInt(readSize, endianness);\n            ehResult.push(\"Entry Point:\".padEnd(align) + `0x${Utils.hex(entry)}`);\n            ehResult.push(\"Entry PHOFF:\".padEnd(align) + `0x${Utils.hex(phoff)}`);\n            ehResult.push(\"Entry SHOFF:\".padEnd(align) + `0x${Utils.hex(shoff)}`);\n\n            const flags = stream.readInt(4, endianness);\n            ehResult.push(\"Flags:\".padEnd(align) + `${Utils.bin(flags)}`);\n\n            ehResult.push(\"ELF Header Size:\".padEnd(align) + `${stream.readInt(2, endianness)} bytes`);\n            ehResult.push(\"Program Header Size:\".padEnd(align) + `${stream.readInt(2, endianness)} bytes`);\n            phEntries = stream.readInt(2, endianness);\n            ehResult.push(\"Program Header Entries:\".padEnd(align) + phEntries);\n            shentSize = stream.readInt(2, endianness);\n            ehResult.push(\"Section Header Size:\".padEnd(align) + shentSize + \" bytes\");\n            shEntries = stream.readInt(2, endianness);\n            ehResult.push(\"Section Header Entries:\".padEnd(align) + shEntries);\n            shstrtab = stream.readInt(2, endianness);\n            ehResult.push(\"Section Header Names:\".padEnd(align) + shstrtab);\n\n            return ehResult.join(\"\\n\");\n        }\n\n        /**\n         * This function parses and extracts relevant information from a Program Header.\n         *\n         * @param {stream} stream\n         * @returns {string}\n         */\n        function programHeader(stream) {\n            /**\n             * A Program Header is comprised of the following structures depending on the binary's format.\n             *\n             * p_type       - 4 Bytes identifying the type of the segment.\n             *\n             * 32-bit:\n             *      p_offset    - 4 Bytes specifying the offset of the segment.\n             *      p_vaddr     - 4 Bytes specifying the virtual address of the segment in memory.\n             *      p_paddr     - 4 Bytes specifying the physical address of the segment in memory.\n             *      p_filesz    - 4 Bytes specifying the size in bytes of the segment in the file image.\n             *      p_memsz     - 4 Bytes specifying the size in bytes of the segment in memory.\n             *      p_flags     - 4 Bytes identifying the segment dependent flags.\n             *      p_align     - 4 Bytes set to 0 or 1 for alignment or no alignment, respectively.\n             *\n             * 64-bit:\n             *      p_flags     - 4 Bytes identifying segment dependent flags.\n             *      p_offset    - 8 Bytes specifying the offset of the segment.\n             *      p_vaddr     - 8 Bytes specifying the virtual address of the segment in memory.\n             *      p_paddr     - 8 Bytes specifying the physical address of the segment in memory.\n             *      p_filesz    - 8 Bytes specifying the size in bytes of the segment in the file image.\n             *      p_memsz     - 8 Bytes specifying the size in bytes of the segment in memory.\n             *      p_align     - 8 Bytes set to 0 or 1 for alignment or no alignment, respectively.\n             */\n\n            /**\n             * This function decodes the flags bitmask for the Program Header.\n             *\n             * @param {integer} flags\n             * @returns {string}\n             */\n            function readFlags(flags) {\n                const result = [];\n                if (flags & 0x1)\n                    result.push(\"Execute\");\n                if (flags & 0x2)\n                    result.push(\"Write\");\n                if (flags & 0x4)\n                    result.push(\"Read\");\n                if (flags &  0xf0000000)\n                    result.push(\"Unspecified\");\n                return result.join(\",\");\n            }\n\n            const phResult = [];\n\n            let pType = \"\";\n            const programHeaderType = stream.readInt(4, endianness);\n            switch (true) {\n                case (programHeaderType === 0x00000000):\n                    pType = \"Unused\";\n                    break;\n                case (programHeaderType === 0x00000001):\n                    pType = \"Loadable Segment\";\n                    break;\n                case (programHeaderType === 0x00000002):\n                    pType = \"Dynamic linking information\";\n                    break;\n                case (programHeaderType === 0x00000003):\n                    pType = \"Interpreter Information\";\n                    break;\n                case (programHeaderType === 0x00000004):\n                    pType = \"Auxiliary Information\";\n                    break;\n                case (programHeaderType === 0x00000005):\n                    pType = \"Reserved\";\n                    break;\n                case (programHeaderType === 0x00000006):\n                    pType = \"Program Header Table\";\n                    break;\n                case (programHeaderType === 0x00000007):\n                    pType = \"Thread-Local Storage Template\";\n                    break;\n                case (programHeaderType >= 0x60000000 && programHeaderType <= 0x6FFFFFFF):\n                    pType = \"Reserved Inclusive Range. OS Specific\";\n                    break;\n                case (programHeaderType >= 0x70000000 && programHeaderType <= 0x7FFFFFFF):\n                    pType = \"Reserved Inclusive Range. Processor Specific\";\n                    break;\n                default:\n                    break;\n\n            }\n            phResult.push(\"Program Header Type:\".padEnd(align) + pType);\n\n            if (format === 2)\n                phResult.push(\"Flags:\".padEnd(align) + readFlags(stream.readInt(4, endianness)));\n\n            const readSize = format === 1? 4 : 8;\n            phResult.push(\"Offset Of Segment:\".padEnd(align) + `${stream.readInt(readSize, endianness)}`);\n            phResult.push(\"Virtual Address of Segment:\".padEnd(align) + `${stream.readInt(readSize, endianness)}`);\n            phResult.push(\"Physical Address of Segment:\".padEnd(align) + `${stream.readInt(readSize, endianness)}`);\n            phResult.push(\"Size of Segment:\".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`);\n            phResult.push(\"Size of Segment in Memory:\".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`);\n\n            if (format === 1)\n                phResult.push(\"Flags:\".padEnd(align) + readFlags(stream.readInt(4, endianness)));\n\n            stream.moveForwardsBy(readSize);\n\n            return phResult.join(\"\\n\");\n        }\n\n        /**\n         * This function parses and extracts relevant information from a Section Header.\n         *\n         * @param {stream} stream\n         * @returns {string}\n         */\n        function sectionHeader(stream) {\n            /**\n             * A Section Header is comprised of the following structures depending on the binary's format.\n             *\n             * sh_name      - 4 Bytes identifying the offset into the .shstrtab for the name of this section.\n             * sh_type      - 4 Bytes identifying the type of this header.\n             *\n             * 32-bit:\n             *      sh_flags        - 4 Bytes identifying section specific flags.\n             *      sh_addr         - 4 Bytes identifying the virtual address of the section in memory.\n             *      sh_offset       - 4 Bytes identifying the offset of the section in the file.\n             *      sh_size         - 4 Bytes specifying the size in bytes of the section in the file image.\n             *      sh_link         - 4 Bytes identifying the index of an associated section.\n             *      sh_info         - 4 Bytes specifying extra information about the section.\n             *      sh_addralign    - 4 Bytes containing the alignment for the section.\n             *      sh_entsize      - 4 Bytes specifying the size, in bytes, of each entry in the section.\n             *\n             * 64-bit:\n             *      sh_flags        - 8 Bytes identifying section specific flags.\n             *      sh_addr         - 8 Bytes identifying the virtual address of the section in memory.\n             *      sh_offset       - 8 Bytes identifying the offset of the section in the file.\n             *      sh_size         - 8 Bytes specifying the size in bytes of the section in the file image.\n             *      sh_link         - 4 Bytes identifying the index of an associated section.\n             *      sh_info         - 4 Bytes specifying extra information about the section.\n             *      sh_addralign    - 8 Bytes containing the alignment for the section.\n             *      sh_entsize      - 8 Bytes specifying the size, in bytes, of each entry in the section.\n             */\n            const shResult = [];\n\n            const nameOffset = stream.readInt(4, endianness);\n            let type = \"\";\n            const shType = stream.readInt(4, endianness);\n            switch (true) {\n                case (shType === 0x00000001):\n                    type = \"Program Data\";\n                    break;\n                case (shType === 0x00000002):\n                    type = \"Symbol Table\";\n                    break;\n                case (shType === 0x00000003):\n                    type = \"String Table\";\n                    break;\n                case (shType === 0x00000004):\n                    type = \"Relocation Entries with Addens\";\n                    break;\n                case (shType === 0x00000005):\n                    type = \"Symbol Hash Table\";\n                    break;\n                case (shType === 0x00000006):\n                    type = \"Dynamic Linking Information\";\n                    break;\n                case (shType === 0x00000007):\n                    type = \"Notes\";\n                    break;\n                case (shType === 0x00000008):\n                    type = \"Program Space with No Data\";\n                    break;\n                case (shType === 0x00000009):\n                    type = \"Relocation Entries with no Addens\";\n                    break;\n                case (shType === 0x0000000A):\n                    type = \"Reserved\";\n                    break;\n                case (shType === 0x0000000B):\n                    type = \"Dynamic Linker Symbol Table\";\n                    break;\n                case (shType === 0x0000000E):\n                    type = \"Array of Constructors\";\n                    break;\n                case (shType === 0x0000000F):\n                    type = \"Array of Destructors\";\n                    break;\n                case (shType === 0x00000010):\n                    type = \"Array of pre-constructors\";\n                    break;\n                case (shType === 0x00000011):\n                    type = \"Section group\";\n                    break;\n                case (shType === 0x00000012):\n                    type = \"Extended section indices\";\n                    break;\n                case (shType === 0x00000013):\n                    type = \"Number of defined types\";\n                    break;\n                case (shType >= 0x60000000 && shType <= 0x6fffffff):\n                    type = \"OS-specific\";\n                    break;\n                case (shType >= 0x70000000 && shType <= 0x7fffffff):\n                    type = \"Processor-specific\";\n                    break;\n                case (shType >= 0x80000000 && shType <= 0x8fffffff):\n                    type = \"Application-specific\";\n                    break;\n                default:\n                    type = \"Unused\";\n                    break;\n            }\n\n            shResult.push(\"Type:\".padEnd(align) + type);\n\n            let nameResult = \"\";\n            if (type !== \"Unused\") {\n                nameResult = readString(stream, namesOffset, nameOffset);\n                shResult.push(\"Section Name: \".padEnd(align) + nameResult);\n            }\n\n            const readSize = (format === 1) ? 4 : 8;\n\n            const flags = stream.readInt(readSize, endianness);\n            const shFlags = [];\n            const bitMasks = [\n                [0x00000001, \"Writable\"],\n                [0x00000002, \"Alloc\"],\n                [0x00000004, \"Executable\"],\n                [0x00000010, \"Merge\"],\n                [0x00000020, \"Strings\"],\n                [0x00000040, \"SHT Info Link\"],\n                [0x00000080, \"Link Order\"],\n                [0x00000100, \"OS Specific Handling\"],\n                [0x00000200, \"Group\"],\n                [0x00000400, \"Thread Local Data\"],\n                [0x0FF00000, \"OS-Specific\"],\n                [0xF0000000, \"Processor Specific\"],\n                [0x04000000, \"Special Ordering (Solaris)\"],\n                [0x08000000, \"Excluded (Solaris)\"]\n            ];\n            bitMasks.forEach(elem => {\n                if (flags & elem[0])\n                    shFlags.push(elem[1]);\n            });\n            shResult.push(\"Flags:\".padEnd(align) + shFlags);\n\n            const vaddr = stream.readInt(readSize, endianness);\n            shResult.push(\"Section Vaddr in memory:\".padEnd(align) + vaddr);\n\n            const shoffset = stream.readInt(readSize, endianness);\n            shResult.push(\"Offset of the section:\".padEnd(align) + shoffset);\n\n            const secSize = stream.readInt(readSize, endianness);\n            shResult.push(\"Section Size:\".padEnd(align) + secSize);\n\n            const associatedSection = stream.readInt(4, endianness);\n            shResult.push(\"Associated Section:\".padEnd(align) + associatedSection);\n\n            const extraInfo = stream.readInt(4, endianness);\n            shResult.push(\"Section Extra Information:\".padEnd(align) + extraInfo);\n\n            // Jump over alignment field.\n            stream.moveForwardsBy(readSize);\n            const entSize = stream.readInt(readSize, endianness);\n            switch (nameResult) {\n                case \".strtab\":\n                    strtabOffset = shoffset;\n                    break;\n                case \".symtab\":\n                    symtabOffset = shoffset;\n                    symtabSize = secSize;\n                    symtabEntSize = entSize;\n                    break;\n                default:\n                    break;\n            }\n            return shResult.join(\"\\n\");\n        }\n\n        /**\n         * This function returns the offset of the Section Header Names Section.\n         *\n         * @param {stream} stream\n         */\n        function getNamesOffset(stream) {\n            const preMove = stream.position;\n            stream.moveTo(shoff + (shentSize * shstrtab));\n            if (format === 1) {\n                stream.moveForwardsBy(0x10);\n                namesOffset = stream.readInt(4, endianness);\n            } else {\n                stream.moveForwardsBy(0x18);\n                namesOffset = stream.readInt(8, endianness);\n            }\n            stream.position = preMove;\n        }\n\n        /**\n         * This function returns a symbol's name from the string table.\n         *\n         * @param {stream} stream\n         * @returns {string}\n         */\n        function getSymbols(stream) {\n            /**\n             * The Symbol Table is comprised of Symbol Table Entries whose structure depends on the binary's format.\n             *\n             * 32-bit:\n             *      st_name         - 4 Bytes specifying an index in the files symbol string table.\n             *      st_value        - 4 Bytes identifying the value associated with the symbol.\n             *      st_size         - 4 Bytes specifying the size associated with the symbol (this is not the size of the symbol).\n             *      st_info         - A byte specifying the type and binding of the symbol.\n             *      st_other        - A byte specifying the symbol's visibility.\n             *      st_shndx        - 2 Bytes identifying the section that this symbol is related to.\n             *\n             * 64-bit:\n             *      st_name         - 4 Bytes specifying an index in the files symbol string table.\n             *      st_info         - A byte specifying the type and binding of the symbol.\n             *      st_other        - A byte specifying the symbol's visibility.\n             *      st_shndx        - 2 Bytes identifying the section that this symbol is related to.\n             *      st_value        - 8 Bytes identifying the value associated with the symbol.\n             *      st_size         - 8 Bytes specifying the size associated with the symbol (this is not the size of the symbol).\n             */\n            const nameOffset = stream.readInt(4, endianness);\n            stream.moveForwardsBy(format === 2 ? 20 : 12);\n            return readString(stream, strtabOffset, nameOffset);\n        }\n\n        input = new Uint8Array(input);\n        const stream = new Stream(input);\n        const result = [\"=\".repeat(align) + \" ELF Header \" + \"=\".repeat(align)];\n        result.push(elfHeader(stream) + \"\\n\");\n\n        getNamesOffset(stream);\n\n        result.push(\"=\".repeat(align) + \" Program Header \" + \"=\".repeat(align));\n        stream.moveTo(phoff);\n        for (let i = 0; i < phEntries; i++)\n            result.push(programHeader(stream) + \"\\n\");\n\n        result.push(\"=\".repeat(align) + \" Section Header \" + \"=\".repeat(align));\n        stream.moveTo(shoff);\n        for (let i = 0; i < shEntries; i++)\n            result.push(sectionHeader(stream) + \"\\n\");\n\n        result.push(\"=\".repeat(align) + \" Symbol Table \" + \"=\".repeat(align));\n\n        stream.moveTo(symtabOffset);\n        let elem = \"\";\n        for (let i = 0; i < (symtabSize / symtabEntSize); i++)\n            if ((elem = getSymbols(stream)) !== \"\")\n                result.push(\"Symbol Name:\".padEnd(align) + elem);\n\n        return result.join(\"\\n\");\n    }\n\n}\n\nexport default ELFInfo;\n"
  },
  {
    "path": "src/core/operations/EncodeNetBIOSName.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Encode NetBIOS Name operation\n */\nclass EncodeNetBIOSName extends Operation {\n\n    /**\n     * EncodeNetBIOSName constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Encode NetBIOS Name\";\n        this.module = \"Default\";\n        this.description = \"NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.<br><br>There are two levels of encoding. The first level maps a NetBIOS name into a domain system name.  The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.<br><br>This operation carries out the first level of encoding. See RFC 1001 for full details.\";\n        this.infoURL = \"https://wikipedia.org/wiki/NetBIOS\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Offset\",\n                \"type\": \"number\",\n                \"value\": 65\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const output = [],\n            offset = args[0];\n\n        if (input.length <= 16) {\n            const len = input.length;\n            input.length = 16;\n            input.fill(32, len, 16);\n            for (let i = 0; i < input.length; i++) {\n                output.push((input[i] >> 4) + offset);\n                output.push((input[i] & 0xf) + offset);\n            }\n        }\n\n        return output;\n\n    }\n\n}\n\nexport default EncodeNetBIOSName;\n"
  },
  {
    "path": "src/core/operations/EncodeText.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport cptable from \"codepage\";\nimport {CHR_ENC_CODE_PAGES} from \"../lib/ChrEnc.mjs\";\n\n/**\n * Encode text operation\n */\nclass EncodeText extends Operation {\n\n    /**\n     * EncodeText constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Encode text\";\n        this.module = \"Encodings\";\n        this.description = [\n            \"Encodes text into the chosen character encoding.\",\n            \"<br><br>\",\n            \"Supported charsets are:\",\n            \"<ul>\",\n            Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join(\"\\n\"),\n            \"</ul>\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Character_encoding\";\n        this.inputType = \"string\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Encoding\",\n                \"type\": \"option\",\n                \"value\": Object.keys(CHR_ENC_CODE_PAGES)\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const format = CHR_ENC_CODE_PAGES[args[0]];\n        const encoded = cptable.utils.encode(format, input);\n        return new Uint8Array(encoded).buffer;\n    }\n\n}\n\n\nexport default EncodeText;\n"
  },
  {
    "path": "src/core/operations/Enigma.mjs",
    "content": "/**\n * Emulation of the Enigma machine.\n *\n * Tested against various genuine Enigma machines using a variety of inputs\n * and settings to confirm correctness.\n *\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {ROTORS, LETTERS, ROTORS_FOURTH, REFLECTORS, Rotor, Reflector, Plugboard, EnigmaMachine} from \"../lib/Enigma.mjs\";\n\n/**\n * Enigma operation\n */\nclass Enigma extends Operation {\n    /**\n     * Enigma constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Enigma\";\n        this.module = \"Bletchley\";\n        this.description = \"Encipher/decipher with the WW2 Enigma machine.<br><br>Enigma was used by the German military, among others, around the WW2 era as a portable cipher machine to protect sensitive military, diplomatic and commercial communications.<br><br>The standard set of German military rotors and reflectors are provided. To configure the plugboard, enter a string of connected pairs of letters, e.g. <code>AB CD EF</code> connects A to B, C to D, and E to F. This is also used to create your own reflectors. To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by <code>&lt;</code> then a list of stepping points.<br>This is deliberately fairly permissive with rotor placements etc compared to a real Enigma (on which, for example, a four-rotor Enigma uses only the thin reflectors and the beta or gamma rotor in the 4th slot).<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Enigma_machine\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Model\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"3-rotor\",\n                        off: [1, 2, 3]\n                    },\n                    {\n                        name: \"4-rotor\",\n                        on: [1, 2, 3]\n                    }\n                ]\n            },\n            {\n                name: \"Left-most (4th) rotor\",\n                type: \"editableOption\",\n                value: ROTORS_FOURTH,\n                defaultIndex: 0\n            },\n            {\n                name: \"Left-most rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Left-most rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Left-hand rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"Left-hand rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Left-hand rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Middle rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 1\n            },\n            {\n                name: \"Middle rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Middle rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Right-hand rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                // Default config is the rotors I-III *left to right*\n                defaultIndex: 2\n            },\n            {\n                name: \"Right-hand rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Right-hand rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Reflector\",\n                type: \"editableOption\",\n                value: REFLECTORS\n            },\n            {\n                name: \"Plugboard\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Strict output\",\n                hint: \"Remove non-alphabet letters and group output\",\n                type: \"boolean\",\n                value: true\n            },\n        ];\n    }\n\n    /**\n     * Helper - for ease of use rotors are specified as a single string; this\n     * method breaks the spec string into wiring and steps parts.\n     *\n     * @param {string} rotor - Rotor specification string.\n     * @param {number} i - For error messages, the number of this rotor.\n     * @returns {string[]}\n     */\n    parseRotorStr(rotor, i) {\n        if (rotor === \"\") {\n            throw new OperationError(`Rotor ${i} must be provided.`);\n        }\n        if (!rotor.includes(\"<\")) {\n            return [rotor, \"\"];\n        }\n        return rotor.split(\"<\", 2);\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const model = args[0];\n        const reflectorstr = args[13];\n        const plugboardstr = args[14];\n        const removeOther = args[15];\n        const rotors = [];\n        for (let i=0; i<4; i++) {\n            if (i === 0 && model === \"3-rotor\") {\n                // Skip the 4th rotor settings\n                continue;\n            }\n            const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*3 + 1], 1);\n            rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*3 + 2], args[i*3 + 3]));\n        }\n        // Rotors are handled in reverse\n        rotors.reverse();\n        const reflector = new Reflector(reflectorstr);\n        const plugboard = new Plugboard(plugboardstr);\n        if (removeOther) {\n            input = input.replace(/[^A-Za-z]/g, \"\");\n        }\n        const enigma = new EnigmaMachine(rotors, reflector, plugboard);\n        let result = enigma.crypt(input);\n        if (removeOther) {\n            // Five character cipher groups is traditional\n            result = result.replace(/([A-Z]{5})(?!$)/g, \"$1 \");\n        }\n        return result;\n    }\n\n    /**\n     * Highlight Enigma\n     * This is only possible if we're passing through non-alphabet characters.\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        if (args[13] === false) {\n            return pos;\n        }\n    }\n\n    /**\n     * Highlight Enigma in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        if (args[13] === false) {\n            return pos;\n        }\n    }\n\n}\n\nexport default Enigma;\n"
  },
  {
    "path": "src/core/operations/Entropy.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport * as d3temp from \"d3\";\nimport * as nodomtemp from \"nodom\";\n\nimport Operation from \"../Operation.mjs\";\n\nconst d3 = d3temp.default ? d3temp.default : d3temp;\nconst nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;\n\n/**\n * Entropy operation\n */\nclass Entropy extends Operation {\n\n    /**\n     * Entropy constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Entropy\";\n        this.module = \"Charts\";\n        this.description = \"Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Entropy_(information_theory)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"json\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Visualisation\",\n                \"type\": \"option\",\n                \"value\": [\"Shannon scale\", \"Histogram (Bar)\", \"Histogram (Line)\", \"Curve\", \"Image\"]\n            }\n        ];\n    }\n\n    /**\n     * Calculates the frequency of bytes in the input.\n     *\n     * @param {Uint8Array} input\n     * @returns {number}\n     */\n    calculateShannonEntropy(input) {\n        const prob = [],\n            occurrences = new Array(256).fill(0);\n\n        // Count occurrences of each byte in the input\n        let i;\n        for (i = 0; i < input.length; i++) {\n            occurrences[input[i]]++;\n        }\n\n        // Store probability list\n        for (i = 0; i < occurrences.length; i++) {\n            if (occurrences[i] > 0) {\n                prob.push(occurrences[i] / input.length);\n            }\n        }\n\n        // Calculate Shannon entropy\n        let entropy = 0,\n            p;\n\n        for (i = 0; i < prob.length; i++) {\n            p = prob[i];\n            entropy += p * Math.log(p) / Math.log(2);\n        }\n\n        return -entropy;\n    }\n\n    /**\n     * Calculates the scanning entropy of the input\n     *\n     * @param {Uint8Array} inputBytes\n     * @returns {Object}\n     */\n    calculateScanningEntropy(inputBytes) {\n        const entropyData = [];\n        const binWidth = inputBytes.length < 256 ? 8 : 256;\n\n        for (let bytePos = 0; bytePos < inputBytes.length; bytePos += binWidth) {\n            const block = inputBytes.slice(bytePos, bytePos+binWidth);\n            entropyData.push(this.calculateShannonEntropy(block));\n        }\n\n        return { entropyData, binWidth };\n    }\n\n    /**\n     * Calculates the frequency of bytes in the input.\n     *\n     * @param {object} svg\n     * @param {function} xScale\n     * @param {function} yScale\n     * @param {integer} svgHeight\n     * @param {integer} svgWidth\n     * @param {object} margins\n     * @param {string} xTitle\n     * @param {string} yTitle\n     */\n    createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, title, xTitle, yTitle) {\n        // Axes\n        const yAxis = d3.axisLeft()\n            .scale(yScale);\n\n        const xAxis = d3.axisBottom()\n            .scale(xScale);\n\n        svg.append(\"g\")\n            .attr(\"transform\", `translate(0, ${svgHeight - margins.bottom})`)\n            .call(xAxis);\n\n        svg.append(\"g\")\n            .attr(\"transform\", `translate(${margins.left},0)`)\n            .call(yAxis);\n\n        // Axes labels\n        svg.append(\"text\")\n            .attr(\"transform\", \"rotate(-90)\")\n            .attr(\"y\", 0 - margins.left)\n            .attr(\"x\", 0 - (svgHeight / 2))\n            .attr(\"dy\", \"1em\")\n            .style(\"text-anchor\", \"middle\")\n            .text(yTitle);\n\n        svg.append(\"text\")\n            .attr(\"transform\", `translate(${svgWidth / 2}, ${svgHeight - margins.bottom + 40})`)\n            .style(\"text-anchor\", \"middle\")\n            .text(xTitle);\n\n        // Add title\n        svg.append(\"text\")\n            .attr(\"transform\", `translate(${svgWidth / 2}, ${margins.top - 10})`)\n            .style(\"text-anchor\", \"middle\")\n            .text(title);\n    }\n\n    /**\n     * Calculates the frequency of bytes in the input.\n     *\n     * @param {Uint8Array} inputBytes\n     * @returns {number[]}\n     */\n    calculateByteFrequency(inputBytes) {\n        const freq = new Array(256).fill(0);\n        if (inputBytes.length === 0) return freq;\n\n        // Count occurrences of each byte in the input\n        let i;\n        for (i = 0; i < inputBytes.length; i++) {\n            freq[inputBytes[i]]++;\n        }\n\n        for (i = 0; i < freq.length; i++) {\n            freq[i] = freq[i] / inputBytes.length;\n        }\n\n        return freq;\n    }\n\n    /**\n     * Calculates the frequency of bytes in the input.\n     *\n     * @param {number[]} byteFrequency\n     * @returns {HTML}\n     */\n    createByteFrequencyLineHistogram(byteFrequency) {\n        const margins = { top: 30, right: 20, bottom: 50, left: 30 };\n\n        const svgWidth = 500,\n            svgHeight = 500;\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${svgWidth} ${svgHeight}`);\n\n        const yScale = d3.scaleLinear()\n            .domain([0, d3.max(byteFrequency, d => d)])\n            .range([svgHeight - margins.bottom, margins.top]);\n\n        const xScale = d3.scaleLinear()\n            .domain([0, byteFrequency.length - 1])\n            .range([margins.left, svgWidth - margins.right]);\n\n        const line = d3.line()\n            .x((_, i) => xScale(i))\n            .y(d => yScale(d))\n            .curve(d3.curveMonotoneX);\n\n        svg.append(\"path\")\n            .datum(byteFrequency)\n            .attr(\"fill\", \"none\")\n            .attr(\"stroke\", \"steelblue\")\n            .attr(\"d\", line);\n\n        this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, \"\", \"Byte\", \"Byte Frequency\");\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n    /**\n     * Creates a byte frequency histogram\n     *\n     * @param {number[]} byteFrequency\n     * @returns {HTML}\n     */\n    createByteFrequencyBarHistogram(byteFrequency) {\n        const margins = { top: 30, right: 20, bottom: 50, left: 30 };\n\n        const svgWidth = 500,\n            svgHeight = 500,\n            binWidth = 1;\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${svgWidth} ${svgHeight}`);\n\n        const yExtent = d3.extent(byteFrequency, d => d);\n        const yScale = d3.scaleLinear()\n            .domain(yExtent)\n            .range([svgHeight - margins.bottom, margins.top]);\n\n        const xScale = d3.scaleLinear()\n            .domain([0, byteFrequency.length - 1])\n            .range([margins.left - binWidth, svgWidth - margins.right]);\n\n        svg.selectAll(\"rect\")\n            .data(byteFrequency)\n            .enter().append(\"rect\")\n            .attr(\"x\", (_, i) => xScale(i) + binWidth)\n            .attr(\"y\", dataPoint => yScale(dataPoint))\n            .attr(\"width\", binWidth)\n            .attr(\"height\", dataPoint => yScale(yExtent[0]) - yScale(dataPoint))\n            .attr(\"fill\", \"blue\");\n\n        this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, \"\", \"Byte\", \"Byte Frequency\");\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n    /**\n     * Creates a byte frequency histogram\n     *\n     * @param {number[]} entropyData\n     * @returns {HTML}\n     */\n    createEntropyCurve(entropyData) {\n        const margins = { top: 30, right: 20, bottom: 50, left: 30 };\n\n        const svgWidth = 500,\n            svgHeight = 500;\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${svgWidth} ${svgHeight}`);\n\n        const yScale = d3.scaleLinear()\n            .domain([0, d3.max(entropyData, d => d)])\n            .range([svgHeight - margins.bottom, margins.top]);\n\n        const xScale = d3.scaleLinear()\n            .domain([0, entropyData.length])\n            .range([margins.left, svgWidth - margins.right]);\n\n        const line = d3.line()\n            .x((_, i) => xScale(i))\n            .y(d => yScale(d))\n            .curve(d3.curveMonotoneX);\n\n        if (entropyData.length > 0) {\n            svg.append(\"path\")\n                .datum(entropyData)\n                .attr(\"d\", line);\n\n            svg.selectAll(\"path\").attr(\"fill\", \"none\").attr(\"stroke\", \"steelblue\");\n        }\n\n        this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, \"Scanning Entropy\", \"Block\", \"Entropy\");\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n    /**\n     * Creates an image representation of the entropy\n     *\n     * @param {number[]} entropyData\n     * @returns {HTML}\n     */\n    createEntropyImage(entropyData) {\n        const svgHeight = 100,\n            svgWidth = 100,\n            cellSize = 1,\n            nodes = [];\n\n        for (let i = 0; i < entropyData.length; i++) {\n            nodes.push({\n                x: i % svgWidth,\n                y: Math.floor(i / svgWidth),\n                entropy: entropyData[i]\n            });\n        }\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${svgWidth} ${svgHeight}`);\n\n        const greyScale = d3.scaleLinear()\n            .domain([0, d3.max(entropyData, d => d)])\n            .range([\"#000000\", \"#FFFFFF\"])\n            .interpolate(d3.interpolateRgb);\n\n        svg\n            .selectAll(\"rect\")\n            .data(nodes)\n            .enter().append(\"rect\")\n            .attr(\"x\", d => d.x * cellSize)\n            .attr(\"y\", d => d.y * cellSize)\n            .attr(\"width\", cellSize)\n            .attr(\"height\", cellSize)\n            .style(\"fill\", d => greyScale(d.entropy));\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n    /**\n     * Displays the entropy as a scale bar for web apps.\n     *\n     * @param {number} entropy\n     * @returns {HTML}\n     */\n    createShannonEntropyVisualization(entropy) {\n        return `Shannon entropy: ${entropy}\n        <br><canvas id='chart-area'></canvas><br>\n        - 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string.\n        - Standard English text usually falls somewhere between 3.5 and 5.\n        - Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5.\n\n        The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections.\n\n        <br><script>\n            var canvas = document.getElementById(\"chart-area\"),\n                parentRect = canvas.closest(\".cm-scroller\").getBoundingClientRect(),\n                entropy = ${entropy},\n                height = parentRect.height * 0.25;\n\n            canvas.width = parentRect.width * 0.95;\n            canvas.height = height > 150 ? 150 : height;\n\n            CanvasComponents.drawScaleBar(canvas, entropy, 8, [\n                {\n                    label: \"English text\",\n                    min: 3.5,\n                    max: 5\n                },{\n                    label: \"Encrypted/compressed\",\n                    min: 7.5,\n                    max: 8\n                }\n            ]);\n        </script>`;\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {json}\n     */\n    run(input, args) {\n        const visualizationType = args[0];\n        input = new Uint8Array(input);\n\n        switch (visualizationType) {\n            case \"Histogram (Bar)\":\n            case \"Histogram (Line)\":\n                return this.calculateByteFrequency(input);\n            case \"Curve\":\n            case \"Image\":\n                return this.calculateScanningEntropy(input).entropyData;\n            case \"Shannon scale\":\n            default:\n                return this.calculateShannonEntropy(input);\n        }\n    }\n\n    /**\n     * Displays the entropy in a visualisation for web apps.\n     *\n     * @param {json} entropyData\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    present(entropyData, args) {\n        const visualizationType = args[0];\n\n        switch (visualizationType) {\n            case \"Histogram (Bar)\":\n                return this.createByteFrequencyBarHistogram(entropyData);\n            case \"Histogram (Line)\":\n                return this.createByteFrequencyLineHistogram(entropyData);\n            case \"Curve\":\n                return this.createEntropyCurve(entropyData);\n            case \"Image\":\n                return this.createEntropyImage(entropyData);\n            case \"Shannon scale\":\n            default:\n                return this.createShannonEntropyVisualization(entropyData);\n        }\n    }\n}\n\nexport default Entropy;\n"
  },
  {
    "path": "src/core/operations/EscapeString.mjs",
    "content": "/**\n * @author Vel0x [dalemy@microsoft.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport jsesc from \"jsesc\";\n\n/**\n * Escape string operation\n */\nclass EscapeString extends Operation {\n\n    /**\n     * EscapeString constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Escape string\";\n        this.module = \"Default\";\n        this.description = \"Escapes special characters in a string so that they do not cause conflicts. For example, <code>Don't stop me now</code> becomes <code>Don\\\\'t stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\\\n</code> (Line feed/newline)</li><li><code>\\\\r</code> (Carriage return)</li><li><code>\\\\t</code> (Horizontal tab)</li><li><code>\\\\b</code> (Backspace)</li><li><code>\\\\f</code> (Form feed)</li><li><code>\\\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\\\\\</code> (Backslash)</li><li><code>\\\\'</code> (Single quote)</li><li><code>\\\\&quot;</code> (Double quote)</li><li><code>\\\\unnnn</code> (Unicode character)</li><li><code>\\\\u{nnnnnn}</code> (Unicode code point)</li></ul>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Escape_sequence\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Escape level\",\n                \"type\": \"option\",\n                \"value\": [\"Special chars\", \"Everything\", \"Minimal\"]\n            },\n            {\n                \"name\": \"Escape quote\",\n                \"type\": \"option\",\n                \"value\": [\"Single\", \"Double\", \"Backtick\"]\n            },\n            {\n                \"name\": \"JSON compatible\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"ES6 compatible\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Uppercase hex\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @example\n     * EscapeString.run(\"Don't do that\", [])\n     * > \"Don\\'t do that\"\n     * EscapeString.run(`Hello\n     * World`, [])\n     * > \"Hello\\nWorld\"\n     */\n    run(input, args) {\n        const level = args[0],\n            quotes = args[1],\n            jsonCompat = args[2],\n            es6Compat = args[3],\n            lowercaseHex = !args[4];\n\n        return jsesc(input, {\n            quotes: quotes.toLowerCase(),\n            es6: es6Compat,\n            escapeEverything: level === \"Everything\",\n            minimal: level === \"Minimal\",\n            json: jsonCompat,\n            lowercaseHex: lowercaseHex,\n        });\n    }\n\n}\n\nexport default EscapeString;\n"
  },
  {
    "path": "src/core/operations/EscapeUnicodeCharacters.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Escape Unicode Characters operation\n */\nclass EscapeUnicodeCharacters extends Operation {\n\n    /**\n     * EscapeUnicodeCharacters constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Escape Unicode Characters\";\n        this.module = \"Default\";\n        this.description = \"Converts characters to their unicode-escaped notations.<br><br>Supports the prefixes:<ul><li><code>\\\\u</code></li><li><code>%u</code></li><li><code>U+</code></li></ul>e.g. <code>σου</code> becomes <code>\\\\u03C3\\\\u03BF\\\\u03C5</code>\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Prefix\",\n                \"type\": \"option\",\n                \"value\": [\"\\\\u\", \"%u\", \"U+\"]\n            },\n            {\n                \"name\": \"Encode all chars\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Padding\",\n                \"type\": \"number\",\n                \"value\": 4\n            },\n            {\n                \"name\": \"Uppercase hex\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const regexWhitelist = /[ -~]/i,\n            [prefix, encodeAll, padding, uppercaseHex] = args;\n\n        let output = \"\",\n            character = \"\";\n\n        for (let i = 0; i < input.length; i++) {\n            character = input[i];\n            if (!encodeAll && regexWhitelist.test(character)) {\n                // It’s a printable ASCII character so don’t escape it.\n                output += character;\n                continue;\n            }\n\n            let cp = character.codePointAt(0).toString(16);\n            if (uppercaseHex) cp = cp.toUpperCase();\n            output += prefix + cp.padStart(padding, \"0\");\n        }\n\n        return output;\n    }\n\n}\n\nexport default EscapeUnicodeCharacters;\n"
  },
  {
    "path": "src/core/operations/ExpandAlphabetRange.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Expand alphabet range operation\n */\nclass ExpandAlphabetRange extends Operation {\n\n    /**\n     * ExpandAlphabetRange constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Expand alphabet range\";\n        this.module = \"Default\";\n        this.description = \"Expand an alphabet range string into a list of the characters in that range.<br><br>e.g. <code>a-z</code> becomes <code>abcdefghijklmnopqrstuvwxyz</code>.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"binaryString\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return Utils.expandAlphRange(input).join(args[0]);\n    }\n\n}\n\nexport default ExpandAlphabetRange;\n"
  },
  {
    "path": "src/core/operations/ExtractAudioMetadata.mjs",
    "content": "/**\n * @author d0s1nt [d0s1nt@cyberchefaudio]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { makeEmptyReport, sniffContainer } from \"../lib/AudioMetaSchema.mjs\";\nimport {\n    parseMp3, parseRiffWave, parseFlac, parseOgg,\n    parseMp4BestEffort, parseAiffBestEffort,\n    parseAacAdts, parseAc3, parseWmaAsf,\n} from \"../lib/AudioParsers.mjs\";\n\n/**\n * Extract Audio Metadata operation.\n */\nclass ExtractAudioMetadata extends Operation {\n    /** Creates the Extract Audio Metadata operation. */\n    constructor() {\n        super();\n\n        this.name = \"Extract Audio Metadata\";\n        this.module = \"Default\";\n        this.description =\n            \"Extract common audio metadata across MP3 (ID3v2/ID3v1/GEOB), WAV/BWF/BW64 (INFO/bext/iXML/axml), FLAC (Vorbis Comment/Picture), OGG (Vorbis/OpusTags), AAC (ADTS), AC3 (Dolby Digital), WMA (ASF), plus best-effort MP4/M4A and AIFF scanning. Outputs normalized JSON.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Audio_file_format\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.presentType = \"html\";\n\n        this.args = [\n            { name: \"Filename (optional)\", type: \"string\", value: \"\" },\n            { name: \"Max embedded text bytes (iXML/axml/etc)\", type: \"number\", value: 1024 * 512 },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {Object}\n     */\n    run(input, args) {\n        const filename = (args?.[0] || \"\").trim() || null;\n        const maxTextBytes = Number.isFinite(args?.[1]) ? Math.max(1024, args[1]) : 1024 * 512;\n\n        if (!(input instanceof ArrayBuffer) || input.byteLength === 0)\n            throw new OperationError(\"No input data. Load an audio file (drag/drop or use the open file button).\");\n\n        const bytes = new Uint8Array(input);\n        const container = sniffContainer(bytes);\n        const report = makeEmptyReport(filename, bytes.length, container);\n\n        try {\n            const parsers = {\n                mp3: () => parseMp3(bytes, report),\n                wav: () => parseRiffWave(bytes, report, maxTextBytes),\n                bw64: () => parseRiffWave(bytes, report, maxTextBytes),\n                flac: () => parseFlac(bytes, report, maxTextBytes),\n                ogg: () => parseOgg(bytes, report),\n                opus: () => parseOgg(bytes, report),\n                mp4: () => parseMp4BestEffort(bytes, report),\n                m4a: () => parseMp4BestEffort(bytes, report),\n                aiff: () => parseAiffBestEffort(bytes, report, maxTextBytes),\n                aac: () => parseAacAdts(bytes, report),\n                ac3: () => parseAc3(bytes, report),\n                wma: () => parseWmaAsf(bytes, report),\n            };\n            if (parsers[container.type]) {\n                parsers[container.type]();\n            } else {\n                report.errors.push({ stage: \"sniff\", message: \"Unknown/unsupported container (best-effort scan not implemented).\" });\n            }\n        } catch (e) {\n            report.errors.push({ stage: \"parse\", message: String(e?.message || e) });\n        }\n\n        return report;\n    }\n\n    /** Renders the extracted metadata as an HTML table. */\n    present(data) {\n        if (!data || typeof data !== \"object\") return JSON.stringify(data, null, 4);\n\n        const esc = Utils.escapeHtml;\n        const row = (k, v) => `<tr><td>${esc(String(k))}</td><td>${esc(String(v ?? \"\"))}</td></tr>\\n`;\n        const section = (title) => `<tr><th colspan=\"2\" style=\"background:#e9ecef;text-align:center\">${esc(title)}</th></tr>\\n`;\n        const objRows = (obj, filter = (v) => v !== null) => {\n            for (const [k, v] of Object.entries(obj)) {\n                if (filter(v)) html += row(k, v);\n            }\n        };\n        const objSection = (obj, title, filter) => {\n            if (!obj) return;\n            html += section(title);\n            objRows(obj, filter);\n        };\n        const listSection = (arr, title, fmt) => {\n            if (!arr?.length) return;\n            html += section(title);\n            for (const item of arr) html += fmt(item);\n        };\n\n        let html = `<table class=\"table table-hover table-sm table-bordered table-nonfluid\">\\n`;\n\n        html += section(\"Artifact\");\n        html += row(\"Filename\", data.artifact?.filename || \"(none)\");\n        html += row(\"Size\", `${(data.artifact?.byte_length ?? 0).toLocaleString()} bytes`);\n        html += row(\"Container\", data.artifact?.container?.type);\n        html += row(\"MIME\", data.artifact?.container?.mime);\n        if (data.artifact?.container?.brand) html += row(\"Brand\", data.artifact.container.brand);\n\n        html += section(\"Detections\");\n        html += row(\"Metadata systems\", (data.detections?.metadata_systems || []).join(\", \") || \"None\");\n        html += row(\"Provenance systems\", (data.detections?.provenance_systems || []).join(\", \") || \"None\");\n\n        const common = data.tags?.common || {};\n        html += section(\"Common Tags\");\n        if (Object.values(common).some((v) => v !== null)) {\n            for (const [key, val] of Object.entries(common)) {\n                if (val !== null) html += row(key.charAt(0).toUpperCase() + key.slice(1), val);\n            }\n        } else {\n            html += row(\"(none)\", \"No common tags found\");\n        }\n\n        listSection(data.tags?.raw?.id3v2?.frames, \"ID3v2 Frames\", (f) => {\n            const val = typeof f.decoded === \"object\" ? JSON.stringify(f.decoded) : (f.decoded ?? `(${f.size} bytes)`);\n            return row(f.id + (f.description ? ` \\u2014 ${f.description}` : \"\"), val);\n        });\n        objSection(data.tags?.raw?.id3v1, \"ID3v1\", (v) => !!v);\n        listSection(data.tags?.raw?.apev2?.items, \"APEv2 Tags\", (i) => row(i.key, i.value));\n\n        if (data.tags?.raw?.vorbis_comments?.comments?.length) {\n            html += section(\"Vorbis Comments\");\n            html += row(\"Vendor\", data.tags.raw.vorbis_comments.vendor);\n            for (const c of data.tags.raw.vorbis_comments.comments) html += row(c.key, c.value);\n        }\n\n        objSection(data.tags?.raw?.riff?.info, \"RIFF INFO\", () => true);\n        objSection(data.tags?.raw?.riff?.bext, \"BWF bext\");\n        listSection(data.tags?.raw?.riff?.chunks, \"RIFF Chunks\", (c) => row(c.id, `${c.size} bytes @ offset ${c.offset}`));\n        listSection(data.tags?.raw?.flac?.blocks, \"FLAC Metadata Blocks\", (b) => row(b.type, `${b.length} bytes`));\n\n        if (data.tags?.raw?.mp4?.top_level_atoms?.length) {\n            html += section(\"MP4 Top-Level Atoms\");\n            const atoms = data.tags.raw.mp4.top_level_atoms;\n            for (const a of atoms.slice(0, 50)) html += row(a.type, `${a.size} bytes @ offset ${a.offset}`);\n            if (atoms.length > 50) html += row(\"...\", `${atoms.length - 50} more atoms`);\n        }\n\n        listSection(data.tags?.raw?.aiff?.chunks, \"AIFF Chunks\", (c) => row(c.id, c.value));\n        objSection(data.tags?.raw?.aac, \"AAC ADTS\");\n        objSection(data.tags?.raw?.ac3, \"AC3 (Dolby Digital)\");\n        objSection(data.tags?.raw?.asf?.content_description, \"ASF Content Description\", (v) => !!v);\n        listSection(data.tags?.raw?.asf?.extended_content, \"ASF Extended Content\", (d) => row(d.name, d.value));\n        listSection(data.embedded, \"Embedded Objects\", (e) => row(e.id, `${e.content_type || \"unknown\"} \\u2014 ${(e.byte_length ?? 0).toLocaleString()} bytes`));\n\n        if (data.provenance?.c2pa?.present) {\n            html += section(\"C2PA Provenance\");\n            html += row(\"Present\", \"Yes\");\n            for (const emb of (data.provenance.c2pa.embedding || []))\n                html += row(\"Carrier\", `${emb.carrier} \\u2014 ${(emb.byte_length ?? 0).toLocaleString()} bytes`);\n        }\n\n        listSection(data.errors, \"Errors\", (e) => row(e.stage, e.message));\n\n        html += \"</table>\";\n        return html;\n    }\n}\n\nexport default ExtractAudioMetadata;\n"
  },
  {
    "path": "src/core/operations/ExtractDates.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search } from \"../lib/Extract.mjs\";\n\n/**\n * Extract dates operation\n */\nclass ExtractDates extends Operation {\n\n    /**\n     * ExtractDates constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract dates\";\n        this.module = \"Regex\";\n        this.description = \"Extracts dates in the following formats<ul><li><code>yyyy-mm-dd</code></li><li><code>dd/mm/yyyy</code></li><li><code>mm/dd/yyyy</code></li></ul>Dividers can be any of /, -, . or space\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Display total\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const displayTotal = args[0],\n            date1 = \"(?:19|20)\\\\d\\\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])\", // yyyy-mm-dd\n            date2 = \"(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\\\d\\\\d\", // dd/mm/yyyy\n            date3 = \"(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\\\d\\\\d\", // mm/dd/yyyy\n            regex = new RegExp(date1 + \"|\" + date2 + \"|\" + date3, \"ig\");\n\n        const results = search(input, regex);\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default ExtractDates;\n"
  },
  {
    "path": "src/core/operations/ExtractDomains.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search, DOMAIN_REGEX, DMARC_DOMAIN_REGEX } from \"../lib/Extract.mjs\";\nimport { caseInsensitiveSort } from \"../lib/Sort.mjs\";\n\n/**\n * Extract domains operation\n */\nclass ExtractDomains extends Operation {\n\n    /**\n     * ExtractDomains constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract domains\";\n        this.module = \"Regex\";\n        this.description = \"Extracts fully qualified domain names.<br>Note that this will not include paths. Use <strong>Extract URLs</strong> to find entire URLs.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Display total\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sort\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Unique\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Underscore (DMARC, DKIM, etc)\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [displayTotal, sort, unique, dmarc] = args;\n\n        const results = search(\n            input,\n            dmarc ? DMARC_DOMAIN_REGEX : DOMAIN_REGEX,\n            null,\n            sort ? caseInsensitiveSort : null,\n            unique\n        );\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default ExtractDomains;\n"
  },
  {
    "path": "src/core/operations/ExtractEXIF.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport ExifParser from \"exif-parser\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Extract EXIF operation\n */\nclass ExtractEXIF extends Operation {\n\n    /**\n     * ExtractEXIF constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract EXIF\";\n        this.module = \"Image\";\n        this.description = [\n            \"Extracts EXIF data from an image.\",\n            \"<br><br>\",\n            \"EXIF data is metadata embedded in images (JPEG, JPG, TIFF) and audio files.\",\n            \"<br><br>\",\n            \"EXIF data from photos usually contains information about the image file itself as well as the device used to create it.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Exif\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        try {\n            const parser = ExifParser.create(input);\n            const result = parser.parse();\n\n            const lines = [];\n            for (const tagName in result.tags) {\n                const value = result.tags[tagName];\n                lines.push(`${tagName}: ${value}`);\n            }\n\n            const numTags = lines.length;\n            lines.unshift(`Found ${numTags} tags.\\n`);\n            return lines.join(\"\\n\");\n        } catch (err) {\n            throw new OperationError(`Could not extract EXIF data from image: ${err}`);\n        }\n    }\n\n}\n\nexport default ExtractEXIF;\n"
  },
  {
    "path": "src/core/operations/ExtractEmailAddresses.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search } from \"../lib/Extract.mjs\";\nimport { caseInsensitiveSort } from \"../lib/Sort.mjs\";\n\n/**\n * Extract email addresses operation\n */\nclass ExtractEmailAddresses extends Operation {\n\n    /**\n     * ExtractEmailAddresses constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract email addresses\";\n        this.module = \"Regex\";\n        this.description = \"Extracts all email addresses from the input.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Display total\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sort\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Unique\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [displayTotal, sort, unique] = args,\n            // email regex from: https://www.regextester.com/98066\n            regex = /(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?\\.)+[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\])/ig;\n\n        const results = search(\n            input,\n            regex,\n            null,\n            sort ? caseInsensitiveSort : null,\n            unique\n        );\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default ExtractEmailAddresses;\n"
  },
  {
    "path": "src/core/operations/ExtractFilePaths.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search } from \"../lib/Extract.mjs\";\nimport { caseInsensitiveSort } from \"../lib/Sort.mjs\";\n\n/**\n * Extract file paths operation\n */\nclass ExtractFilePaths extends Operation {\n\n    /**\n     * ExtractFilePaths constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract file paths\";\n        this.module = \"Regex\";\n        this.description = \"Extracts anything that looks like a Windows or UNIX file path.<br><br>Note that if UNIX is selected, there will likely be a lot of false positives.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Windows\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"UNIX\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Display total\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sort\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Unique\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = args,\n            winDrive = \"[A-Z]:\\\\\\\\\",\n            winName = \"[A-Z\\\\d][A-Z\\\\d\\\\- '_\\\\(\\\\)~]{0,61}\",\n            winExt = \"[A-Z\\\\d]{1,6}\",\n            winPath = winDrive + \"(?:\" + winName + \"\\\\\\\\?)*\" + winName +\n                \"(?:\\\\.\" + winExt + \")?\",\n            unixPath = \"(?:/[A-Z\\\\d.][A-Z\\\\d\\\\-.]{0,61})+\";\n        let filePaths = \"\";\n\n        if (includeWinPath && includeUnixPath) {\n            filePaths = winPath + \"|\" + unixPath;\n        } else if (includeWinPath) {\n            filePaths = winPath;\n        } else if (includeUnixPath) {\n            filePaths = unixPath;\n        }\n\n        if (!filePaths) {\n            return \"\";\n        }\n\n        const regex = new RegExp(filePaths, \"ig\");\n        const results = search(\n            input,\n            regex,\n            null,\n            sort ? caseInsensitiveSort : null,\n            unique\n        );\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n\n    }\n\n}\n\nexport default ExtractFilePaths;\n"
  },
  {
    "path": "src/core/operations/ExtractFiles.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {scanForFileTypes, extractFile} from \"../lib/FileType.mjs\";\nimport {FILE_SIGNATURES} from \"../lib/FileSignatures.mjs\";\n\n/**\n * Extract Files operation\n */\nclass ExtractFiles extends Operation {\n\n    /**\n     * ExtractFiles constructor\n     */\n    constructor() {\n        super();\n\n        // Get the first extension for each signature that can be extracted\n        let supportedExts = Object.keys(FILE_SIGNATURES).map(cat => {\n            return FILE_SIGNATURES[cat]\n                .filter(sig => sig.extractor)\n                .map(sig => sig.extension.toUpperCase());\n        });\n\n        // Flatten categories and remove duplicates\n        supportedExts = [].concat(...supportedExts).unique();\n\n        this.name = \"Extract Files\";\n        this.module = \"Default\";\n        this.description = `Performs file carving to attempt to extract files from the input.<br><br>This operation is currently capable of carving out the following formats:\n            <ul>\n                <li>\n                ${supportedExts.join(\"</li><li>\")}\n                </li>\n            </ul>Minimum File Size can be used to prune small false positives.`;\n        this.infoURL = \"https://forensics.wiki/file_carving\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"List<File>\";\n        this.presentType = \"html\";\n        this.args = Object.keys(FILE_SIGNATURES).map(cat => {\n            return {\n                name: cat,\n                type: \"boolean\",\n                value: cat === \"Miscellaneous\" ? false : true\n            };\n        }).concat([\n            {\n                name: \"Ignore failed extractions\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Minimum File Size\",\n                type: \"number\",\n                value: 100\n            }\n        ]);\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {List<File>}\n     */\n    run(input, args) {\n        const bytes = new Uint8Array(input),\n            categories = [],\n            minSize = args.pop(1),\n            ignoreFailedExtractions = args.pop(1);\n\n        args.forEach((cat, i) => {\n            if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);\n        });\n\n        // Scan for embedded files\n        const detectedFiles = scanForFileTypes(bytes, categories);\n\n        // Extract each file that we support\n        const files = [];\n        const errors = [];\n        detectedFiles.forEach(detectedFile => {\n            try {\n                const file = extractFile(bytes, detectedFile.fileDetails, detectedFile.offset);\n                if (file.size >= minSize)\n                    files.push(file);\n            } catch (err) {\n                if (!ignoreFailedExtractions && err.message.indexOf(\"No extraction algorithm available\") < 0) {\n                    errors.push(\n                        `Error while attempting to extract ${detectedFile.fileDetails.name} ` +\n                        `at offset ${detectedFile.offset}:\\n` +\n                        `${err.message}`\n                    );\n                }\n            }\n        });\n\n        if (errors.length) {\n            throw new OperationError(errors.join(\"\\n\\n\"));\n        }\n\n        return files;\n    }\n\n\n    /**\n     * Displays the files in HTML for web apps.\n     *\n     * @param {File[]} files\n     * @returns {html}\n     */\n    async present(files) {\n        return await Utils.displayFilesAsHTML(files);\n    }\n\n}\n\nexport default ExtractFiles;\n"
  },
  {
    "path": "src/core/operations/ExtractHashes.mjs",
    "content": "/**\n * @author mshwed [m@ttshwed.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search } from \"../lib/Extract.mjs\";\n\n/**\n * Extract Hash Values operation\n */\nclass ExtractHashes extends Operation {\n\n    /**\n     * ExtractHashValues constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract hashes\";\n        this.module = \"Regex\";\n        this.description = \"Extracts potential hashes based on hash character length\";\n        this.infoURL = \"https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Hash character length\",\n                type: \"number\",\n                value: 40\n            },\n            {\n                name: \"All hashes\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Display Total\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const results = [];\n        let hashCount = 0;\n\n        const [hashLength, searchAllHashes, showDisplayTotal] = args;\n\n        // Convert character length to bit length\n        let hashBitLengths = [(hashLength / 2) * 8];\n\n        if (searchAllHashes) hashBitLengths = [4, 8, 16, 32, 64, 128, 160, 192, 224, 256, 320, 384, 512, 1024];\n\n        for (const hashBitLength of hashBitLengths) {\n            // Convert bit length to character length\n            const hashCharacterLength = (hashBitLength / 8) * 2;\n\n            const regex = new RegExp(`(\\\\b|^)[a-f0-9]{${hashCharacterLength}}(\\\\b|$)`, \"g\");\n            const searchResults = search(input, regex, null, false);\n\n            hashCount += searchResults.length;\n            results.push(...searchResults);\n        }\n\n        let output = \"\";\n        if (showDisplayTotal) {\n            output = `Total Results: ${hashCount}\\n\\n`;\n        }\n\n        output = output + results.join(\"\\n\");\n        return output;\n    }\n\n}\n\nexport default ExtractHashes;\n"
  },
  {
    "path": "src/core/operations/ExtractID3.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Extract ID3 operation\n */\nclass ExtractID3 extends Operation {\n\n    /**\n     * ExtractID3 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract ID3\";\n        this.module = \"Default\";\n        this.description = \"This operation extracts ID3 metadata from an MP3 file.<br><br>ID3 is a metadata container most often used in conjunction with the MP3 audio file format. It allows information such as the title, artist, album, track number, and other information about the file to be stored in the file itself.\";\n        this.infoURL = \"https://wikipedia.org/wiki/ID3\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.presentType = \"html\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n\n        /**\n         * Extracts the ID3 header fields.\n         */\n        function extractHeader() {\n            if (!Array.from(input.slice(0, 3)).equals([0x49, 0x44, 0x33]))\n                throw new OperationError(\"No valid ID3 header.\");\n\n            const header = {\n                \"Type\": \"ID3\",\n                // Tag version\n                \"Version\": input[3].toString() + \".\" + input[4].toString(),\n                // Header version\n                \"Flags\": input[5].toString()\n            };\n\n            input = input.slice(6);\n            return header;\n        }\n\n        /**\n         * Converts the size fields to a single integer.\n         *\n         * @param {number} num\n         * @returns {string}\n         */\n        function readSize(num) {\n            let result = 0;\n\n            // The sizes are 7 bit numbers stored in 8 bit locations\n            for (let i = (num) * 7; i; i -= 7) {\n                result = (result << i) | input[0];\n                input = input.slice(1);\n            }\n            return result;\n        }\n\n        /**\n         * Reads frame header based on ID.\n         *\n         * @param {string} id\n         * @returns {number}\n         */\n        function readFrame(id) {\n            const frame = {};\n\n            // Size of frame\n            const size = readSize(4);\n            frame.Size = size.toString();\n            frame.Description = FRAME_DESCRIPTIONS[id];\n            input = input.slice(2);\n\n            // Read data from frame\n            let data = \"\";\n            for (let i = 1; i < size; i++)\n                data += String.fromCharCode(input[i]);\n            frame.Data = data;\n\n            // Move to next Frame\n            input = input.slice(size);\n\n            return [frame, size];\n        }\n\n        const result = extractHeader();\n\n        const headerTagSize = readSize(4);\n        result.Size = headerTagSize.toString();\n\n        const tags = {};\n        let pos = 10;\n\n        // While the current element is in the header\n        while (pos < headerTagSize) {\n\n            // Frame Identifier of frame\n            let id = String.fromCharCode(input[0]) + String.fromCharCode(input[1]) + String.fromCharCode(input[2]);\n            input = input.slice(3);\n\n            // If the next character is non-zero it is an identifier\n            if (input[0] !== 0) {\n                id += String.fromCharCode(input[0]);\n            }\n            input = input.slice(1);\n\n            if (id in FRAME_DESCRIPTIONS) {\n                const [frame, size] = readFrame(id);\n                tags[id] = frame;\n                pos += 10 + size;\n            } else if (id === \"\\x00\\x00\\x00\") { // end of header\n                break;\n            } else {\n                throw new OperationError(\"Unknown Frame Identifier: \" + id);\n            }\n        }\n\n        result.Tags = tags;\n\n        return result;\n    }\n\n    /**\n     * Displays the extracted data in a more accessible format for web apps.\n     * @param {JSON} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data || !Object.prototype.hasOwnProperty.call(data, \"Tags\"))\n            return JSON.stringify(data, null, 4);\n\n        let output = `<table class=\"table table-hover table-sm table-bordered table-nonfluid\">\n            <tr><th>Tag</th><th>Description</th><th>Data</th></tr>`;\n\n        for (const tagID in data.Tags) {\n            const description = data.Tags[tagID].Description,\n                contents = data.Tags[tagID].Data;\n            output += `<tr><td>${tagID}</td><td>${Utils.escapeHtml(description)}</td><td>${Utils.escapeHtml(contents)}</td></tr>`;\n        }\n        output += \"</table>\";\n        return output;\n    }\n\n}\n\n// Borrowed from https://github.com/aadsm/jsmediatags\nconst FRAME_DESCRIPTIONS = {\n    // v2.2\n    \"BUF\": \"Recommended buffer size\",\n    \"CNT\": \"Play counter\",\n    \"COM\": \"Comments\",\n    \"CRA\": \"Audio encryption\",\n    \"CRM\": \"Encrypted meta frame\",\n    \"ETC\": \"Event timing codes\",\n    \"EQU\": \"Equalization\",\n    \"GEO\": \"General encapsulated object\",\n    \"IPL\": \"Involved people list\",\n    \"LNK\": \"Linked information\",\n    \"MCI\": \"Music CD Identifier\",\n    \"MLL\": \"MPEG location lookup table\",\n    \"PIC\": \"Attached picture\",\n    \"POP\": \"Popularimeter\",\n    \"REV\": \"Reverb\",\n    \"RVA\": \"Relative volume adjustment\",\n    \"SLT\": \"Synchronized lyric/text\",\n    \"STC\": \"Synced tempo codes\",\n    \"TAL\": \"Album/Movie/Show title\",\n    \"TBP\": \"BPM (Beats Per Minute)\",\n    \"TCM\": \"Composer\",\n    \"TCO\": \"Content type\",\n    \"TCR\": \"Copyright message\",\n    \"TDA\": \"Date\",\n    \"TDY\": \"Playlist delay\",\n    \"TEN\": \"Encoded by\",\n    \"TFT\": \"File type\",\n    \"TIM\": \"Time\",\n    \"TKE\": \"Initial key\",\n    \"TLA\": \"Language(s)\",\n    \"TLE\": \"Length\",\n    \"TMT\": \"Media type\",\n    \"TOA\": \"Original artist(s)/performer(s)\",\n    \"TOF\": \"Original filename\",\n    \"TOL\": \"Original Lyricist(s)/text writer(s)\",\n    \"TOR\": \"Original release year\",\n    \"TOT\": \"Original album/Movie/Show title\",\n    \"TP1\": \"Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group\",\n    \"TP2\": \"Band/Orchestra/Accompaniment\",\n    \"TP3\": \"Conductor/Performer refinement\",\n    \"TP4\": \"Interpreted, remixed, or otherwise modified by\",\n    \"TPA\": \"Part of a set\",\n    \"TPB\": \"Publisher\",\n    \"TRC\": \"ISRC (International Standard Recording Code)\",\n    \"TRD\": \"Recording dates\",\n    \"TRK\": \"Track number/Position in set\",\n    \"TSI\": \"Size\",\n    \"TSS\": \"Software/hardware and settings used for encoding\",\n    \"TT1\": \"Content group description\",\n    \"TT2\": \"Title/Songname/Content description\",\n    \"TT3\": \"Subtitle/Description refinement\",\n    \"TXT\": \"Lyricist/text writer\",\n    \"TXX\": \"User defined text information frame\",\n    \"TYE\": \"Year\",\n    \"UFI\": \"Unique file identifier\",\n    \"ULT\": \"Unsychronized lyric/text transcription\",\n    \"WAF\": \"Official audio file webpage\",\n    \"WAR\": \"Official artist/performer webpage\",\n    \"WAS\": \"Official audio source webpage\",\n    \"WCM\": \"Commercial information\",\n    \"WCP\": \"Copyright/Legal information\",\n    \"WPB\": \"Publishers official webpage\",\n    \"WXX\": \"User defined URL link frame\",\n    // v2.3\n    \"AENC\": \"Audio encryption\",\n    \"APIC\": \"Attached picture\",\n    \"ASPI\": \"Audio seek point index\",\n    \"CHAP\": \"Chapter\",\n    \"CTOC\": \"Table of contents\",\n    \"COMM\": \"Comments\",\n    \"COMR\": \"Commercial frame\",\n    \"ENCR\": \"Encryption method registration\",\n    \"EQU2\": \"Equalisation (2)\",\n    \"EQUA\": \"Equalization\",\n    \"ETCO\": \"Event timing codes\",\n    \"GEOB\": \"General encapsulated object\",\n    \"GRID\": \"Group identification registration\",\n    \"IPLS\": \"Involved people list\",\n    \"LINK\": \"Linked information\",\n    \"MCDI\": \"Music CD identifier\",\n    \"MLLT\": \"MPEG location lookup table\",\n    \"OWNE\": \"Ownership frame\",\n    \"PRIV\": \"Private frame\",\n    \"PCNT\": \"Play counter\",\n    \"POPM\": \"Popularimeter\",\n    \"POSS\": \"Position synchronisation frame\",\n    \"RBUF\": \"Recommended buffer size\",\n    \"RVA2\": \"Relative volume adjustment (2)\",\n    \"RVAD\": \"Relative volume adjustment\",\n    \"RVRB\": \"Reverb\",\n    \"SEEK\": \"Seek frame\",\n    \"SYLT\": \"Synchronized lyric/text\",\n    \"SYTC\": \"Synchronized tempo codes\",\n    \"TALB\": \"Album/Movie/Show title\",\n    \"TBPM\": \"BPM (beats per minute)\",\n    \"TCOM\": \"Composer\",\n    \"TCON\": \"Content type\",\n    \"TCOP\": \"Copyright message\",\n    \"TDAT\": \"Date\",\n    \"TDLY\": \"Playlist delay\",\n    \"TDRC\": \"Recording time\",\n    \"TDRL\": \"Release time\",\n    \"TDTG\": \"Tagging time\",\n    \"TENC\": \"Encoded by\",\n    \"TEXT\": \"Lyricist/Text writer\",\n    \"TFLT\": \"File type\",\n    \"TIME\": \"Time\",\n    \"TIPL\": \"Involved people list\",\n    \"TIT1\": \"Content group description\",\n    \"TIT2\": \"Title/songname/content description\",\n    \"TIT3\": \"Subtitle/Description refinement\",\n    \"TKEY\": \"Initial key\",\n    \"TLAN\": \"Language(s)\",\n    \"TLEN\": \"Length\",\n    \"TMCL\": \"Musician credits list\",\n    \"TMED\": \"Media type\",\n    \"TMOO\": \"Mood\",\n    \"TOAL\": \"Original album/movie/show title\",\n    \"TOFN\": \"Original filename\",\n    \"TOLY\": \"Original lyricist(s)/text writer(s)\",\n    \"TOPE\": \"Original artist(s)/performer(s)\",\n    \"TORY\": \"Original release year\",\n    \"TOWN\": \"File owner/licensee\",\n    \"TPE1\": \"Lead performer(s)/Soloist(s)\",\n    \"TPE2\": \"Band/orchestra/accompaniment\",\n    \"TPE3\": \"Conductor/performer refinement\",\n    \"TPE4\": \"Interpreted, remixed, or otherwise modified by\",\n    \"TPOS\": \"Part of a set\",\n    \"TPRO\": \"Produced notice\",\n    \"TPUB\": \"Publisher\",\n    \"TRCK\": \"Track number/Position in set\",\n    \"TRDA\": \"Recording dates\",\n    \"TRSN\": \"Internet radio station name\",\n    \"TRSO\": \"Internet radio station owner\",\n    \"TSOA\": \"Album sort order\",\n    \"TSOP\": \"Performer sort order\",\n    \"TSOT\": \"Title sort order\",\n    \"TSIZ\": \"Size\",\n    \"TSRC\": \"ISRC (international standard recording code)\",\n    \"TSSE\": \"Software/Hardware and settings used for encoding\",\n    \"TSST\": \"Set subtitle\",\n    \"TYER\": \"Year\",\n    \"TXXX\": \"User defined text information frame\",\n    \"UFID\": \"Unique file identifier\",\n    \"USER\": \"Terms of use\",\n    \"USLT\": \"Unsychronized lyric/text transcription\",\n    \"WCOM\": \"Commercial information\",\n    \"WCOP\": \"Copyright/Legal information\",\n    \"WOAF\": \"Official audio file webpage\",\n    \"WOAR\": \"Official artist/performer webpage\",\n    \"WOAS\": \"Official audio source webpage\",\n    \"WORS\": \"Official internet radio station homepage\",\n    \"WPAY\": \"Payment\",\n    \"WPUB\": \"Publishers official webpage\",\n    \"WXXX\": \"User defined URL link frame\"\n};\n\nexport default ExtractID3;\n"
  },
  {
    "path": "src/core/operations/ExtractIPAddresses.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search } from \"../lib/Extract.mjs\";\nimport { ipSort } from \"../lib/Sort.mjs\";\n\n/**\n * Extract IP addresses operation\n */\nclass ExtractIPAddresses extends Operation {\n\n    /**\n     * ExtractIPAddresses constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract IP addresses\";\n        this.module = \"Regex\";\n        this.description = \"Extracts all IPv4 and IPv6 addresses.<br><br>Warning: Given a string <code>1.2.3.4.5.6.7.8</code>, this will match <code>1.2.3.4 and 5.6.7.8</code> so always check the original input!\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"IPv4\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"IPv6\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Remove local IPv4 addresses\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Display total\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sort\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Unique\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,\n\n            // IPv4 decimal groups can have values 0 to 255. To construct a regex the following sub-regex is reused:\n            ipv4DecimalByte = \"(?:25[0-5]|2[0-4]\\\\d|1?[0-9]\\\\d|\\\\d)\",\n            ipv4OctalByte = \"(?:0[1-3]?[0-7]{1,2})\",\n\n            // Look behind and ahead will be used to exclude matches with additional decimal digits left and right of IP address\n            lookBehind = \"(?<!\\\\d)\",\n            lookAhead = \"(?!\\\\d)\",\n\n            // Each variant requires exactly 4 groups with literal . between.\n            ipv4Decimal = \"(?:\" + lookBehind + ipv4DecimalByte + \"\\\\.){3}\" + \"(?:\" + ipv4DecimalByte + lookAhead + \")\",\n            ipv4Octal = \"(?:\" + lookBehind + ipv4OctalByte + \"\\\\.){3}\" + \"(?:\" + ipv4OctalByte + lookAhead + \")\",\n\n            // Then we allow IPv4 addresses to be expressed either entirely in decimal or entirely in Octal\n            ipv4 = \"(?:\" + ipv4Decimal + \"|\" + ipv4Octal + \")\",\n            ipv6 = \"((?=.*::)(?!.*::.+::)(::)?([\\\\dA-F]{1,4}:(:|\\\\b)|){5}|([\\\\dA-F]{1,4}:){6})(([\\\\dA-F]{1,4}((?!\\\\3)::|:\\\\b|(?![\\\\dA-F])))|(?!\\\\2\\\\3)){2}\";\n        let ips  = \"\";\n\n        if (includeIpv4 && includeIpv6) {\n            ips = ipv4 + \"|\" + ipv6;\n        } else if (includeIpv4) {\n            ips = ipv4;\n        } else if (includeIpv6) {\n            ips = ipv6;\n        }\n\n        if (!ips) return \"\";\n\n        const regex = new RegExp(ips, \"ig\");\n\n        const ten = \"10\\\\..+\",\n            oneninetwo = \"192\\\\.168\\\\..+\",\n            oneseventwo = \"172\\\\.(?:1[6-9]|2\\\\d|3[01])\\\\..+\",\n            onetwoseven = \"127\\\\..+\",\n            removeRegex = new RegExp(\"^(?:\" + ten + \"|\" + oneninetwo +\n                \"|\" + oneseventwo + \"|\" + onetwoseven + \")\");\n\n        const results = search(\n            input,\n            regex,\n            removeLocal ? removeRegex : null,\n            sort ? ipSort : null,\n            unique\n        );\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default ExtractIPAddresses;\n"
  },
  {
    "path": "src/core/operations/ExtractLSB.mjs",
    "content": "/**\n * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { fromBinary } from \"../lib/Binary.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { Jimp } from \"jimp\";\n\n/**\n * Extract LSB operation\n */\nclass ExtractLSB extends Operation {\n    /**\n     * ExtractLSB constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract LSB\";\n        this.module = \"Image\";\n        this.description =\n            \"Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography.\";\n        this.infoURL =\n            \"https://wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Colour Pattern #1\",\n                type: \"option\",\n                value: COLOUR_OPTIONS,\n            },\n            {\n                name: \"Colour Pattern #2\",\n                type: \"option\",\n                value: [\"\", ...COLOUR_OPTIONS],\n            },\n            {\n                name: \"Colour Pattern #3\",\n                type: \"option\",\n                value: [\"\", ...COLOUR_OPTIONS],\n            },\n            {\n                name: \"Colour Pattern #4\",\n                type: \"option\",\n                value: [\"\", ...COLOUR_OPTIONS],\n            },\n            {\n                name: \"Pixel Order\",\n                type: \"option\",\n                value: [\"Row\", \"Column\"],\n            },\n            {\n                name: \"Bit\",\n                type: \"number\",\n                value: 0,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        if (!isImage(input))\n            throw new OperationError(\"Please enter a valid image file.\");\n\n        const bit = 7 - args.pop(),\n            pixelOrder = args.pop(),\n            colours = args\n                .filter((option) => option !== \"\")\n                .map((option) => COLOUR_OPTIONS.indexOf(option)),\n            parsedImage = await Jimp.read(input),\n            width = parsedImage.bitmap.width,\n            height = parsedImage.bitmap.height,\n            rgba = parsedImage.bitmap.data;\n\n        if (bit < 0 || bit > 7) {\n            throw new OperationError(\n                \"Error: Bit argument must be between 0 and 7\",\n            );\n        }\n\n        let i,\n            combinedBinary = \"\";\n\n        if (pixelOrder === \"Row\") {\n            for (i = 0; i < rgba.length; i += 4) {\n                for (const colour of colours) {\n                    combinedBinary += Utils.bin(rgba[i + colour])[bit];\n                }\n            }\n        } else {\n            let rowWidth;\n            const pixelWidth = width * 4;\n            for (let col = 0; col < width; col++) {\n                for (let row = 0; row < height; row++) {\n                    rowWidth = row * pixelWidth;\n                    for (const colour of colours) {\n                        i = rowWidth + (col + colour * 4);\n                        combinedBinary += Utils.bin(rgba[i])[bit];\n                    }\n                }\n            }\n        }\n\n        return fromBinary(combinedBinary);\n    }\n}\n\nconst COLOUR_OPTIONS = [\"R\", \"G\", \"B\", \"A\"];\n\nexport default ExtractLSB;\n"
  },
  {
    "path": "src/core/operations/ExtractMACAddresses.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search } from \"../lib/Extract.mjs\";\nimport { hexadecimalSort } from \"../lib/Sort.mjs\";\n\n/**\n * Extract MAC addresses operation\n */\nclass ExtractMACAddresses extends Operation {\n\n    /**\n     * ExtractMACAddresses constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract MAC addresses\";\n        this.module = \"Regex\";\n        this.description = \"Extracts all Media Access Control (MAC) addresses from the input.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Display total\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sort\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Unique\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [displayTotal, sort, unique] = args,\n            regex = /[A-F\\d]{2}(?:[:-][A-F\\d]{2}){5}/ig,\n            results = search(\n                input,\n                regex,\n                null,\n                sort ? hexadecimalSort : null,\n                unique\n            );\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default ExtractMACAddresses;\n"
  },
  {
    "path": "src/core/operations/ExtractRGBA.mjs",
    "content": "/**\n * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { Jimp } from \"jimp\";\n\nimport { RGBA_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n/**\n * Extract RGBA operation\n */\nclass ExtractRGBA extends Operation {\n    /**\n     * ExtractRGBA constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract RGBA\";\n        this.module = \"Image\";\n        this.description =\n            \"Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RGBA_color_space\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"editableOption\",\n                value: RGBA_DELIM_OPTIONS,\n            },\n            {\n                name: \"Include Alpha\",\n                type: \"boolean\",\n                value: true,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        if (!isImage(input))\n            throw new OperationError(\"Please enter a valid image file.\");\n\n        const delimiter = args[0],\n            includeAlpha = args[1],\n            parsedImage = await Jimp.read(input);\n\n        let bitmap = parsedImage.bitmap.data;\n        bitmap = includeAlpha ?\n            bitmap :\n            bitmap.filter((val, idx) => idx % 4 !== 3);\n\n        return bitmap.join(delimiter);\n    }\n}\n\nexport default ExtractRGBA;\n"
  },
  {
    "path": "src/core/operations/ExtractURLs.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { search, URL_REGEX } from \"../lib/Extract.mjs\";\nimport { caseInsensitiveSort } from \"../lib/Sort.mjs\";\n\n/**\n * Extract URLs operation\n */\nclass ExtractURLs extends Operation {\n\n    /**\n     * ExtractURLs constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Extract URLs\";\n        this.module = \"Regex\";\n        this.description = \"Extracts Uniform Resource Locators (URLs) from the input. The protocol (http, ftp etc.) is required otherwise there will be far too many false positives.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Display total\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sort\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Unique\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [displayTotal, sort, unique] = args;\n        const results = search(\n            input,\n            URL_REGEX,\n            null,\n            sort ? caseInsensitiveSort : null,\n            unique\n        );\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default ExtractURLs;\n"
  },
  {
    "path": "src/core/operations/FangURL.mjs",
    "content": "/**\n * @author arnydo [github@arnydo.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * FangURL operation\n */\nclass FangURL extends Operation {\n\n    /**\n     * FangURL constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fang URL\";\n        this.module = \"Default\";\n        this.description = \"Takes a 'Defanged' Universal Resource Locator (URL) and 'Fangs' it. Meaning, it removes the alterations (defanged) that render it useless so that it can be used again.\";\n        this.infoURL = \"https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Restore [.]\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Restore hxxp\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Restore ://\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [dots, http, slashes] = args;\n\n        input = fangURL(input, dots, http, slashes);\n\n        return input;\n    }\n\n}\n\n\n/**\n * Defangs a given URL\n *\n * @param {string} url\n * @param {boolean} dots\n * @param {boolean} http\n * @param {boolean} slashes\n * @returns {string}\n */\nfunction fangURL(url, dots, http, slashes) {\n    if (dots) url = url.replace(/\\[\\.\\]/g, \".\");\n    if (http) url = url.replace(/hxxp/g, \"http\");\n    if (slashes) url = url.replace(/\\[:\\/\\/\\]/g, \"://\");\n\n    return url;\n}\n\nexport default FangURL;\n"
  },
  {
    "path": "src/core/operations/FernetDecrypt.mjs",
    "content": "/**\n * @author Karsten Silkenbäumer [github.com/kassi]\n * @copyright Karsten Silkenbäumer 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport fernet from \"fernet\";\n\n/**\n * FernetDecrypt operation\n */\nclass FernetDecrypt extends Operation {\n    /**\n     * FernetDecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fernet Decrypt\";\n        this.module = \"Default\";\n        this.description = \"Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().<br><br><b>Key:</b> The key must be 32 bytes (256 bits) encoded with Base64.\";\n        this.infoURL = \"https://asecuritysite.com/encryption/fer\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n        ];\n        this.patterns = [\n            {\n                match: \"^[A-Z\\\\d\\\\-_=]{20,}$\",\n                flags: \"i\",\n                args: []\n            },\n        ];\n    }\n    /**\n     * @param {String} input\n     * @param {Object[]} args\n     * @returns {String}\n     */\n    run(input, args) {\n        const [secretInput] = args;\n        try {\n            const secret = new fernet.Secret(secretInput);\n            const token = new fernet.Token({\n                secret: secret,\n                token: input,\n                ttl: 0\n            });\n            return token.decode();\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n}\n\nexport default FernetDecrypt;\n"
  },
  {
    "path": "src/core/operations/FernetEncrypt.mjs",
    "content": "/**\n * @author Karsten Silkenbäumer [github.com/kassi]\n * @copyright Karsten Silkenbäumer 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport fernet from \"fernet\";\n\n/**\n * FernetEncrypt operation\n */\nclass FernetEncrypt extends Operation {\n    /**\n     * FernetEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fernet Encrypt\";\n        this.module = \"Default\";\n        this.description = \"Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().<br><br><b>Key:</b> The key must be 32 bytes (256 bits) encoded with Base64.\";\n        this.infoURL = \"https://asecuritysite.com/encryption/fer\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n        ];\n    }\n    /**\n     * @param {String} input\n     * @param {Object[]} args\n     * @returns {String}\n     */\n    run(input, args) {\n        const [secretInput] = args;\n        try {\n            const secret = new fernet.Secret(secretInput);\n            const token = new fernet.Token({\n                secret: secret,\n            });\n            return token.encode(input);\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n}\n\nexport default FernetEncrypt;\n"
  },
  {
    "path": "src/core/operations/FileTree.mjs",
    "content": "/**\n * @author sw5678\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {INPUT_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * Unique operation\n */\nclass FileTree extends Operation {\n\n    /**\n     * Unique constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"File Tree\";\n        this.module = \"Default\";\n        this.description = \"Creates a file tree from a list of file paths (similar to the tree command in Linux)\";\n        this.infoURL = \"https://wikipedia.org/wiki/Tree_(command)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"File Path Delimiter\",\n                type: \"binaryString\",\n                value: \"/\"\n            },\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: INPUT_DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n\n        // Set up arrow and pipe for nice output display\n        const ARROW = \"|---\";\n        const PIPE = \"|   \";\n\n        // Get args from input\n        const fileDelim = args[0];\n        const entryDelim = Utils.charRep(args[1]);\n\n        // Store path to print\n        const completedList = [];\n        const printList = [];\n\n        // Loop through all entries\n        const filePaths = input.split(entryDelim).unique().sort();\n        for (let i = 0; i < filePaths.length; i++) {\n            // Split by file delimiter\n            let path = filePaths[i].split(fileDelim);\n\n            if (path[0] === \"\") {\n                path = path.slice(1, path.length);\n            }\n\n            for (let j = 0; j < path.length; j++) {\n                let printLine;\n                let key;\n                if (j === 0) {\n                    printLine = path[j];\n                    key = path[j];\n                } else {\n                    printLine = PIPE.repeat(j-1) + ARROW + path[j];\n                    key = path.slice(0, j+1).join(\"/\");\n                }\n\n                // Check to see we have already added that path\n                if (!completedList.includes(key)) {\n                    completedList.push(key);\n                    printList.push(printLine);\n                }\n            }\n        }\n        return printList.join(\"\\n\");\n    }\n\n}\n\nexport default FileTree;\n"
  },
  {
    "path": "src/core/operations/Filter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {INPUT_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport XRegExp from \"xregexp\";\n\n/**\n * Filter operation\n */\nclass Filter extends Operation {\n\n    /**\n     * Filter constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Filter\";\n        this.module = \"Regex\";\n        this.description = \"Splits up the input using the specified delimiter and then filters each branch based on a regular expression.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": INPUT_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Regex\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Invert condition\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0]),\n            reverse = args[2];\n        let regex;\n\n        try {\n            regex = new XRegExp(args[1]);\n        } catch (err) {\n            throw new OperationError(`Invalid regex. Details: ${err.message}`);\n        }\n\n        const regexFilter = function(value) {\n            return reverse ^ regex.test(value);\n        };\n\n        return input.split(delim).filter(regexFilter).join(delim);\n    }\n\n}\n\nexport default Filter;\n"
  },
  {
    "path": "src/core/operations/FindReplace.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport XRegExp from \"xregexp\";\n\n/**\n * Find / Replace operation\n */\nclass FindReplace extends Operation {\n\n    /**\n     * FindReplace constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Find / Replace\";\n        this.module = \"Regex\";\n        this.description = \"Replaces all occurrences of the first string with the second.<br><br>Includes support for regular expressions (regex), simple strings and extended strings (which support \\\\n, \\\\r, \\\\t, \\\\b, \\\\f and escaped hex bytes using \\\\x notation, e.g. \\\\x00 for a null byte).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Regular_expression\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Find\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Regex\", \"Extended (\\\\n, \\\\t, \\\\x...)\", \"Simple string\"]\n            },\n            {\n                \"name\": \"Replace\",\n                \"type\": \"binaryString\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Global match\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Case insensitive\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Multiline matching\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Dot matches all\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [{option: type}, replace, g, i, m, s] = args;\n        let find = args[0].string,\n            modifiers = \"\";\n\n        if (g) modifiers += \"g\";\n        if (i) modifiers += \"i\";\n        if (m) modifiers += \"m\";\n        if (s) modifiers += \"s\";\n\n        if (type === \"Regex\") {\n            find = new XRegExp(find, modifiers);\n            return input.replace(find, replace);\n        }\n\n        if (type.indexOf(\"Extended\") === 0) {\n            find = Utils.parseEscapedChars(find);\n        }\n\n        find = new XRegExp(Utils.escapeRegex(find), modifiers);\n\n        return input.replace(find, replace);\n    }\n\n}\n\nexport default FindReplace;\n"
  },
  {
    "path": "src/core/operations/FlaskSessionDecode.mjs",
    "content": "/**\n * @author ThePlayer372-FR []\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { fromBase64 } from \"../lib/Base64.mjs\";\n\n/**\n * Flask Session Decode operation\n */\nclass FlaskSessionDecode extends Operation {\n    /**\n     * FlaskSessionDecode constructor\n    */\n    constructor() {\n        super();\n\n        this.name = \"Flask Session Decode\";\n        this.module = \"Crypto\";\n        this.description = \"Decodes the payload of a Flask session cookie (itsdangerous) into JSON.\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.args = [\n            {\n                name: \"View TimeStamp\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {Object[]}\n     */\n    run(input, args) {\n        input = input.trim();\n        const parts = input.split(\".\");\n        if (parts.length !== 3) {\n            throw new OperationError(\"Invalid Flask token format. Expected payload.timestamp.signature\");\n        }\n\n        const payloadB64 = parts[0];\n        const time = parts[1];\n\n        const timeB64 = time.replace(/-/g, \"+\").replace(/_/g, \"/\");\n        const binary = fromBase64(timeB64);\n        const bytes = new Uint8Array(4);\n        for (let i = 0; i < 4; i++) {\n            bytes[i] = binary.charCodeAt(i);\n        }\n        const view = new DataView(bytes.buffer);\n        const timestamp = view.getInt32(0, false);\n\n        const base64 = payloadB64.replace(/-/g, \"+\").replace(/_/g, \"/\");\n        const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, \"=\");\n        let payloadJson;\n        try {\n            payloadJson = fromBase64(padded);\n        } catch (e) {\n            throw new OperationError(\"Invalid Base64 payload\");\n        }\n\n        try {\n            let data = JSON.parse(payloadJson);\n\n            if (args[0]) {\n                data = {payload: data, timestamp: timestamp};\n            }\n            return data;\n        } catch (e) {\n            throw new OperationError(\"Unable to decode JSON payload: \" + e.message);\n        }\n    }\n}\n\nexport default FlaskSessionDecode;\n"
  },
  {
    "path": "src/core/operations/FlaskSessionSign.mjs",
    "content": "/**\n * @author ThePlayer372-FR []\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport CryptoApi from \"crypto-api/src/crypto-api.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Flask Session Sign operation\n */\nclass FlaskSessionSign extends Operation {\n    /**\n     * FlaskSessionSign constructor\n    */\n    constructor() {\n        super();\n\n        this.name = \"Flask Session Sign\";\n        this.module = \"Crypto\";\n        this.description = \"Signs a JSON payload to produce a Flask session cookie (itsdangerous HMAC).\";\n        this.inputType = \"JSON\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"Decimal\", \"Binary\", \"Base64\", \"UTF8\", \"Latin1\"]\n            },\n            {\n                name: \"Salt\",\n                type: \"toggleString\",\n                value: \"cookie-session\",\n                toggleValues: [\"UTF8\", \"Hex\", \"Decimal\", \"Binary\", \"Base64\", \"Latin1\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"option\",\n                value: [\"sha1\", \"sha256\"],\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!args[0].string) {\n            throw new OperationError(\"Secret key required\");\n        }\n        const key = Utils.convertToByteString(args[0].string, args[0].option);\n        const salt = Utils.convertToByteString(args[1].string || \"cookie-session\", args[1].option);\n        const algorithm = args[2] || \"sha1\";\n\n        const payloadB64 = toBase64(Utils.strToByteArray(JSON.stringify(input)));\n        const payload = payloadB64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n\n        const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));\n        derivedKey.update(salt);\n\n        const currentTimeStamp = Math.ceil(Date.now() / 1000);\n        const buffer = new ArrayBuffer(4);\n        const view = new DataView(buffer);\n        view.setInt32(0, currentTimeStamp, false);\n        const bytes = new Uint8Array(buffer);\n        let binary = \"\";\n        bytes.forEach(b => binary += String.fromCharCode(b));\n        const timeB64 = toBase64(Utils.strToByteArray(binary));\n        const time = timeB64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n\n        const data = Utils.convertToByteString(payload + \".\" + time, \"utf8\");\n        const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));\n        sign.update(data);\n\n        const signB64 = toBase64(sign.finalize());\n        const sign64 = signB64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n\n        return payload + \".\" + time + \".\" + sign64;\n    }\n}\n\n\nexport default FlaskSessionSign;\n"
  },
  {
    "path": "src/core/operations/FlaskSessionVerify.mjs",
    "content": "/**\n * @author ThePlayer372-FR []\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport CryptoApi from \"crypto-api/src/crypto-api.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toBase64, fromBase64 } from \"../lib/Base64.mjs\";\n\n/**\n * Flask Session Verify operation\n */\nclass FlaskSessionVerify extends Operation {\n    /**\n     * FlaskSessionVerify constructor\n    */\n    constructor() {\n        super();\n\n        this.name = \"Flask Session Verify\";\n        this.module = \"Crypto\";\n        this.description = \"Verifies the HMAC signature of a Flask session cookie (itsdangerous) generated.\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"Decimal\", \"Binary\", \"Base64\", \"UTF8\", \"Latin1\"]\n            },\n            {\n                name: \"Salt\",\n                type: \"toggleString\",\n                value: \"cookie-session\",\n                toggleValues: [\"UTF8\", \"Hex\", \"Decimal\", \"Binary\", \"Base64\", \"Latin1\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"option\",\n                value: [\"sha1\", \"sha256\"],\n            },\n            {\n                name: \"View TimeStamp\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n\n        if (!args[0].string) {\n            throw new OperationError(\"Secret key required\");\n        }\n\n        const key = Utils.convertToByteString(args[0].string, args[0].option);\n        const salt = Utils.convertToByteString(args[1].string || \"cookie-session\", args[1].option);\n        const algorithm = args[2] || \"sha1\";\n\n        input = input.trim();\n\n        const parts = input.split(\".\");\n\n        if (parts.length !== 3) {\n            throw new OperationError(\"Invalid Flask token format. Expected payload.timestamp.signature\");\n        }\n\n        const data = Utils.convertToByteString(parts[0] + \".\" + parts[1], \"utf8\");\n\n\n        const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));\n        derivedKey.update(salt);\n\n        const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));\n        sign.update(data);\n\n        const payloadB64 = parts[0];\n        const base64 = payloadB64.replace(/-/g, \"+\").replace(/_/g, \"/\");\n        const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, \"=\");\n\n        const time = parts[1];\n\n        const timeB64 = time.replace(/-/g, \"+\").replace(/_/g, \"/\");\n        const binary = fromBase64(timeB64);\n        const bytes = new Uint8Array(4);\n        for (let i = 0; i < 4; i++) {\n            bytes[i] = binary.charCodeAt(i);\n        }\n        const view = new DataView(bytes.buffer);\n        const timestamp = view.getInt32(0, false);\n\n        let payloadJson;\n        try {\n            payloadJson = fromBase64(padded);\n        } catch (e) {\n            throw new OperationError(\"Invalid Base64 payload\");\n        }\n\n        const signB64 = toBase64(sign.finalize());\n        const sign64 = signB64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n\n        if (sign64 !== parts[2]) {\n            throw new OperationError(\"Invalid signature!\");\n        }\n\n        try {\n            const decoded = JSON.parse(payloadJson);\n            if (!args[3]) {\n                return {\n                    valid: true,\n                    payload: decoded,\n                };\n            } else {\n                return {\n                    valid: true,\n                    payload: decoded,\n                    timestamp: timestamp\n                };\n            }\n        } catch (e) {\n            throw new OperationError(\"Unable to decode JSON payload: \" + e.message);\n        }\n\n    }\n}\n\n\nexport default FlaskSessionVerify;\n"
  },
  {
    "path": "src/core/operations/Fletcher16Checksum.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Fletcher-16 Checksum operation\n */\nclass Fletcher16Checksum extends Operation {\n\n    /**\n     * Fletcher16Checksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fletcher-16 Checksum\";\n        this.module = \"Crypto\";\n        this.description = \"The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-16\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let a = 0,\n            b = 0;\n        input = new Uint8Array(input);\n\n        for (let i = 0; i < input.length; i++) {\n            a = (a + input[i]) % 0xff;\n            b = (b + a) % 0xff;\n        }\n\n        return Utils.hex(((b << 8) | a) >>> 0, 4);\n    }\n\n}\n\nexport default Fletcher16Checksum;\n"
  },
  {
    "path": "src/core/operations/Fletcher32Checksum.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Fletcher-32 Checksum operation\n */\nclass Fletcher32Checksum extends Operation {\n\n    /**\n     * Fletcher32Checksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fletcher-32 Checksum\";\n        this.module = \"Crypto\";\n        this.description = \"The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-32\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let a = 0,\n            b = 0;\n        if (ArrayBuffer.isView(input)) {\n            input = new DataView(input.buffer, input.byteOffset, input.byteLength);\n        } else {\n            input = new DataView(input);\n        }\n\n        for (let i = 0; i < input.byteLength - 1; i += 2) {\n            a = (a + input.getUint16(i, true)) % 0xffff;\n            b = (b + a) % 0xffff;\n        }\n        if (input.byteLength % 2 !== 0) {\n            a = (a + input.getUint8(input.byteLength - 1)) % 0xffff;\n            b = (b + a) % 0xffff;\n        }\n\n        return Utils.hex(((b << 16) | a) >>> 0, 8);\n    }\n\n}\n\nexport default Fletcher32Checksum;\n"
  },
  {
    "path": "src/core/operations/Fletcher64Checksum.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Fletcher-64 Checksum operation\n */\nclass Fletcher64Checksum extends Operation {\n\n    /**\n     * Fletcher64Checksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fletcher-64 Checksum\";\n        this.module = \"Crypto\";\n        this.description = \"The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-64\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let a = 0,\n            b = 0;\n        if (ArrayBuffer.isView(input)) {\n            input = new DataView(input.buffer, input.byteOffset, input.byteLength);\n        } else {\n            input = new DataView(input);\n        }\n\n        for (let i = 0; i < input.byteLength - 3; i += 4) {\n            a = (a + input.getUint32(i, true)) % 0xffffffff;\n            b = (b + a) % 0xffffffff;\n        }\n        if (input.byteLength % 4 !== 0) {\n            let lastValue = 0;\n            for (let i = 0; i < input.byteLength % 4; i++) {\n                lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i);\n            }\n            a = (a + lastValue) % 0xffffffff;\n            b = (b + a) % 0xffffffff;\n        }\n\n        return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8);\n    }\n\n}\n\nexport default Fletcher64Checksum;\n"
  },
  {
    "path": "src/core/operations/Fletcher8Checksum.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Fletcher-8 Checksum operation\n */\nclass Fletcher8Checksum extends Operation {\n\n    /**\n     * Fletcher8Checksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fletcher-8 Checksum\";\n        this.module = \"Crypto\";\n        this.description = \"The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Fletcher%27s_checksum\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let a = 0,\n            b = 0;\n        input = new Uint8Array(input);\n\n        for (let i = 0; i < input.length; i++) {\n            a = (a + input[i]) % 0xf;\n            b = (b + a) % 0xf;\n        }\n\n        return Utils.hex(((b << 4) | a) >>> 0, 2);\n    }\n\n}\n\nexport default Fletcher8Checksum;\n"
  },
  {
    "path": "src/core/operations/FlipImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Flip Image operation\n */\nclass FlipImage extends Operation {\n    /**\n     * FlipImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Flip Image\";\n        this.module = \"Image\";\n        this.description = \"Flips an image along its X or Y axis.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Axis\",\n                type: \"option\",\n                value: [\"Horizontal\", \"Vertical\"],\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [flipAxis] = args;\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid input file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Flipping image...\");\n            switch (flipAxis) {\n                case \"Horizontal\":\n                    image.flip({\n                        horizontal: true,\n                        vertical: false,\n                    });\n                    break;\n                case \"Vertical\":\n                    image.flip({\n                        horizontal: false,\n                        vertical: true,\n                    });\n                    break;\n            }\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error flipping image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the flipped image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default FlipImage;\n"
  },
  {
    "path": "src/core/operations/Fork.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Recipe from \"../Recipe.mjs\";\nimport Dish from \"../Dish.mjs\";\n\n/**\n * Fork operation\n */\nclass Fork extends Operation {\n\n    /**\n     * Fork constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fork\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"Split the input data up based on the specified delimiter and run all subsequent operations on each branch separately.<br><br>For example, to decode multiple Base64 strings, enter them all on separate lines then add the 'Fork' and 'From Base64' operations to the recipe. Each string will be decoded separately.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Split delimiter\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\n\"\n            },\n            {\n                \"name\": \"Merge delimiter\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\n\"\n            },\n            {\n                \"name\": \"Ignore errors\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @returns {Object} The updated state of the recipe.\n     */\n    async run(state) {\n        const opList     = state.opList,\n            inputType    = opList[state.progress].inputType,\n            outputType   = opList[state.progress].outputType,\n            input        = await state.dish.get(inputType),\n            ings         = opList[state.progress].ingValues,\n            [splitDelim, mergeDelim, ignoreErrors] = ings,\n            subOpList    = [];\n        let inputs       = [],\n            i;\n\n        if (input)\n            inputs = input.split(splitDelim);\n\n        // Set to 1 as if we are here, then there is one, the current one.\n        let numOp = 1;\n        // Create subOpList for each tranche to operate on\n        // all remaining operations unless we encounter a Merge\n        for (i = state.progress + 1; i < opList.length; i++) {\n            if (opList[i].name === \"Merge\" && !opList[i].disabled) {\n                numOp--;\n                if (numOp === 0 || opList[i].ingValues[0])\n                    break;\n                else\n                    // Not this Fork's Merge.\n                    subOpList.push(opList[i]);\n            } else {\n                if (opList[i].name === \"Fork\" || opList[i].name === \"Subsection\")\n                    numOp++;\n                subOpList.push(opList[i]);\n            }\n        }\n\n        const recipe = new Recipe();\n        const outputs = [];\n        let progress = 0;\n\n        state.forkOffset += state.progress + 1;\n\n        recipe.addOperations(subOpList);\n\n        // Take a deep(ish) copy of the ingredient values\n        const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));\n\n        // Run recipe over each tranche\n        for (i = 0; i < inputs.length; i++) {\n            // Baseline ing values for each tranche so that registers are reset\n            recipe.opList.forEach((op, i) => {\n                op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));\n            });\n\n            const dish = new Dish();\n            dish.set(inputs[i], inputType);\n\n            try {\n                progress = await recipe.execute(dish, 0, state);\n            } catch (err) {\n                if (!ignoreErrors) {\n                    throw err;\n                }\n                progress = err.progress + 1;\n            }\n            outputs.push(await dish.get(outputType));\n        }\n\n        state.dish.set(outputs.join(mergeDelim), outputType);\n        state.progress += progress;\n        return state;\n    }\n\n}\n\nexport default Fork;\n"
  },
  {
    "path": "src/core/operations/FormatMACAddresses.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Format MAC addresses operation\n */\nclass FormatMACAddresses extends Operation {\n\n    /**\n     * FormatMACAddresses constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Format MAC addresses\";\n        this.module = \"Default\";\n        this.description = \"Displays given MAC addresses in multiple different formats.<br><br>Expects addresses in a list separated by newlines, spaces or commas.<br><br>WARNING: There are no validity checks.\";\n        this.infoURL = \"https://wikipedia.org/wiki/MAC_address#Notational_conventions\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Output case\",\n                \"type\": \"option\",\n                \"value\": [\"Both\", \"Upper only\", \"Lower only\"]\n            },\n            {\n                \"name\": \"No delimiter\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Dash delimiter\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Colon delimiter\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Cisco style\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"IPv6 interface ID\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input) return \"\";\n\n        const [\n                outputCase,\n                noDelim,\n                dashDelim,\n                colonDelim,\n                ciscoStyle,\n                ipv6IntID\n            ] = args,\n            outputList = [],\n            macs = input.toLowerCase().split(/[,\\s\\r\\n]+/);\n\n        macs.forEach(function(mac) {\n            const cleanMac = mac.replace(/[:.-]+/g, \"\"),\n                macHyphen = cleanMac.replace(/(.{2}(?=.))/g, \"$1-\"),\n                macColon = cleanMac.replace(/(.{2}(?=.))/g, \"$1:\"),\n                macCisco = cleanMac.replace(/(.{4}(?=.))/g, \"$1.\");\n            let macIPv6 = cleanMac.slice(0, 6) + \"fffe\" + cleanMac.slice(6);\n\n            macIPv6 = macIPv6.replace(/(.{4}(?=.))/g, \"$1:\");\n            let bite = parseInt(macIPv6.slice(0, 2), 16) ^ 2;\n            bite = bite.toString(16).padStart(2, \"0\");\n            macIPv6 = bite + macIPv6.slice(2);\n\n            if (outputCase === \"Lower only\") {\n                if (noDelim) outputList.push(cleanMac);\n                if (dashDelim) outputList.push(macHyphen);\n                if (colonDelim) outputList.push(macColon);\n                if (ciscoStyle) outputList.push(macCisco);\n                if (ipv6IntID) outputList.push(macIPv6);\n            } else if (outputCase === \"Upper only\") {\n                if (noDelim) outputList.push(cleanMac.toUpperCase());\n                if (dashDelim) outputList.push(macHyphen.toUpperCase());\n                if (colonDelim) outputList.push(macColon.toUpperCase());\n                if (ciscoStyle) outputList.push(macCisco.toUpperCase());\n                if (ipv6IntID) outputList.push(macIPv6.toUpperCase());\n            } else {\n                if (noDelim) outputList.push(cleanMac, cleanMac.toUpperCase());\n                if (dashDelim) outputList.push(macHyphen, macHyphen.toUpperCase());\n                if (colonDelim) outputList.push(macColon, macColon.toUpperCase());\n                if (ciscoStyle) outputList.push(macCisco, macCisco.toUpperCase());\n                if (ipv6IntID) outputList.push(macIPv6, macIPv6.toUpperCase());\n            }\n\n            outputList.push(\n                \"\" // Empty line to delimit groups\n            );\n        });\n\n        // Return the data as a string\n        return outputList.join(\"\\n\");\n    }\n\n}\n\nexport default FormatMACAddresses;\n"
  },
  {
    "path": "src/core/operations/FrequencyDistribution.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Frequency distribution operation\n */\nclass FrequencyDistribution extends Operation {\n\n    /**\n     * FrequencyDistribution constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Frequency distribution\";\n        this.module = \"Default\";\n        this.description = \"Displays the distribution of bytes in the data as a graph.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Frequency_distribution\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"json\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Show 0%s\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Show ASCII\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {json}\n     */\n    run(input, args) {\n        const data = new Uint8Array(input);\n        if (!data.length) throw new OperationError(\"No data\");\n\n        const distrib = new Array(256).fill(0),\n            percentages = new Array(256),\n            len = data.length;\n        let i;\n\n        // Count bytes\n        for (i = 0; i < len; i++) {\n            distrib[data[i]]++;\n        }\n\n        // Calculate percentages\n        let repr = 0;\n        for (i = 0; i < 256; i++) {\n            if (distrib[i] > 0) repr++;\n            percentages[i] = distrib[i] / len * 100;\n        }\n\n        return {\n            \"dataLength\": len,\n            \"percentages\": percentages,\n            \"distribution\": distrib,\n            \"bytesRepresented\": repr\n        };\n    }\n\n    /**\n     * Displays the frequency distribution as a bar chart for web apps.\n     *\n     * @param {json} freq\n     * @returns {html}\n     */\n    present(freq, args) {\n        const [showZeroes, showAscii] = args;\n\n        // Print\n        let output = `<canvas id='chart-area'></canvas><br>\nTotal data length: ${freq.dataLength}\nNumber of bytes represented: ${freq.bytesRepresented}\nNumber of bytes not represented: ${256 - freq.bytesRepresented}\n\n<script>\n    var canvas = document.getElementById(\"chart-area\"),\n        parentRect = canvas.closest(\".cm-scroller\").getBoundingClientRect(),\n        scores = ${JSON.stringify(freq.percentages)};\n\n    canvas.width = parentRect.width * 0.95;\n    canvas.height = parentRect.height * 0.9;\n\n    CanvasComponents.drawBarChart(canvas, scores, \"Byte\", \"Frequency %\", 16, 6);\n</script>\n<table class=\"table table-hover table-sm\">\n    <tr><th>Byte</th>${showAscii ? \"<th>ASCII</th>\" : \"\"}<th>Percentage</th><th></th></tr>`;\n\n        for (let i = 0; i < 256; i++) {\n            if (freq.distribution[i] || showZeroes) {\n                let c = \"\";\n                if (showAscii) {\n                    if (i <= 32) {\n                        c = String.fromCharCode(0x2400 + i);\n                    } else  if (i === 127) {\n                        c = String.fromCharCode(0x2421);\n                    } else {\n                        c = String.fromCharCode(i);\n                    }\n                }\n                const bite = `<td>${Utils.hex(i, 2)}</td>`,\n                    ascii = showAscii ? `<td>${c}</td>` : \"\",\n                    percentage = `<td>${(freq.percentages[i].toFixed(2).replace(\".00\", \"\") + \"%\").padEnd(8, \" \")}</td>`,\n                    bars = `<td>${Array(Math.ceil(freq.percentages[i])+1).join(\"|\")}</td>`;\n\n                output += `<tr>${bite}${ascii}${percentage}${bars}</tr>`;\n            }\n        }\n\n        output += \"</table>\";\n        return output;\n    }\n\n}\n\nexport default FrequencyDistribution;\n"
  },
  {
    "path": "src/core/operations/FromBCD.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from \"../lib/BCD.mjs\";\nimport BigNumber from \"bignumber.js\";\n\n/**\n * From BCD operation\n */\nclass FromBCD extends Operation {\n\n    /**\n     * FromBCD constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From BCD\";\n        this.module = \"Default\";\n        this.description = \"Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Binary-coded_decimal\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Scheme\",\n                \"type\": \"option\",\n                \"value\": ENCODING_SCHEME\n            },\n            {\n                \"name\": \"Packed\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Signed\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": FORMAT\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:\\\\d{4} ){3,}\\\\d{4}$\",\n                flags: \"\",\n                args: [\"8 4 2 1\", true, false, \"Nibbles\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const encoding = ENCODING_LOOKUP[args[0]],\n            packed = args[1],\n            signed = args[2],\n            inputFormat = args[3],\n            nibbles = [];\n\n        let output = \"\",\n            byteArray;\n\n        // Normalise the input\n        switch (inputFormat) {\n            case \"Nibbles\":\n            case \"Bytes\":\n                input = input.replace(/\\s/g, \"\");\n                for (let i = 0; i < input.length; i += 4) {\n                    nibbles.push(parseInt(input.substr(i, 4), 2));\n                }\n                break;\n            case \"Raw\":\n            default:\n                byteArray = new Uint8Array(Utils.strToArrayBuffer(input));\n                byteArray.forEach(b => {\n                    nibbles.push(b >>> 4);\n                    nibbles.push(b & 15);\n                });\n                break;\n        }\n\n        if (!packed) {\n            // Discard each high nibble\n            for (let i = 0; i < nibbles.length; i++) {\n                nibbles.splice(i, 1); // lgtm [js/loop-iteration-skipped-due-to-shifting]\n            }\n        }\n\n        if (signed) {\n            const sign = nibbles.pop();\n            if (sign === 13 ||\n                sign === 11) {\n                // Negative\n                output += \"-\";\n            }\n        }\n\n        nibbles.forEach(n => {\n            if (isNaN(n)) throw new OperationError(\"Invalid input\");\n            const val = encoding.indexOf(n);\n            if (val < 0) throw new OperationError(`Value ${Utils.bin(n, 4)} is not in the encoding scheme`);\n            output += val.toString();\n        });\n\n        return new BigNumber(output);\n    }\n\n}\n\nexport default FromBCD;\n"
  },
  {
    "path": "src/core/operations/FromBase.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport BigNumber from \"bignumber.js\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * From Base operation\n */\nclass FromBase extends Operation {\n\n    /**\n     * FromBase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base\";\n        this.module = \"Default\";\n        this.description = \"Converts a number to decimal from a given numerical base.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Radix\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Radix\",\n                \"type\": \"number\",\n                \"value\": 36\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const radix = args[0];\n        if (radix < 2 || radix > 36) {\n            throw new OperationError(\"Error: Radix argument must be between 2 and 36\");\n        }\n\n        const number = input.replace(/\\s/g, \"\").split(\".\");\n        let result = new BigNumber(number[0], radix);\n\n        if (number.length === 1) return result;\n\n        // Fractional part\n        for (let i = 0; i < number[1].length; i++) {\n            const digit = new BigNumber(number[1][i], radix);\n            result += digit.div(Math.pow(radix, i+1));\n        }\n\n        return result;\n    }\n\n}\n\nexport default FromBase;\n"
  },
  {
    "path": "src/core/operations/FromBase32.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {ALPHABET_OPTIONS} from \"../lib/Base32.mjs\";\n\n\n/**\n * From Base32 operation\n */\nclass FromBase32 extends Operation {\n\n    /**\n     * FromBase32 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base32\";\n        this.module = \"Default\";\n        this.description = \"Base32 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. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Base32\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"editableOption\",\n                value: ALPHABET_OPTIONS\n            },\n            {\n                name: \"Remove non-alphabet chars\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:[A-Z2-7]{8})+(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}={1})?$\",\n                flags: \"\",\n                args: [\"A-Z2-7=\", false]\n            },\n            {\n                pattern: \"^(?:[0-9A-V]{8})+(?:[0-9A-V]{2}={6}|[0-9A-V]{4}={4}|[0-9A-V]{5}={3}|[0-9A-V]{7}={1})?$\",\n                flags: \"\",\n                args: [\"0-9A-V=\", false]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        if (!input) return [];\n\n        const alphabet = args[0] ?\n                Utils.expandAlphRange(args[0]).join(\"\") : \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=\",\n            removeNonAlphChars = args[1],\n            output = [];\n\n        let chr1, chr2, chr3, chr4, chr5,\n            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,\n            i = 0;\n\n        if (removeNonAlphChars) {\n            const re = new RegExp(\"[^\" + alphabet.replace(/[\\]\\\\\\-^]/g, \"\\\\$&\") + \"]\", \"g\");\n            input = input.replace(re, \"\");\n        }\n\n        while (i < input.length) {\n            enc1 = alphabet.indexOf(input.charAt(i++));\n            enc2 = alphabet.indexOf(input.charAt(i++) || \"=\");\n            enc3 = alphabet.indexOf(input.charAt(i++) || \"=\");\n            enc4 = alphabet.indexOf(input.charAt(i++) || \"=\");\n            enc5 = alphabet.indexOf(input.charAt(i++) || \"=\");\n            enc6 = alphabet.indexOf(input.charAt(i++) || \"=\");\n            enc7 = alphabet.indexOf(input.charAt(i++) || \"=\");\n            enc8 = alphabet.indexOf(input.charAt(i++) || \"=\");\n\n            chr1 = (enc1 << 3) | (enc2 >> 2);\n            chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4);\n            chr3 = ((enc4 & 15) << 4) | (enc5 >> 1);\n            chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3);\n            chr5 = ((enc7 & 7) << 5) | enc8;\n\n            output.push(chr1);\n            if ((enc2 & 3) !== 0 || enc3 !== 32) output.push(chr2);\n            if ((enc4 & 15) !== 0 || enc5 !== 32) output.push(chr3);\n            if ((enc5 & 1) !== 0 || enc6 !== 32) output.push(chr4);\n            if ((enc7 & 7) !== 0 || enc8 !== 32) output.push(chr5);\n        }\n\n        return output;\n    }\n\n}\n\nexport default FromBase32;\n\n"
  },
  {
    "path": "src/core/operations/FromBase45.mjs",
    "content": "/**\n * @author Thomas Weißschuh [thomas@t-8ch.de]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport {ALPHABET, highlightToBase45, highlightFromBase45} from \"../lib/Base45.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n\n/**\n * From Base45 operation\n */\nclass FromBase45 extends Operation {\n\n    /**\n     * FromBase45 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base45\";\n        this.module = \"Default\";\n        this.description = \"Base45 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. The high number base results in shorter strings than with the decimal or hexadecimal system. Base45 is optimized for usage with QR codes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_numeral_systems\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"string\",\n                value: ALPHABET\n            },\n            {\n                name: \"Remove non-alphabet chars\",\n                type: \"boolean\",\n                value: true\n            },\n        ];\n\n        this.highlight = highlightFromBase45;\n        this.highlightReverse = highlightToBase45;\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        if (!input) return [];\n        const alphabet = Utils.expandAlphRange(args[0]).join(\"\");\n        const removeNonAlphChars = args[1];\n\n        const res = [];\n\n        // Remove non-alphabet characters\n        if (removeNonAlphChars) {\n            const re = new RegExp(\"[^\" + alphabet.replace(/[[\\]\\\\\\-^$]/g, \"\\\\$&\") + \"]\", \"g\");\n            input = input.replace(re, \"\");\n        }\n\n        for (const triple of Utils.chunked(input, 3)) {\n            triple.reverse();\n            let b = 0;\n            for (const c of triple) {\n                const idx = alphabet.indexOf(c);\n                if (idx === -1) {\n                    throw new OperationError(`Character not in alphabet: '${c}'`);\n                }\n                b *= 45;\n                b += idx;\n            }\n\n            if (b > 65535) {\n                throw new OperationError(`Triplet too large: '${triple.join(\"\")}'`);\n            }\n\n            if (triple.length > 2) {\n                /**\n                 * The last triple may only have 2 bytes so we push the MSB when we got 3 bytes\n                 * Pushing MSB\n                 */\n                res.push(b >> 8);\n            }\n\n            /**\n             * Pushing LSB\n             */\n            res.push(b & 0xff);\n\n        }\n\n        return res;\n    }\n\n}\n\nexport default FromBase45;\n"
  },
  {
    "path": "src/core/operations/FromBase58.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {ALPHABET_OPTIONS} from \"../lib/Base58.mjs\";\n\n/**\n * From Base58 operation\n */\nclass FromBase58 extends Operation {\n\n    /**\n     * FromBase58 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base58\";\n        this.module = \"Default\";\n        this.description = \"Base58 (similar to Base64) is a notation for encoding arbitrary byte data. It differs from Base64 by removing easily misread characters (i.e. l, I, 0 and O) to improve human readability.<br><br>This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included) back into its raw form.<br><br>e.g. <code>StV1DL6CwTryKyV</code> becomes <code>hello world</code><br><br>Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Base58\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Alphabet\",\n                \"type\": \"editableOption\",\n                \"value\": ALPHABET_OPTIONS\n            },\n            {\n                \"name\": \"Remove non-alphabet chars\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^[1-9A-HJ-NP-Za-km-z]{20,}$\",\n                flags: \"\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\", false]\n            },\n            {\n                pattern: \"^[1-9A-HJ-NP-Za-km-z]{20,}$\",\n                flags: \"\",\n                args: [\"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz\", false]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        let alphabet = args[0] || ALPHABET_OPTIONS[0].value;\n        const removeNonAlphaChars = args[1] === undefined ? true : args[1],\n            result = [];\n\n        alphabet = Utils.expandAlphRange(alphabet).join(\"\");\n\n        if (alphabet.length !== 58 ||\n            [].unique.call(alphabet).length !== 58) {\n            throw new OperationError(\"Alphabet must be of length 58\");\n        }\n\n        if (input.length === 0) return [];\n\n        let zeroPrefix = 0;\n        for (let i = 0; i < input.length && input[i] === alphabet[0]; i++) {\n            zeroPrefix++;\n        }\n\n        [].forEach.call(input, function(c, charIndex) {\n            const index = alphabet.indexOf(c);\n\n            if (index === -1) {\n                if (removeNonAlphaChars) {\n                    return;\n                } else {\n                    throw new OperationError(`Char '${c}' at position ${charIndex} not in alphabet`);\n                }\n            }\n\n            let carry = index;\n\n            for (let i = 0; i < result.length; i++) {\n                carry += result[i] * 58;\n                result[i] = carry & 0xFF;\n                carry = carry >> 8;\n            }\n\n            while (carry > 0) {\n                result.push(carry & 0xFF);\n                carry = carry >> 8;\n            }\n        });\n\n        while (zeroPrefix--) {\n            result.push(0);\n        }\n\n        return result.reverse();\n    }\n\n}\n\nexport default FromBase58;\n"
  },
  {
    "path": "src/core/operations/FromBase62.mjs",
    "content": "/**\n * @author tcode2k16 [tcode2k16@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport BigNumber from \"bignumber.js\";\nimport Utils from \"../Utils.mjs\";\n\n\n/**\n * From Base62 operation\n */\nclass FromBase62 extends Operation {\n\n    /**\n     * FromBase62 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base62\";\n        this.module = \"Default\";\n        this.description = \"Base62 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. The high number base results in shorter strings than with the decimal or hexadecimal system.\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_numeral_systems\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"string\",\n                value: \"0-9A-Za-z\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        if (input.length < 1) return [];\n        const alphabet = Utils.expandAlphRange(args[0]).join(\"\");\n        const BN62 = BigNumber.clone({ ALPHABET: alphabet });\n\n        const re = new RegExp(\"[^\" + alphabet.replace(/[[\\]\\\\\\-^$]/g, \"\\\\$&\") + \"]\", \"g\");\n        input = input.replace(re, \"\");\n\n        // Read number in using Base62 alphabet\n        const number = new BN62(input, 62);\n        // Copy to new BigNumber object that uses the default alphabet\n        const normalized = new BigNumber(number);\n\n        // Convert to hex and add leading 0 if required\n        let hex = normalized.toString(16);\n        if (hex.length % 2 !== 0) hex = \"0\" + hex;\n\n        return Utils.convertToByteArray(hex, \"Hex\");\n    }\n\n}\n\nexport default FromBase62;\n"
  },
  {
    "path": "src/core/operations/FromBase64.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {fromBase64, ALPHABET_OPTIONS} from \"../lib/Base64.mjs\";\n\n/**\n * From Base64 operation\n */\nclass FromBase64 extends Operation {\n\n    /**\n     * FromBase64 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base64\";\n        this.module = \"Default\";\n        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>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Base64\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"editableOption\",\n                value: ALPHABET_OPTIONS\n            },\n            {\n                name: \"Remove non-alphabet chars\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Strict mode\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^\\\\s*(?:[A-Z\\\\d+/]{4})+(?:[A-Z\\\\d+/]{2}==|[A-Z\\\\d+/]{3}=)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"A-Za-z0-9+/=\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*[A-Z\\\\d\\\\-_]{20,}\\\\s*$\",\n                flags: \"i\",\n                args: [\"A-Za-z0-9-_\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z\\\\d+\\\\-]{4}){5,}(?:[A-Z\\\\d+\\\\-]{2}==|[A-Z\\\\d+\\\\-]{3}=)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"A-Za-z0-9+\\\\-=\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z\\\\d./]{4}){5,}(?:[A-Z\\\\d./]{2}==|[A-Z\\\\d./]{3}=)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"./0-9A-Za-z=\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*[A-Z\\\\d_.]{20,}\\\\s*$\",\n                flags: \"i\",\n                args: [\"A-Za-z0-9_.\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z\\\\d._]{4}){5,}(?:[A-Z\\\\d._]{2}--|[A-Z\\\\d._]{3}-)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"A-Za-z0-9._-\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z\\\\d+/]{4}){5,}(?:[A-Z\\\\d+/]{2}==|[A-Z\\\\d+/]{3}=)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"0-9a-zA-Z+/=\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z\\\\d+/]{4}){5,}(?:[A-Z\\\\d+/]{2}==|[A-Z\\\\d+/]{3}=)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"0-9A-Za-z+/=\", true, false]\n            },\n            {\n                pattern: \"^[ !\\\"#$%&'()*+,\\\\-./\\\\d:;<=>?@A-Z[\\\\\\\\\\\\]^_]{20,}$\",\n                flags: \"\",\n                args: [\" -_\", false, false]\n            },\n            {\n                pattern: \"^\\\\s*[A-Z\\\\d+\\\\-]{20,}\\\\s*$\",\n                flags: \"i\",\n                args: [\"+\\\\-0-9A-Za-z\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*[!\\\"#$%&'()*+,\\\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\\\s*$\",\n                flags: \"\",\n                args: [\"!-,-0-689@A-NP-VX-Z[`a-fh-mp-r\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[N-ZA-M\\\\d+/]{4}){5,}(?:[N-ZA-M\\\\d+/]{2}==|[N-ZA-M\\\\d+/]{3}=)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"N-ZA-Mn-za-m0-9+/=\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*[A-Z\\\\d./]{20,}\\\\s*$\",\n                flags: \"i\",\n                args: [\"./0-9A-Za-z\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z=\\\\d\\\\+/]{4}){5,}(?:[A-Z=\\\\d\\\\+/]{2}CC|[A-Z=\\\\d\\\\+/]{3}C)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z=\\\\d\\\\+/]{4}){5,}(?:[A-Z=\\\\d\\\\+/]{2}55|[A-Z=\\\\d\\\\+/]{3}5)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z=\\\\d\\\\+/]{4}){5,}(?:[A-Z=\\\\d\\\\+/]{2}22|[A-Z=\\\\d\\\\+/]{3}2)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2\", true, false]\n            },\n            {\n                pattern: \"^\\\\s*(?:[A-Z=\\\\d\\\\+/]{4}){5,}(?:[A-Z=\\\\d\\\\+/]{2}55|[A-Z=\\\\d\\\\+/]{3}5)?\\\\s*$\",\n                flags: \"i\",\n                args: [\"HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5\", true, false]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const [alphabet, removeNonAlphChars, strictMode] = args;\n\n        return fromBase64(input, alphabet, \"byteArray\", removeNonAlphChars, strictMode);\n    }\n\n    /**\n     * Highlight to Base64\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        pos[0].start = Math.ceil(pos[0].start / 4 * 3);\n        pos[0].end = Math.floor(pos[0].end / 4 * 3);\n        return pos;\n    }\n\n    /**\n     * Highlight from Base64\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        pos[0].start = Math.floor(pos[0].start / 3 * 4);\n        pos[0].end = Math.ceil(pos[0].end / 3 * 4);\n        return pos;\n    }\n}\n\nexport default FromBase64;\n"
  },
  {
    "path": "src/core/operations/FromBase85.mjs",
    "content": "/**\n * @author PenguinGeorge [george@penguingeorge.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {ALPHABET_OPTIONS} from \"../lib/Base85.mjs\";\n\n/**\n * From Base85 operation\n */\nclass FromBase85 extends Operation {\n\n    /**\n     * From Base85 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base85\";\n        this.module = \"Default\";\n        this.description = \"Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.<br><br>This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>BOu!rD]j7BEbo7</code> becomes <code>hello world</code><br><br>Base85 is commonly used in Adobe's PostScript and PDF file formats.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Ascii85\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"editableOption\",\n                value: ALPHABET_OPTIONS\n            },\n            {\n                name: \"Remove non-alphabet chars\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"All-zero group char\",\n                type: \"binaryShortString\",\n                value: \"z\",\n                maxLength: 1\n            }\n        ];\n        this.checks = [\n            {\n                pattern:\n                    \"^\\\\s*(?:<~)?\" + // Optional whitespace and starting marker\n                    \"[\\\\s!-uz]*\" +   // Any amount of base85 characters and whitespace\n                    \"[!-uz]{15}\" +   // At least 15 continoues base85 characters without whitespace\n                    \"[\\\\s!-uz]*\" +   // Any amount of base85 characters and whitespace\n                    \"(?:~>)?\\\\s*$\",  // Optional ending marker and whitespace\n                args: [\"!-u\"],\n            },\n            {\n                pattern:\n                    \"^\" +\n                    \"[\\\\s0-9a-zA-Z.\\\\-:+=^!/*?&<>()[\\\\]{}@%$#]*\" +\n                    \"[0-9a-zA-Z.\\\\-:+=^!/*?&<>()[\\\\]{}@%$#]{15}\" + // At least 15 continoues base85 characters without whitespace\n                    \"[\\\\s0-9a-zA-Z.\\\\-:+=^!/*?&<>()[\\\\]{}@%$#]*\" +\n                    \"$\",\n                args: [\"0-9a-zA-Z.\\\\-:+=^!/*?&<>()[]{}@%$#\"],\n            },\n            {\n                pattern:\n                    \"^\" +\n                    \"[\\\\s0-9A-Za-z!#$%&()*+\\\\-;<=>?@^_`{|}~]*\" +\n                    \"[0-9A-Za-z!#$%&()*+\\\\-;<=>?@^_`{|}~]{15}\" + // At least 15 continoues base85 characters without whitespace\n                    \"[\\\\s0-9A-Za-z!#$%&()*+\\\\-;<=>?@^_`{|}~]*\" +\n                    \"$\",\n                args: [\"0-9A-Za-z!#$%&()*+\\\\-;<=>?@^_`{|}~\"],\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const alphabet = Utils.expandAlphRange(args[0]).join(\"\"),\n            removeNonAlphChars = args[1],\n            allZeroGroupChar = typeof args[2] === \"string\" ? args[2].slice(0, 1) : \"\",\n            result = [];\n\n        if (alphabet.length !== 85 ||\n            [].unique.call(alphabet).length !== 85) {\n            throw new OperationError(\"Alphabet must be of length 85\");\n        }\n\n        if (allZeroGroupChar && alphabet.includes(allZeroGroupChar)) {\n            throw new OperationError(\"The all-zero group char cannot appear in the alphabet\");\n        }\n\n        // Remove delimiters if present\n        const matches = input.match(/^<~(.+?)~>$/);\n        if (matches !== null) input = matches[1];\n\n        // Remove non-alphabet characters\n        if (removeNonAlphChars) {\n            const re = new RegExp(\"[^~\" + allZeroGroupChar +alphabet.replace(/[[\\]\\\\\\-^$]/g, \"\\\\$&\") + \"]\", \"g\");\n            input = input.replace(re, \"\");\n            // Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters)\n            const matches = input.match(/^<~(.+?)~>$/);\n            if (matches !== null) input = matches[1];\n        }\n\n        if (input.length === 0) return [];\n\n        let i = 0;\n        let block, blockBytes;\n        while (i < input.length) {\n            if (input[i] === allZeroGroupChar) {\n                result.push(0, 0, 0, 0);\n                i++;\n            } else {\n                let digits = [];\n                digits = input\n                    .substr(i, 5)\n                    .split(\"\")\n                    .map((chr, idx) => {\n                        const digit = alphabet.indexOf(chr);\n                        if ((digit < 0 || digit > 84) && chr !== allZeroGroupChar) {\n                            throw `Invalid character '${chr}' at index ${i + idx}`;\n                        }\n                        return digit;\n                    });\n\n                block =\n                    digits[0] * 52200625 +\n                    digits[1] * 614125 +\n                    (i + 2 < input.length ? digits[2] : 84) * 7225 +\n                    (i + 3 < input.length ? digits[3] : 84) * 85 +\n                    (i + 4 < input.length ? digits[4] : 84);\n\n                blockBytes = [\n                    (block >> 24) & 0xff,\n                    (block >> 16) & 0xff,\n                    (block >> 8) & 0xff,\n                    block & 0xff\n                ];\n\n                if (input.length < i + 5) {\n                    blockBytes.splice(input.length - (i + 5), 5);\n                }\n\n                result.push.apply(result, blockBytes);\n                i += 5;\n            }\n        }\n\n        return result;\n    }\n\n}\n\nexport default FromBase85;\n"
  },
  {
    "path": "src/core/operations/FromBase92.mjs",
    "content": "/**\n * @author sg5506844 [sg5506844@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport { base92Ord } from \"../lib/Base92.mjs\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * From Base92 operation\n */\nclass FromBase92 extends Operation {\n    /**\n     * FromBase92 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Base92\";\n        this.module = \"Default\";\n        this.description = \"Base92 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.\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_numeral_systems\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const res = [];\n        let bitString = \"\";\n\n        for (let i = 0; i < input.length; i += 2) {\n            if (i + 1 !== input.length) {\n                const x = base92Ord(input[i]) * 91 + base92Ord(input[i + 1]);\n                bitString += x.toString(2).padStart(13, \"0\");\n            } else {\n                const x = base92Ord(input[i]);\n                bitString += x.toString(2).padStart(6, \"0\");\n            }\n            while (bitString.length >= 8) {\n                res.push(parseInt(bitString.slice(0, 8), 2));\n                bitString = bitString.slice(8);\n            }\n        }\n\n        return res;\n    }\n}\n\nexport default FromBase92;\n"
  },
  {
    "path": "src/core/operations/FromBech32.mjs",
    "content": "/**\n * @author Medjedtxm\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { decode } from \"../lib/Bech32.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\n\n/**\n * From Bech32 operation\n */\nclass FromBech32 extends Operation {\n\n    /**\n     * FromBech32 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Bech32\";\n        this.module = \"Default\";\n        this.description = \"Bech32 is an encoding scheme primarily used for Bitcoin SegWit addresses (BIP-0173). It uses a 32-character alphabet that excludes easily confused characters (1, b, i, o) and includes a checksum for error detection.<br><br>Bech32m (BIP-0350) is an updated version used for Bitcoin Taproot addresses.<br><br>Auto-detect will attempt Bech32 first, then Bech32m if the checksum fails.<br><br>Output format options allow you to see the Human-Readable Part (HRP) along with the decoded data.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bech32\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Encoding\",\n                \"type\": \"option\",\n                \"value\": [\"Auto-detect\", \"Bech32\", \"Bech32m\"]\n            },\n            {\n                \"name\": \"Output Format\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\", \"Bitcoin scriptPubKey\", \"HRP: Hex\", \"JSON\"]\n            }\n        ];\n        this.checks = [\n            {\n                // Bitcoin mainnet SegWit/Taproot addresses\n                pattern: \"^bc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$\",\n                flags: \"i\",\n                args: [\"Auto-detect\", \"Hex\"]\n            },\n            {\n                // Bitcoin testnet addresses\n                pattern: \"^tb1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$\",\n                flags: \"i\",\n                args: [\"Auto-detect\", \"Hex\"]\n            },\n            {\n                // AGE public keys\n                pattern: \"^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$\",\n                flags: \"i\",\n                args: [\"Auto-detect\", \"HRP: Hex\"]\n            },\n            {\n                // AGE secret keys\n                pattern: \"^AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{6,87}$\",\n                flags: \"\",\n                args: [\"Auto-detect\", \"HRP: Hex\"]\n            },\n            {\n                // Litecoin mainnet addresses\n                pattern: \"^ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$\",\n                flags: \"i\",\n                args: [\"Auto-detect\", \"Hex\"]\n            },\n            {\n                // Generic bech32 pattern\n                pattern: \"^[a-z]{1,83}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,}$\",\n                flags: \"i\",\n                args: [\"Auto-detect\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const encoding = args[0];\n        const outputFormat = args[1];\n\n        input = input.trim();\n\n        if (input.length === 0) {\n            return \"\";\n        }\n\n        const decoded = decode(input, encoding);\n\n        // Format output based on selected option\n        switch (outputFormat) {\n            case \"Raw\":\n                return decoded.data.map(b => String.fromCharCode(b)).join(\"\");\n\n            case \"Hex\":\n                return toHex(decoded.data, \"\");\n\n            case \"Bitcoin scriptPubKey\": {\n                // Convert to Bitcoin scriptPubKey format as shown in BIP-0173/BIP-0350\n                // Format: [OP_version][length][witness_program]\n                // OP_0 = 0x00, OP_1-OP_16 = 0x51-0x60\n                if (decoded.witnessVersion === null || decoded.data.length < 2) {\n                    // Not a SegWit address, fall back to hex\n                    return toHex(decoded.data, \"\");\n                }\n                const witnessVersion = decoded.data[0];\n                const witnessProgram = decoded.data.slice(1);\n\n                // Convert witness version to OP code\n                let opCode;\n                if (witnessVersion === 0) {\n                    opCode = 0x00; // OP_0\n                } else if (witnessVersion >= 1 && witnessVersion <= 16) {\n                    opCode = 0x50 + witnessVersion; // OP_1 = 0x51, ..., OP_16 = 0x60\n                } else {\n                    // Invalid witness version, fall back to hex\n                    return toHex(decoded.data, \"\");\n                }\n\n                // Build scriptPubKey: [OP_version][length][program]\n                const scriptPubKey = [opCode, witnessProgram.length, ...witnessProgram];\n                return toHex(scriptPubKey, \"\");\n            }\n\n            case \"HRP: Hex\":\n                return `${decoded.hrp}: ${toHex(decoded.data, \"\")}`;\n\n            case \"JSON\":\n                return JSON.stringify({\n                    hrp: decoded.hrp,\n                    encoding: decoded.encoding,\n                    data: toHex(decoded.data, \"\")\n                }, null, 2);\n\n            default:\n                return toHex(decoded.data, \"\");\n        }\n    }\n\n}\n\nexport default FromBech32;\n"
  },
  {
    "path": "src/core/operations/FromBinary.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {BIN_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport {fromBinary} from \"../lib/Binary.mjs\";\n\n/**\n * From Binary operation\n */\nclass FromBinary extends Operation {\n\n    /**\n     * FromBinary constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Binary\";\n        this.module = \"Default\";\n        this.description = \"Converts a binary string back into its raw form.<br><br>e.g. <code>01001000 01101001</code> becomes <code>Hi</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Binary_code\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": BIN_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Byte Length\",\n                \"type\": \"number\",\n                \"value\": 8,\n                \"min\": 1\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:[01]{8})+$\",\n                flags: \"\",\n                args: [\"None\"]\n            },\n            {\n                pattern: \"^(?:[01]{8})(?: [01]{8})*$\",\n                flags: \"\",\n                args: [\"Space\"]\n            },\n            {\n                pattern: \"^(?:[01]{8})(?:,[01]{8})*$\",\n                flags: \"\",\n                args: [\"Comma\"]\n            },\n            {\n                pattern: \"^(?:[01]{8})(?:;[01]{8})*$\",\n                flags: \"\",\n                args: [\"Semi-colon\"]\n            },\n            {\n                pattern: \"^(?:[01]{8})(?::[01]{8})*$\",\n                flags: \"\",\n                args: [\"Colon\"]\n            },\n            {\n                pattern: \"^(?:[01]{8})(?:\\\\n[01]{8})*$\",\n                flags: \"\",\n                args: [\"Line feed\"]\n            },\n            {\n                pattern: \"^(?:[01]{8})(?:\\\\r\\\\n[01]{8})*$\",\n                flags: \"\",\n                args: [\"CRLF\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const byteLen = args[1] ? args[1] : 8;\n        return fromBinary(input, args[0], byteLen);\n    }\n\n    /**\n     * Highlight From Binary\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n        pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length));\n        pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length));\n        return pos;\n    }\n\n    /**\n     * Highlight From Binary in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n        pos[0].start = pos[0].start * (8 + delim.length);\n        pos[0].end = pos[0].end * (8 + delim.length) - delim.length;\n        return pos;\n    }\n\n}\n\nexport default FromBinary;\n"
  },
  {
    "path": "src/core/operations/FromBraille.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {BRAILLE_LOOKUP} from \"../lib/Braille.mjs\";\n\n/**\n * From Braille operation\n */\nclass FromBraille extends Operation {\n\n    /**\n     * FromBraille constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Braille\";\n        this.module = \"Default\";\n        this.description = \"Converts six-dot braille symbols to text.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Braille\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return input.split(\"\").map(b => {\n            const idx = BRAILLE_LOOKUP.dot6.indexOf(b);\n            return idx < 0 ? b : BRAILLE_LOOKUP.ascii[idx];\n        }).join(\"\");\n    }\n\n    /**\n     * Highlight From Braille\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight From Braille in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default FromBraille;\n"
  },
  {
    "path": "src/core/operations/FromCaseInsensitiveRegex.mjs",
    "content": "/**\n * @author masq [github.cyberchef@masq.cc]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * From Case Insensitive Regex operation\n */\nclass FromCaseInsensitiveRegex extends Operation {\n\n    /**\n     * FromCaseInsensitiveRegex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Case Insensitive Regex\";\n        this.module = \"Default\";\n        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>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Regular_expression\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return input.replace(/\\[[a-z]{2}\\]/ig, m => m[1].toUpperCase() === m[2].toUpperCase() ? m[1] : m);\n    }\n}\n\nexport default FromCaseInsensitiveRegex;\n"
  },
  {
    "path": "src/core/operations/FromCharcode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { DELIM_OPTIONS } from \"../lib/Delim.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * From Charcode operation\n */\nclass FromCharcode extends Operation {\n\n    /**\n     * FromCharcode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Charcode\";\n        this.module = \"Default\";\n        this.description = \"Converts unicode character codes back into text.<br><br>e.g. <code>0393 03b5 03b9 03ac 20 03c3 03bf 03c5</code> becomes <code>Γειά σου</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Plane_(Unicode)\";\n        this.inputType = \"string\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Base\",\n                \"type\": \"number\",\n                \"value\": 16\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     *\n     * @throws {OperationError} if base out of range\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0] || \"Space\"),\n            base = args[1];\n        let bites = input.split(delim),\n            i = 0;\n\n        if (base < 2 || base > 36) {\n            throw new OperationError(\"Error: Base argument must be between 2 and 36\");\n        }\n\n        if (input.length === 0) {\n            return new ArrayBuffer;\n        }\n\n        if (base !== 16 && isWorkerEnvironment()) self.setOption(\"attemptHighlight\", false);\n\n        // Split into groups of 2 if the whole string is concatenated and\n        // too long to be a single character\n        if (bites.length === 1 && input.length > 17) {\n            bites = [];\n            for (i = 0; i < input.length; i += 2) {\n                bites.push(input.slice(i, i+2));\n            }\n        }\n\n        let latin1 = \"\";\n        for (i = 0; i < bites.length; i++) {\n            latin1 += Utils.chr(parseInt(bites[i], base));\n        }\n        return Utils.strToArrayBuffer(latin1);\n    }\n\n}\n\nexport default FromCharcode;\n"
  },
  {
    "path": "src/core/operations/FromDecimal.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport {fromDecimal} from \"../lib/Decimal.mjs\";\n\n/**\n * From Decimal operation\n */\nclass FromDecimal extends Operation {\n\n    /**\n     * FromDecimal constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Decimal\";\n        this.module = \"Default\";\n        this.description = \"Converts the data from an ordinal integer array back into its raw form.<br><br>e.g. <code>72 101 108 108 111</code> becomes <code>Hello</code>\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Support signed values\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5])(?: (?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5]))*$\",\n                flags: \"\",\n                args: [\"Space\", false]\n            },\n            {\n                pattern: \"^(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5])(?:,(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5]))*$\",\n                flags: \"\",\n                args: [\"Comma\", false]\n            },\n            {\n                pattern: \"^(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5])(?:;(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5]))*$\",\n                flags: \"\",\n                args: [\"Semi-colon\", false]\n            },\n            {\n                pattern: \"^(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5])(?::(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5]))*$\",\n                flags: \"\",\n                args: [\"Colon\", false]\n            },\n            {\n                pattern: \"^(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5])(?:\\\\n(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5]))*$\",\n                flags: \"\",\n                args: [\"Line feed\", false]\n            },\n            {\n                pattern: \"^(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5])(?:\\\\r\\\\n(?:\\\\d{1,2}|1\\\\d{2}|2[0-4]\\\\d|25[0-5]))*$\",\n                flags: \"\",\n                args: [\"CRLF\", false]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        let data = fromDecimal(input, args[0]);\n        if (args[1]) { // Convert negatives\n            data = data.map(v => v < 0 ? 0xFF + v + 1 : v);\n        }\n        return data;\n    }\n\n}\n\nexport default FromDecimal;\n"
  },
  {
    "path": "src/core/operations/FromFloat.mjs",
    "content": "/**\n * @author tcode2k16 [tcode2k16@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport ieee754 from \"ieee754\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * From Float operation\n */\nclass FromFloat extends Operation {\n\n    /**\n     * FromFloat constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Float\";\n        this.module = \"Default\";\n        this.description = \"Convert from IEEE754 Floating Point Numbers\";\n        this.infoURL = \"https://wikipedia.org/wiki/IEEE_754\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Endianness\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Big Endian\",\n                    \"Little Endian\"\n                ]\n            },\n            {\n                \"name\": \"Size\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Float (4 bytes)\",\n                    \"Double (8 bytes)\"\n                ]\n            },\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        if (input.length === 0) return [];\n\n        const [endianness, size, delimiterName] = args;\n        const delim = Utils.charRep(delimiterName || \"Space\");\n        const byteSize = size === \"Double (8 bytes)\" ? 8 : 4;\n        const isLE = endianness === \"Little Endian\";\n        const mLen = byteSize === 4 ? 23 : 52;\n        const floats = input.split(delim);\n\n        const output = new Array(floats.length*byteSize);\n        for (let i = 0; i < floats.length; i++) {\n            ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize);\n        }\n        return output;\n    }\n\n}\n\nexport default FromFloat;\n"
  },
  {
    "path": "src/core/operations/FromHTMLEntity.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * From HTML Entity operation\n */\nclass FromHTMLEntity extends Operation {\n\n    /**\n     * FromHTMLEntity constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From HTML Entity\";\n        this.module = \"Encodings\";\n        this.description = \"Converts HTML entities back to characters<br><br>e.g. <code>&amp;<span>amp;</span></code> becomes <code>&amp;</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern: \"&(?:#\\\\d{2,3}|#x[\\\\da-f]{2}|[a-z]{2,6});\",\n                flags: \"i\",\n                args: []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const regex = /&(#?x?[a-zA-Z0-9]{1,20});/g;\n        let output = \"\",\n            m,\n            i = 0;\n\n        while ((m = regex.exec(input))) {\n            // Add up to match\n            for (; i < m.index;)\n                output += input[i++];\n\n            // Add match\n            const bite = entityToByte[m[1]];\n            if (bite) {\n                output += Utils.chr(bite);\n            } else if (!bite && m[1][0] === \"#\" && m[1].length > 1 && /^#\\d{1,6}$/.test(m[1])) {\n                // Numeric entity (e.g. &#10;)\n                const num = m[1].slice(1, m[1].length);\n                output += Utils.chr(parseInt(num, 10));\n            } else if (!bite && m[1][0] === \"#\" && m[1].length > 3 && /^#x[\\dA-F]{2,8}$/i.test(m[1])) {\n                // Hex entity (e.g. &#x3A;)\n                const hex = m[1].slice(2, m[1].length);\n                output += Utils.chr(parseInt(hex, 16));\n            } else {\n                // Not a valid entity, print as normal\n                for (; i < regex.lastIndex;)\n                    output += input[i++];\n            }\n\n            i = regex.lastIndex;\n        }\n        // Add all after final match\n        for (; i < input.length;)\n            output += input[i++];\n\n        return output;\n    }\n\n}\n\n\n/**\n * Lookup table to translate HTML entity codes to their byte values.\n */\nconst entityToByte = {\n    \"Tab\": 9,\n    \"NewLine\": 10,\n    \"excl\": 33,\n    \"quot\": 34,\n    \"num\": 35,\n    \"dollar\": 36,\n    \"percnt\": 37,\n    \"amp\": 38,\n    \"apos\": 39,\n    \"lpar\": 40,\n    \"rpar\": 41,\n    \"ast\": 42,\n    \"plus\": 43,\n    \"comma\": 44,\n    \"period\": 46,\n    \"sol\": 47,\n    \"colon\": 58,\n    \"semi\": 59,\n    \"lt\": 60,\n    \"equals\": 61,\n    \"gt\": 62,\n    \"quest\": 63,\n    \"commat\": 64,\n    \"lsqb\": 91,\n    \"bsol\": 92,\n    \"rsqb\": 93,\n    \"Hat\": 94,\n    \"lowbar\": 95,\n    \"grave\": 96,\n    \"lcub\": 123,\n    \"verbar\": 124,\n    \"rcub\": 125,\n    \"nbsp\": 160,\n    \"iexcl\": 161,\n    \"cent\": 162,\n    \"pound\": 163,\n    \"curren\": 164,\n    \"yen\": 165,\n    \"brvbar\": 166,\n    \"sect\": 167,\n    \"uml\": 168,\n    \"copy\": 169,\n    \"ordf\": 170,\n    \"laquo\": 171,\n    \"not\": 172,\n    \"shy\": 173,\n    \"reg\": 174,\n    \"macr\": 175,\n    \"deg\": 176,\n    \"plusmn\": 177,\n    \"sup2\": 178,\n    \"sup3\": 179,\n    \"acute\": 180,\n    \"micro\": 181,\n    \"para\": 182,\n    \"middot\": 183,\n    \"cedil\": 184,\n    \"sup1\": 185,\n    \"ordm\": 186,\n    \"raquo\": 187,\n    \"frac14\": 188,\n    \"frac12\": 189,\n    \"frac34\": 190,\n    \"iquest\": 191,\n    \"Agrave\": 192,\n    \"Aacute\": 193,\n    \"Acirc\": 194,\n    \"Atilde\": 195,\n    \"Auml\": 196,\n    \"Aring\": 197,\n    \"AElig\": 198,\n    \"Ccedil\": 199,\n    \"Egrave\": 200,\n    \"Eacute\": 201,\n    \"Ecirc\": 202,\n    \"Euml\": 203,\n    \"Igrave\": 204,\n    \"Iacute\": 205,\n    \"Icirc\": 206,\n    \"Iuml\": 207,\n    \"ETH\": 208,\n    \"Ntilde\": 209,\n    \"Ograve\": 210,\n    \"Oacute\": 211,\n    \"Ocirc\": 212,\n    \"Otilde\": 213,\n    \"Ouml\": 214,\n    \"times\": 215,\n    \"Oslash\": 216,\n    \"Ugrave\": 217,\n    \"Uacute\": 218,\n    \"Ucirc\": 219,\n    \"Uuml\": 220,\n    \"Yacute\": 221,\n    \"THORN\": 222,\n    \"szlig\": 223,\n    \"agrave\": 224,\n    \"aacute\": 225,\n    \"acirc\": 226,\n    \"atilde\": 227,\n    \"auml\": 228,\n    \"aring\": 229,\n    \"aelig\": 230,\n    \"ccedil\": 231,\n    \"egrave\": 232,\n    \"eacute\": 233,\n    \"ecirc\": 234,\n    \"euml\": 235,\n    \"igrave\": 236,\n    \"iacute\": 237,\n    \"icirc\": 238,\n    \"iuml\": 239,\n    \"eth\": 240,\n    \"ntilde\": 241,\n    \"ograve\": 242,\n    \"oacute\": 243,\n    \"ocirc\": 244,\n    \"otilde\": 245,\n    \"ouml\": 246,\n    \"divide\": 247,\n    \"oslash\": 248,\n    \"ugrave\": 249,\n    \"uacute\": 250,\n    \"ucirc\": 251,\n    \"uuml\": 252,\n    \"yacute\": 253,\n    \"thorn\": 254,\n    \"yuml\": 255,\n    \"Amacr\": 256,\n    \"amacr\": 257,\n    \"Abreve\": 258,\n    \"abreve\": 259,\n    \"Aogon\": 260,\n    \"aogon\": 261,\n    \"Cacute\": 262,\n    \"cacute\": 263,\n    \"Ccirc\": 264,\n    \"ccirc\": 265,\n    \"Cdot\": 266,\n    \"cdot\": 267,\n    \"Ccaron\": 268,\n    \"ccaron\": 269,\n    \"Dcaron\": 270,\n    \"dcaron\": 271,\n    \"Dstrok\": 272,\n    \"dstrok\": 273,\n    \"Emacr\": 274,\n    \"emacr\": 275,\n    \"Edot\": 278,\n    \"edot\": 279,\n    \"Eogon\": 280,\n    \"eogon\": 281,\n    \"Ecaron\": 282,\n    \"ecaron\": 283,\n    \"Gcirc\": 284,\n    \"gcirc\": 285,\n    \"Gbreve\": 286,\n    \"gbreve\": 287,\n    \"Gdot\": 288,\n    \"gdot\": 289,\n    \"Gcedil\": 290,\n    \"Hcirc\": 292,\n    \"hcirc\": 293,\n    \"Hstrok\": 294,\n    \"hstrok\": 295,\n    \"Itilde\": 296,\n    \"itilde\": 297,\n    \"Imacr\": 298,\n    \"imacr\": 299,\n    \"Iogon\": 302,\n    \"iogon\": 303,\n    \"Idot\": 304,\n    \"imath\": 305,\n    \"IJlig\": 306,\n    \"ijlig\": 307,\n    \"Jcirc\": 308,\n    \"jcirc\": 309,\n    \"Kcedil\": 310,\n    \"kcedil\": 311,\n    \"kgreen\": 312,\n    \"Lacute\": 313,\n    \"lacute\": 314,\n    \"Lcedil\": 315,\n    \"lcedil\": 316,\n    \"Lcaron\": 317,\n    \"lcaron\": 318,\n    \"Lmidot\": 319,\n    \"lmidot\": 320,\n    \"Lstrok\": 321,\n    \"lstrok\": 322,\n    \"Nacute\": 323,\n    \"nacute\": 324,\n    \"Ncedil\": 325,\n    \"ncedil\": 326,\n    \"Ncaron\": 327,\n    \"ncaron\": 328,\n    \"napos\": 329,\n    \"ENG\": 330,\n    \"eng\": 331,\n    \"Omacr\": 332,\n    \"omacr\": 333,\n    \"Odblac\": 336,\n    \"odblac\": 337,\n    \"OElig\": 338,\n    \"oelig\": 339,\n    \"Racute\": 340,\n    \"racute\": 341,\n    \"Rcedil\": 342,\n    \"rcedil\": 343,\n    \"Rcaron\": 344,\n    \"rcaron\": 345,\n    \"Sacute\": 346,\n    \"sacute\": 347,\n    \"Scirc\": 348,\n    \"scirc\": 349,\n    \"Scedil\": 350,\n    \"scedil\": 351,\n    \"Scaron\": 352,\n    \"scaron\": 353,\n    \"Tcedil\": 354,\n    \"tcedil\": 355,\n    \"Tcaron\": 356,\n    \"tcaron\": 357,\n    \"Tstrok\": 358,\n    \"tstrok\": 359,\n    \"Utilde\": 360,\n    \"utilde\": 361,\n    \"Umacr\": 362,\n    \"umacr\": 363,\n    \"Ubreve\": 364,\n    \"ubreve\": 365,\n    \"Uring\": 366,\n    \"uring\": 367,\n    \"Udblac\": 368,\n    \"udblac\": 369,\n    \"Uogon\": 370,\n    \"uogon\": 371,\n    \"Wcirc\": 372,\n    \"wcirc\": 373,\n    \"Ycirc\": 374,\n    \"ycirc\": 375,\n    \"Yuml\": 376,\n    \"Zacute\": 377,\n    \"zacute\": 378,\n    \"Zdot\": 379,\n    \"zdot\": 380,\n    \"Zcaron\": 381,\n    \"zcaron\": 382,\n    \"fnof\": 402,\n    \"imped\": 437,\n    \"gacute\": 501,\n    \"jmath\": 567,\n    \"circ\": 710,\n    \"caron\": 711,\n    \"breve\": 728,\n    \"dot\": 729,\n    \"ring\": 730,\n    \"ogon\": 731,\n    \"tilde\": 732,\n    \"dblac\": 733,\n    \"DownBreve\": 785,\n    \"UnderBar\": 818,\n    \"Alpha\": 913,\n    \"Beta\": 914,\n    \"Gamma\": 915,\n    \"Delta\": 916,\n    \"Epsilon\": 917,\n    \"Zeta\": 918,\n    \"Eta\": 919,\n    \"Theta\": 920,\n    \"Iota\": 921,\n    \"Kappa\": 922,\n    \"Lambda\": 923,\n    \"Mu\": 924,\n    \"Nu\": 925,\n    \"Xi\": 926,\n    \"Omicron\": 927,\n    \"Pi\": 928,\n    \"Rho\": 929,\n    \"Sigma\": 931,\n    \"Tau\": 932,\n    \"Upsilon\": 933,\n    \"Phi\": 934,\n    \"Chi\": 935,\n    \"Psi\": 936,\n    \"Omega\": 937,\n    \"alpha\": 945,\n    \"beta\": 946,\n    \"gamma\": 947,\n    \"delta\": 948,\n    \"epsilon\": 949,\n    \"zeta\": 950,\n    \"eta\": 951,\n    \"theta\": 952,\n    \"iota\": 953,\n    \"kappa\": 954,\n    \"lambda\": 955,\n    \"mu\": 956,\n    \"nu\": 957,\n    \"xi\": 958,\n    \"omicron\": 959,\n    \"pi\": 960,\n    \"rho\": 961,\n    \"sigmaf\": 962,\n    \"sigma\": 963,\n    \"tau\": 964,\n    \"upsilon\": 965,\n    \"phi\": 966,\n    \"chi\": 967,\n    \"psi\": 968,\n    \"omega\": 969,\n    \"thetasym\": 977,\n    \"upsih\": 978,\n    \"straightphi\": 981,\n    \"piv\": 982,\n    \"Gammad\": 988,\n    \"gammad\": 989,\n    \"kappav\": 1008,\n    \"rhov\": 1009,\n    \"epsi\": 1013,\n    \"bepsi\": 1014,\n    \"IOcy\": 1025,\n    \"DJcy\": 1026,\n    \"GJcy\": 1027,\n    \"Jukcy\": 1028,\n    \"DScy\": 1029,\n    \"Iukcy\": 1030,\n    \"YIcy\": 1031,\n    \"Jsercy\": 1032,\n    \"LJcy\": 1033,\n    \"NJcy\": 1034,\n    \"TSHcy\": 1035,\n    \"KJcy\": 1036,\n    \"Ubrcy\": 1038,\n    \"DZcy\": 1039,\n    \"Acy\": 1040,\n    \"Bcy\": 1041,\n    \"Vcy\": 1042,\n    \"Gcy\": 1043,\n    \"Dcy\": 1044,\n    \"IEcy\": 1045,\n    \"ZHcy\": 1046,\n    \"Zcy\": 1047,\n    \"Icy\": 1048,\n    \"Jcy\": 1049,\n    \"Kcy\": 1050,\n    \"Lcy\": 1051,\n    \"Mcy\": 1052,\n    \"Ncy\": 1053,\n    \"Ocy\": 1054,\n    \"Pcy\": 1055,\n    \"Rcy\": 1056,\n    \"Scy\": 1057,\n    \"Tcy\": 1058,\n    \"Ucy\": 1059,\n    \"Fcy\": 1060,\n    \"KHcy\": 1061,\n    \"TScy\": 1062,\n    \"CHcy\": 1063,\n    \"SHcy\": 1064,\n    \"SHCHcy\": 1065,\n    \"HARDcy\": 1066,\n    \"Ycy\": 1067,\n    \"SOFTcy\": 1068,\n    \"Ecy\": 1069,\n    \"YUcy\": 1070,\n    \"YAcy\": 1071,\n    \"acy\": 1072,\n    \"bcy\": 1073,\n    \"vcy\": 1074,\n    \"gcy\": 1075,\n    \"dcy\": 1076,\n    \"iecy\": 1077,\n    \"zhcy\": 1078,\n    \"zcy\": 1079,\n    \"icy\": 1080,\n    \"jcy\": 1081,\n    \"kcy\": 1082,\n    \"lcy\": 1083,\n    \"mcy\": 1084,\n    \"ncy\": 1085,\n    \"ocy\": 1086,\n    \"pcy\": 1087,\n    \"rcy\": 1088,\n    \"scy\": 1089,\n    \"tcy\": 1090,\n    \"ucy\": 1091,\n    \"fcy\": 1092,\n    \"khcy\": 1093,\n    \"tscy\": 1094,\n    \"chcy\": 1095,\n    \"shcy\": 1096,\n    \"shchcy\": 1097,\n    \"hardcy\": 1098,\n    \"ycy\": 1099,\n    \"softcy\": 1100,\n    \"ecy\": 1101,\n    \"yucy\": 1102,\n    \"yacy\": 1103,\n    \"iocy\": 1105,\n    \"djcy\": 1106,\n    \"gjcy\": 1107,\n    \"jukcy\": 1108,\n    \"dscy\": 1109,\n    \"iukcy\": 1110,\n    \"yicy\": 1111,\n    \"jsercy\": 1112,\n    \"ljcy\": 1113,\n    \"njcy\": 1114,\n    \"tshcy\": 1115,\n    \"kjcy\": 1116,\n    \"ubrcy\": 1118,\n    \"dzcy\": 1119,\n    \"ensp\": 8194,\n    \"emsp\": 8195,\n    \"emsp13\": 8196,\n    \"emsp14\": 8197,\n    \"numsp\": 8199,\n    \"puncsp\": 8200,\n    \"thinsp\": 8201,\n    \"hairsp\": 8202,\n    \"ZeroWidthSpace\": 8203,\n    \"zwnj\": 8204,\n    \"zwj\": 8205,\n    \"lrm\": 8206,\n    \"rlm\": 8207,\n    \"hyphen\": 8208,\n    \"ndash\": 8211,\n    \"mdash\": 8212,\n    \"horbar\": 8213,\n    \"Verbar\": 8214,\n    \"lsquo\": 8216,\n    \"rsquo\": 8217,\n    \"sbquo\": 8218,\n    \"ldquo\": 8220,\n    \"rdquo\": 8221,\n    \"bdquo\": 8222,\n    \"dagger\": 8224,\n    \"Dagger\": 8225,\n    \"bull\": 8226,\n    \"nldr\": 8229,\n    \"hellip\": 8230,\n    \"permil\": 8240,\n    \"pertenk\": 8241,\n    \"prime\": 8242,\n    \"Prime\": 8243,\n    \"tprime\": 8244,\n    \"bprime\": 8245,\n    \"lsaquo\": 8249,\n    \"rsaquo\": 8250,\n    \"oline\": 8254,\n    \"caret\": 8257,\n    \"hybull\": 8259,\n    \"frasl\": 8260,\n    \"bsemi\": 8271,\n    \"qprime\": 8279,\n    \"MediumSpace\": 8287,\n    \"NoBreak\": 8288,\n    \"ApplyFunction\": 8289,\n    \"InvisibleTimes\": 8290,\n    \"InvisibleComma\": 8291,\n    \"euro\": 8364,\n    \"tdot\": 8411,\n    \"TripleDot\": 8411,\n    \"DotDot\": 8412,\n    \"Copf\": 8450,\n    \"incare\": 8453,\n    \"gscr\": 8458,\n    \"hamilt\": 8459,\n    \"Hfr\": 8460,\n    \"quaternions\": 8461,\n    \"planckh\": 8462,\n    \"planck\": 8463,\n    \"Iscr\": 8464,\n    \"image\": 8465,\n    \"Lscr\": 8466,\n    \"ell\": 8467,\n    \"Nopf\": 8469,\n    \"numero\": 8470,\n    \"copysr\": 8471,\n    \"weierp\": 8472,\n    \"Popf\": 8473,\n    \"rationals\": 8474,\n    \"Rscr\": 8475,\n    \"real\": 8476,\n    \"reals\": 8477,\n    \"rx\": 8478,\n    \"trade\": 8482,\n    \"integers\": 8484,\n    \"ohm\": 8486,\n    \"mho\": 8487,\n    \"Zfr\": 8488,\n    \"iiota\": 8489,\n    \"angst\": 8491,\n    \"bernou\": 8492,\n    \"Cfr\": 8493,\n    \"escr\": 8495,\n    \"Escr\": 8496,\n    \"Fscr\": 8497,\n    \"phmmat\": 8499,\n    \"order\": 8500,\n    \"alefsym\": 8501,\n    \"beth\": 8502,\n    \"gimel\": 8503,\n    \"daleth\": 8504,\n    \"CapitalDifferentialD\": 8517,\n    \"DifferentialD\": 8518,\n    \"ExponentialE\": 8519,\n    \"ImaginaryI\": 8520,\n    \"frac13\": 8531,\n    \"frac23\": 8532,\n    \"frac15\": 8533,\n    \"frac25\": 8534,\n    \"frac35\": 8535,\n    \"frac45\": 8536,\n    \"frac16\": 8537,\n    \"frac56\": 8538,\n    \"frac18\": 8539,\n    \"frac38\": 8540,\n    \"frac58\": 8541,\n    \"frac78\": 8542,\n    \"larr\": 8592,\n    \"uarr\": 8593,\n    \"rarr\": 8594,\n    \"darr\": 8595,\n    \"harr\": 8596,\n    \"varr\": 8597,\n    \"nwarr\": 8598,\n    \"nearr\": 8599,\n    \"searr\": 8600,\n    \"swarr\": 8601,\n    \"nlarr\": 8602,\n    \"nrarr\": 8603,\n    \"rarrw\": 8605,\n    \"Larr\": 8606,\n    \"Uarr\": 8607,\n    \"Rarr\": 8608,\n    \"Darr\": 8609,\n    \"larrtl\": 8610,\n    \"rarrtl\": 8611,\n    \"LeftTeeArrow\": 8612,\n    \"UpTeeArrow\": 8613,\n    \"map\": 8614,\n    \"DownTeeArrow\": 8615,\n    \"larrhk\": 8617,\n    \"rarrhk\": 8618,\n    \"larrlp\": 8619,\n    \"rarrlp\": 8620,\n    \"harrw\": 8621,\n    \"nharr\": 8622,\n    \"lsh\": 8624,\n    \"rsh\": 8625,\n    \"ldsh\": 8626,\n    \"rdsh\": 8627,\n    \"crarr\": 8629,\n    \"cularr\": 8630,\n    \"curarr\": 8631,\n    \"olarr\": 8634,\n    \"orarr\": 8635,\n    \"lharu\": 8636,\n    \"lhard\": 8637,\n    \"uharr\": 8638,\n    \"uharl\": 8639,\n    \"rharu\": 8640,\n    \"rhard\": 8641,\n    \"dharr\": 8642,\n    \"dharl\": 8643,\n    \"rlarr\": 8644,\n    \"udarr\": 8645,\n    \"lrarr\": 8646,\n    \"llarr\": 8647,\n    \"uuarr\": 8648,\n    \"rrarr\": 8649,\n    \"ddarr\": 8650,\n    \"lrhar\": 8651,\n    \"rlhar\": 8652,\n    \"nlArr\": 8653,\n    \"nhArr\": 8654,\n    \"nrArr\": 8655,\n    \"lArr\": 8656,\n    \"uArr\": 8657,\n    \"rArr\": 8658,\n    \"dArr\": 8659,\n    \"hArr\": 8660,\n    \"vArr\": 8661,\n    \"nwArr\": 8662,\n    \"neArr\": 8663,\n    \"seArr\": 8664,\n    \"swArr\": 8665,\n    \"lAarr\": 8666,\n    \"rAarr\": 8667,\n    \"zigrarr\": 8669,\n    \"larrb\": 8676,\n    \"rarrb\": 8677,\n    \"duarr\": 8693,\n    \"loarr\": 8701,\n    \"roarr\": 8702,\n    \"hoarr\": 8703,\n    \"forall\": 8704,\n    \"comp\": 8705,\n    \"part\": 8706,\n    \"exist\": 8707,\n    \"nexist\": 8708,\n    \"empty\": 8709,\n    \"nabla\": 8711,\n    \"isin\": 8712,\n    \"notin\": 8713,\n    \"ni\": 8715,\n    \"notni\": 8716,\n    \"prod\": 8719,\n    \"coprod\": 8720,\n    \"sum\": 8721,\n    \"minus\": 8722,\n    \"mnplus\": 8723,\n    \"plusdo\": 8724,\n    \"setmn\": 8726,\n    \"lowast\": 8727,\n    \"compfn\": 8728,\n    \"radic\": 8730,\n    \"prop\": 8733,\n    \"infin\": 8734,\n    \"angrt\": 8735,\n    \"ang\": 8736,\n    \"angmsd\": 8737,\n    \"angsph\": 8738,\n    \"mid\": 8739,\n    \"nmid\": 8740,\n    \"par\": 8741,\n    \"npar\": 8742,\n    \"and\": 8743,\n    \"or\": 8744,\n    \"cap\": 8745,\n    \"cup\": 8746,\n    \"int\": 8747,\n    \"Int\": 8748,\n    \"tint\": 8749,\n    \"conint\": 8750,\n    \"Conint\": 8751,\n    \"Cconint\": 8752,\n    \"cwint\": 8753,\n    \"cwconint\": 8754,\n    \"awconint\": 8755,\n    \"there4\": 8756,\n    \"becaus\": 8757,\n    \"ratio\": 8758,\n    \"Colon\": 8759,\n    \"minusd\": 8760,\n    \"mDDot\": 8762,\n    \"homtht\": 8763,\n    \"sim\": 8764,\n    \"bsim\": 8765,\n    \"ac\": 8766,\n    \"acd\": 8767,\n    \"wreath\": 8768,\n    \"nsim\": 8769,\n    \"esim\": 8770,\n    \"sime\": 8771,\n    \"nsime\": 8772,\n    \"cong\": 8773,\n    \"simne\": 8774,\n    \"ncong\": 8775,\n    \"asymp\": 8776,\n    \"nap\": 8777,\n    \"ape\": 8778,\n    \"apid\": 8779,\n    \"bcong\": 8780,\n    \"asympeq\": 8781,\n    \"bump\": 8782,\n    \"bumpe\": 8783,\n    \"esdot\": 8784,\n    \"eDot\": 8785,\n    \"efDot\": 8786,\n    \"erDot\": 8787,\n    \"colone\": 8788,\n    \"ecolon\": 8789,\n    \"ecir\": 8790,\n    \"cire\": 8791,\n    \"wedgeq\": 8793,\n    \"veeeq\": 8794,\n    \"trie\": 8796,\n    \"equest\": 8799,\n    \"ne\": 8800,\n    \"equiv\": 8801,\n    \"nequiv\": 8802,\n    \"le\": 8804,\n    \"ge\": 8805,\n    \"lE\": 8806,\n    \"gE\": 8807,\n    \"lnE\": 8808,\n    \"gnE\": 8809,\n    \"Lt\": 8810,\n    \"Gt\": 8811,\n    \"twixt\": 8812,\n    \"NotCupCap\": 8813,\n    \"nlt\": 8814,\n    \"ngt\": 8815,\n    \"nle\": 8816,\n    \"nge\": 8817,\n    \"lsim\": 8818,\n    \"gsim\": 8819,\n    \"nlsim\": 8820,\n    \"ngsim\": 8821,\n    \"lg\": 8822,\n    \"gl\": 8823,\n    \"ntlg\": 8824,\n    \"ntgl\": 8825,\n    \"pr\": 8826,\n    \"sc\": 8827,\n    \"prcue\": 8828,\n    \"sccue\": 8829,\n    \"prsim\": 8830,\n    \"scsim\": 8831,\n    \"npr\": 8832,\n    \"nsc\": 8833,\n    \"sub\": 8834,\n    \"sup\": 8835,\n    \"nsub\": 8836,\n    \"nsup\": 8837,\n    \"sube\": 8838,\n    \"supe\": 8839,\n    \"nsube\": 8840,\n    \"nsupe\": 8841,\n    \"subne\": 8842,\n    \"supne\": 8843,\n    \"cupdot\": 8845,\n    \"uplus\": 8846,\n    \"sqsub\": 8847,\n    \"sqsup\": 8848,\n    \"sqsube\": 8849,\n    \"sqsupe\": 8850,\n    \"sqcap\": 8851,\n    \"sqcup\": 8852,\n    \"oplus\": 8853,\n    \"ominus\": 8854,\n    \"otimes\": 8855,\n    \"osol\": 8856,\n    \"odot\": 8857,\n    \"ocir\": 8858,\n    \"oast\": 8859,\n    \"odash\": 8861,\n    \"plusb\": 8862,\n    \"minusb\": 8863,\n    \"timesb\": 8864,\n    \"sdotb\": 8865,\n    \"vdash\": 8866,\n    \"dashv\": 8867,\n    \"top\": 8868,\n    \"perp\": 8869,\n    \"models\": 8871,\n    \"vDash\": 8872,\n    \"Vdash\": 8873,\n    \"Vvdash\": 8874,\n    \"VDash\": 8875,\n    \"nvdash\": 8876,\n    \"nvDash\": 8877,\n    \"nVdash\": 8878,\n    \"nVDash\": 8879,\n    \"prurel\": 8880,\n    \"vltri\": 8882,\n    \"vrtri\": 8883,\n    \"ltrie\": 8884,\n    \"rtrie\": 8885,\n    \"origof\": 8886,\n    \"imof\": 8887,\n    \"mumap\": 8888,\n    \"hercon\": 8889,\n    \"intcal\": 8890,\n    \"veebar\": 8891,\n    \"barvee\": 8893,\n    \"angrtvb\": 8894,\n    \"lrtri\": 8895,\n    \"xwedge\": 8896,\n    \"xvee\": 8897,\n    \"xcap\": 8898,\n    \"xcup\": 8899,\n    \"diam\": 8900,\n    \"sdot\": 8901,\n    \"sstarf\": 8902,\n    \"divonx\": 8903,\n    \"bowtie\": 8904,\n    \"ltimes\": 8905,\n    \"rtimes\": 8906,\n    \"lthree\": 8907,\n    \"rthree\": 8908,\n    \"bsime\": 8909,\n    \"cuvee\": 8910,\n    \"cuwed\": 8911,\n    \"Sub\": 8912,\n    \"Sup\": 8913,\n    \"Cap\": 8914,\n    \"Cup\": 8915,\n    \"fork\": 8916,\n    \"epar\": 8917,\n    \"ltdot\": 8918,\n    \"gtdot\": 8919,\n    \"Ll\": 8920,\n    \"Gg\": 8921,\n    \"leg\": 8922,\n    \"gel\": 8923,\n    \"cuepr\": 8926,\n    \"cuesc\": 8927,\n    \"nprcue\": 8928,\n    \"nsccue\": 8929,\n    \"nsqsube\": 8930,\n    \"nsqsupe\": 8931,\n    \"lnsim\": 8934,\n    \"gnsim\": 8935,\n    \"prnsim\": 8936,\n    \"scnsim\": 8937,\n    \"nltri\": 8938,\n    \"nrtri\": 8939,\n    \"nltrie\": 8940,\n    \"nrtrie\": 8941,\n    \"vellip\": 8942,\n    \"ctdot\": 8943,\n    \"utdot\": 8944,\n    \"dtdot\": 8945,\n    \"disin\": 8946,\n    \"isinsv\": 8947,\n    \"isins\": 8948,\n    \"isindot\": 8949,\n    \"notinvc\": 8950,\n    \"notinvb\": 8951,\n    \"isinE\": 8953,\n    \"nisd\": 8954,\n    \"xnis\": 8955,\n    \"nis\": 8956,\n    \"notnivc\": 8957,\n    \"notnivb\": 8958,\n    \"barwed\": 8965,\n    \"Barwed\": 8966,\n    \"lceil\": 8968,\n    \"rceil\": 8969,\n    \"lfloor\": 8970,\n    \"rfloor\": 8971,\n    \"drcrop\": 8972,\n    \"dlcrop\": 8973,\n    \"urcrop\": 8974,\n    \"ulcrop\": 8975,\n    \"bnot\": 8976,\n    \"profline\": 8978,\n    \"profsurf\": 8979,\n    \"telrec\": 8981,\n    \"target\": 8982,\n    \"ulcorn\": 8988,\n    \"urcorn\": 8989,\n    \"dlcorn\": 8990,\n    \"drcorn\": 8991,\n    \"frown\": 8994,\n    \"smile\": 8995,\n    \"lang\": 9001,\n    \"rang\": 9002,\n    \"cylcty\": 9005,\n    \"profalar\": 9006,\n    \"topbot\": 9014,\n    \"ovbar\": 9021,\n    \"solbar\": 9023,\n    \"angzarr\": 9084,\n    \"lmoust\": 9136,\n    \"rmoust\": 9137,\n    \"tbrk\": 9140,\n    \"bbrk\": 9141,\n    \"bbrktbrk\": 9142,\n    \"OverParenthesis\": 9180,\n    \"UnderParenthesis\": 9181,\n    \"OverBrace\": 9182,\n    \"UnderBrace\": 9183,\n    \"trpezium\": 9186,\n    \"elinters\": 9191,\n    \"blank\": 9251,\n    \"oS\": 9416,\n    \"boxh\": 9472,\n    \"boxv\": 9474,\n    \"boxdr\": 9484,\n    \"boxdl\": 9488,\n    \"boxur\": 9492,\n    \"boxul\": 9496,\n    \"boxvr\": 9500,\n    \"boxvl\": 9508,\n    \"boxhd\": 9516,\n    \"boxhu\": 9524,\n    \"boxvh\": 9532,\n    \"boxH\": 9552,\n    \"boxV\": 9553,\n    \"boxdR\": 9554,\n    \"boxDr\": 9555,\n    \"boxDR\": 9556,\n    \"boxdL\": 9557,\n    \"boxDl\": 9558,\n    \"boxDL\": 9559,\n    \"boxuR\": 9560,\n    \"boxUr\": 9561,\n    \"boxUR\": 9562,\n    \"boxuL\": 9563,\n    \"boxUl\": 9564,\n    \"boxUL\": 9565,\n    \"boxvR\": 9566,\n    \"boxVr\": 9567,\n    \"boxVR\": 9568,\n    \"boxvL\": 9569,\n    \"boxVl\": 9570,\n    \"boxVL\": 9571,\n    \"boxHd\": 9572,\n    \"boxhD\": 9573,\n    \"boxHD\": 9574,\n    \"boxHu\": 9575,\n    \"boxhU\": 9576,\n    \"boxHU\": 9577,\n    \"boxvH\": 9578,\n    \"boxVh\": 9579,\n    \"boxVH\": 9580,\n    \"uhblk\": 9600,\n    \"lhblk\": 9604,\n    \"block\": 9608,\n    \"blk14\": 9617,\n    \"blk12\": 9618,\n    \"blk34\": 9619,\n    \"squ\": 9633,\n    \"squf\": 9642,\n    \"EmptyVerySmallSquare\": 9643,\n    \"rect\": 9645,\n    \"marker\": 9646,\n    \"fltns\": 9649,\n    \"xutri\": 9651,\n    \"utrif\": 9652,\n    \"utri\": 9653,\n    \"rtrif\": 9656,\n    \"rtri\": 9657,\n    \"xdtri\": 9661,\n    \"dtrif\": 9662,\n    \"dtri\": 9663,\n    \"ltrif\": 9666,\n    \"ltri\": 9667,\n    \"loz\": 9674,\n    \"cir\": 9675,\n    \"tridot\": 9708,\n    \"xcirc\": 9711,\n    \"ultri\": 9720,\n    \"urtri\": 9721,\n    \"lltri\": 9722,\n    \"EmptySmallSquare\": 9723,\n    \"FilledSmallSquare\": 9724,\n    \"starf\": 9733,\n    \"bigstar\": 9733,\n    \"star\": 9734,\n    \"phone\": 9742,\n    \"female\": 9792,\n    \"male\": 9794,\n    \"spades\": 9824,\n    \"clubs\": 9827,\n    \"hearts\": 9829,\n    \"diams\": 9830,\n    \"sung\": 9834,\n    \"flat\": 9837,\n    \"natur\": 9838,\n    \"sharp\": 9839,\n    \"check\": 10003,\n    \"cross\": 10007,\n    \"malt\": 10016,\n    \"sext\": 10038,\n    \"VerticalSeparator\": 10072,\n    \"lbbrk\": 10098,\n    \"rbbrk\": 10099,\n    \"lobrk\": 10214,\n    \"robrk\": 10215,\n    \"Lang\": 10218,\n    \"Rang\": 10219,\n    \"loang\": 10220,\n    \"roang\": 10221,\n    \"xlarr\": 10229,\n    \"xrarr\": 10230,\n    \"xharr\": 10231,\n    \"xlArr\": 10232,\n    \"xrArr\": 10233,\n    \"xhArr\": 10234,\n    \"xmap\": 10236,\n    \"dzigrarr\": 10239,\n    \"nvlArr\": 10498,\n    \"nvrArr\": 10499,\n    \"nvHarr\": 10500,\n    \"Map\": 10501,\n    \"lbarr\": 10508,\n    \"rbarr\": 10509,\n    \"lBarr\": 10510,\n    \"rBarr\": 10511,\n    \"RBarr\": 10512,\n    \"DDotrahd\": 10513,\n    \"UpArrowBar\": 10514,\n    \"DownArrowBar\": 10515,\n    \"Rarrtl\": 10518,\n    \"latail\": 10521,\n    \"ratail\": 10522,\n    \"lAtail\": 10523,\n    \"rAtail\": 10524,\n    \"larrfs\": 10525,\n    \"rarrfs\": 10526,\n    \"larrbfs\": 10527,\n    \"rarrbfs\": 10528,\n    \"nwarhk\": 10531,\n    \"nearhk\": 10532,\n    \"searhk\": 10533,\n    \"swarhk\": 10534,\n    \"nwnear\": 10535,\n    \"nesear\": 10536,\n    \"seswar\": 10537,\n    \"swnwar\": 10538,\n    \"rarrc\": 10547,\n    \"cudarrr\": 10549,\n    \"ldca\": 10550,\n    \"rdca\": 10551,\n    \"cudarrl\": 10552,\n    \"larrpl\": 10553,\n    \"curarrm\": 10556,\n    \"cularrp\": 10557,\n    \"rarrpl\": 10565,\n    \"harrcir\": 10568,\n    \"Uarrocir\": 10569,\n    \"lurdshar\": 10570,\n    \"ldrushar\": 10571,\n    \"LeftRightVector\": 10574,\n    \"RightUpDownVector\": 10575,\n    \"DownLeftRightVector\": 10576,\n    \"LeftUpDownVector\": 10577,\n    \"LeftVectorBar\": 10578,\n    \"RightVectorBar\": 10579,\n    \"RightUpVectorBar\": 10580,\n    \"RightDownVectorBar\": 10581,\n    \"DownLeftVectorBar\": 10582,\n    \"DownRightVectorBar\": 10583,\n    \"LeftUpVectorBar\": 10584,\n    \"LeftDownVectorBar\": 10585,\n    \"LeftTeeVector\": 10586,\n    \"RightTeeVector\": 10587,\n    \"RightUpTeeVector\": 10588,\n    \"RightDownTeeVector\": 10589,\n    \"DownLeftTeeVector\": 10590,\n    \"DownRightTeeVector\": 10591,\n    \"LeftUpTeeVector\": 10592,\n    \"LeftDownTeeVector\": 10593,\n    \"lHar\": 10594,\n    \"uHar\": 10595,\n    \"rHar\": 10596,\n    \"dHar\": 10597,\n    \"luruhar\": 10598,\n    \"ldrdhar\": 10599,\n    \"ruluhar\": 10600,\n    \"rdldhar\": 10601,\n    \"lharul\": 10602,\n    \"llhard\": 10603,\n    \"rharul\": 10604,\n    \"lrhard\": 10605,\n    \"udhar\": 10606,\n    \"duhar\": 10607,\n    \"RoundImplies\": 10608,\n    \"erarr\": 10609,\n    \"simrarr\": 10610,\n    \"larrsim\": 10611,\n    \"rarrsim\": 10612,\n    \"rarrap\": 10613,\n    \"ltlarr\": 10614,\n    \"gtrarr\": 10616,\n    \"subrarr\": 10617,\n    \"suplarr\": 10619,\n    \"lfisht\": 10620,\n    \"rfisht\": 10621,\n    \"ufisht\": 10622,\n    \"dfisht\": 10623,\n    \"lopar\": 10629,\n    \"ropar\": 10630,\n    \"lbrke\": 10635,\n    \"rbrke\": 10636,\n    \"lbrkslu\": 10637,\n    \"rbrksld\": 10638,\n    \"lbrksld\": 10639,\n    \"rbrkslu\": 10640,\n    \"langd\": 10641,\n    \"rangd\": 10642,\n    \"lparlt\": 10643,\n    \"rpargt\": 10644,\n    \"gtlPar\": 10645,\n    \"ltrPar\": 10646,\n    \"vzigzag\": 10650,\n    \"vangrt\": 10652,\n    \"angrtvbd\": 10653,\n    \"ange\": 10660,\n    \"range\": 10661,\n    \"dwangle\": 10662,\n    \"uwangle\": 10663,\n    \"angmsdaa\": 10664,\n    \"angmsdab\": 10665,\n    \"angmsdac\": 10666,\n    \"angmsdad\": 10667,\n    \"angmsdae\": 10668,\n    \"angmsdaf\": 10669,\n    \"angmsdag\": 10670,\n    \"angmsdah\": 10671,\n    \"bemptyv\": 10672,\n    \"demptyv\": 10673,\n    \"cemptyv\": 10674,\n    \"raemptyv\": 10675,\n    \"laemptyv\": 10676,\n    \"ohbar\": 10677,\n    \"omid\": 10678,\n    \"opar\": 10679,\n    \"operp\": 10681,\n    \"olcross\": 10683,\n    \"odsold\": 10684,\n    \"olcir\": 10686,\n    \"ofcir\": 10687,\n    \"olt\": 10688,\n    \"ogt\": 10689,\n    \"cirscir\": 10690,\n    \"cirE\": 10691,\n    \"solb\": 10692,\n    \"bsolb\": 10693,\n    \"boxbox\": 10697,\n    \"trisb\": 10701,\n    \"rtriltri\": 10702,\n    \"LeftTriangleBar\": 10703,\n    \"RightTriangleBar\": 10704,\n    \"race\": 10714,\n    \"iinfin\": 10716,\n    \"infintie\": 10717,\n    \"nvinfin\": 10718,\n    \"eparsl\": 10723,\n    \"smeparsl\": 10724,\n    \"eqvparsl\": 10725,\n    \"lozf\": 10731,\n    \"RuleDelayed\": 10740,\n    \"dsol\": 10742,\n    \"xodot\": 10752,\n    \"xoplus\": 10753,\n    \"xotime\": 10754,\n    \"xuplus\": 10756,\n    \"xsqcup\": 10758,\n    \"qint\": 10764,\n    \"fpartint\": 10765,\n    \"cirfnint\": 10768,\n    \"awint\": 10769,\n    \"rppolint\": 10770,\n    \"scpolint\": 10771,\n    \"npolint\": 10772,\n    \"pointint\": 10773,\n    \"quatint\": 10774,\n    \"intlarhk\": 10775,\n    \"pluscir\": 10786,\n    \"plusacir\": 10787,\n    \"simplus\": 10788,\n    \"plusdu\": 10789,\n    \"plussim\": 10790,\n    \"plustwo\": 10791,\n    \"mcomma\": 10793,\n    \"minusdu\": 10794,\n    \"loplus\": 10797,\n    \"roplus\": 10798,\n    \"Cross\": 10799,\n    \"timesd\": 10800,\n    \"timesbar\": 10801,\n    \"smashp\": 10803,\n    \"lotimes\": 10804,\n    \"rotimes\": 10805,\n    \"otimesas\": 10806,\n    \"Otimes\": 10807,\n    \"odiv\": 10808,\n    \"triplus\": 10809,\n    \"triminus\": 10810,\n    \"tritime\": 10811,\n    \"iprod\": 10812,\n    \"amalg\": 10815,\n    \"capdot\": 10816,\n    \"ncup\": 10818,\n    \"ncap\": 10819,\n    \"capand\": 10820,\n    \"cupor\": 10821,\n    \"cupcap\": 10822,\n    \"capcup\": 10823,\n    \"cupbrcap\": 10824,\n    \"capbrcup\": 10825,\n    \"cupcup\": 10826,\n    \"capcap\": 10827,\n    \"ccups\": 10828,\n    \"ccaps\": 10829,\n    \"ccupssm\": 10832,\n    \"And\": 10835,\n    \"Or\": 10836,\n    \"andand\": 10837,\n    \"oror\": 10838,\n    \"orslope\": 10839,\n    \"andslope\": 10840,\n    \"andv\": 10842,\n    \"orv\": 10843,\n    \"andd\": 10844,\n    \"ord\": 10845,\n    \"wedbar\": 10847,\n    \"sdote\": 10854,\n    \"simdot\": 10858,\n    \"congdot\": 10861,\n    \"easter\": 10862,\n    \"apacir\": 10863,\n    \"apE\": 10864,\n    \"eplus\": 10865,\n    \"pluse\": 10866,\n    \"Esim\": 10867,\n    \"Colone\": 10868,\n    \"Equal\": 10869,\n    \"eDDot\": 10871,\n    \"equivDD\": 10872,\n    \"ltcir\": 10873,\n    \"gtcir\": 10874,\n    \"ltquest\": 10875,\n    \"gtquest\": 10876,\n    \"les\": 10877,\n    \"ges\": 10878,\n    \"lesdot\": 10879,\n    \"gesdot\": 10880,\n    \"lesdoto\": 10881,\n    \"gesdoto\": 10882,\n    \"lesdotor\": 10883,\n    \"gesdotol\": 10884,\n    \"lap\": 10885,\n    \"gap\": 10886,\n    \"lne\": 10887,\n    \"gne\": 10888,\n    \"lnap\": 10889,\n    \"gnap\": 10890,\n    \"lEg\": 10891,\n    \"gEl\": 10892,\n    \"lsime\": 10893,\n    \"gsime\": 10894,\n    \"lsimg\": 10895,\n    \"gsiml\": 10896,\n    \"lgE\": 10897,\n    \"glE\": 10898,\n    \"lesges\": 10899,\n    \"gesles\": 10900,\n    \"els\": 10901,\n    \"egs\": 10902,\n    \"elsdot\": 10903,\n    \"egsdot\": 10904,\n    \"el\": 10905,\n    \"eg\": 10906,\n    \"siml\": 10909,\n    \"simg\": 10910,\n    \"simlE\": 10911,\n    \"simgE\": 10912,\n    \"LessLess\": 10913,\n    \"GreaterGreater\": 10914,\n    \"glj\": 10916,\n    \"gla\": 10917,\n    \"ltcc\": 10918,\n    \"gtcc\": 10919,\n    \"lescc\": 10920,\n    \"gescc\": 10921,\n    \"smt\": 10922,\n    \"lat\": 10923,\n    \"smte\": 10924,\n    \"late\": 10925,\n    \"bumpE\": 10926,\n    \"pre\": 10927,\n    \"sce\": 10928,\n    \"prE\": 10931,\n    \"scE\": 10932,\n    \"prnE\": 10933,\n    \"scnE\": 10934,\n    \"prap\": 10935,\n    \"scap\": 10936,\n    \"prnap\": 10937,\n    \"scnap\": 10938,\n    \"Pr\": 10939,\n    \"Sc\": 10940,\n    \"subdot\": 10941,\n    \"supdot\": 10942,\n    \"subplus\": 10943,\n    \"supplus\": 10944,\n    \"submult\": 10945,\n    \"supmult\": 10946,\n    \"subedot\": 10947,\n    \"supedot\": 10948,\n    \"subE\": 10949,\n    \"supE\": 10950,\n    \"subsim\": 10951,\n    \"supsim\": 10952,\n    \"subnE\": 10955,\n    \"supnE\": 10956,\n    \"csub\": 10959,\n    \"csup\": 10960,\n    \"csube\": 10961,\n    \"csupe\": 10962,\n    \"subsup\": 10963,\n    \"supsub\": 10964,\n    \"subsub\": 10965,\n    \"supsup\": 10966,\n    \"suphsub\": 10967,\n    \"supdsub\": 10968,\n    \"forkv\": 10969,\n    \"topfork\": 10970,\n    \"mlcp\": 10971,\n    \"Dashv\": 10980,\n    \"Vdashl\": 10982,\n    \"Barv\": 10983,\n    \"vBar\": 10984,\n    \"vBarv\": 10985,\n    \"Vbar\": 10987,\n    \"Not\": 10988,\n    \"bNot\": 10989,\n    \"rnmid\": 10990,\n    \"cirmid\": 10991,\n    \"midcir\": 10992,\n    \"topcir\": 10993,\n    \"nhpar\": 10994,\n    \"parsim\": 10995,\n    \"parsl\": 11005,\n    \"fflig\": 64256,\n    \"filig\": 64257,\n    \"fllig\": 64258,\n    \"ffilig\": 64259,\n    \"ffllig\": 64260,\n    \"Ascr\": 119964,\n    \"Cscr\": 119966,\n    \"Dscr\": 119967,\n    \"Gscr\": 119970,\n    \"Jscr\": 119973,\n    \"Kscr\": 119974,\n    \"Nscr\": 119977,\n    \"Oscr\": 119978,\n    \"Pscr\": 119979,\n    \"Qscr\": 119980,\n    \"Sscr\": 119982,\n    \"Tscr\": 119983,\n    \"Uscr\": 119984,\n    \"Vscr\": 119985,\n    \"Wscr\": 119986,\n    \"Xscr\": 119987,\n    \"Yscr\": 119988,\n    \"Zscr\": 119989,\n    \"ascr\": 119990,\n    \"bscr\": 119991,\n    \"cscr\": 119992,\n    \"dscr\": 119993,\n    \"fscr\": 119995,\n    \"hscr\": 119997,\n    \"iscr\": 119998,\n    \"jscr\": 119999,\n    \"kscr\": 120000,\n    \"lscr\": 120001,\n    \"mscr\": 120002,\n    \"nscr\": 120003,\n    \"pscr\": 120005,\n    \"qscr\": 120006,\n    \"rscr\": 120007,\n    \"sscr\": 120008,\n    \"tscr\": 120009,\n    \"uscr\": 120010,\n    \"vscr\": 120011,\n    \"wscr\": 120012,\n    \"xscr\": 120013,\n    \"yscr\": 120014,\n    \"zscr\": 120015,\n    \"Afr\": 120068,\n    \"Bfr\": 120069,\n    \"Dfr\": 120071,\n    \"Efr\": 120072,\n    \"Ffr\": 120073,\n    \"Gfr\": 120074,\n    \"Jfr\": 120077,\n    \"Kfr\": 120078,\n    \"Lfr\": 120079,\n    \"Mfr\": 120080,\n    \"Nfr\": 120081,\n    \"Ofr\": 120082,\n    \"Pfr\": 120083,\n    \"Qfr\": 120084,\n    \"Sfr\": 120086,\n    \"Tfr\": 120087,\n    \"Ufr\": 120088,\n    \"Vfr\": 120089,\n    \"Wfr\": 120090,\n    \"Xfr\": 120091,\n    \"Yfr\": 120092,\n    \"afr\": 120094,\n    \"bfr\": 120095,\n    \"cfr\": 120096,\n    \"dfr\": 120097,\n    \"efr\": 120098,\n    \"ffr\": 120099,\n    \"gfr\": 120100,\n    \"hfr\": 120101,\n    \"ifr\": 120102,\n    \"jfr\": 120103,\n    \"kfr\": 120104,\n    \"lfr\": 120105,\n    \"mfr\": 120106,\n    \"nfr\": 120107,\n    \"ofr\": 120108,\n    \"pfr\": 120109,\n    \"qfr\": 120110,\n    \"rfr\": 120111,\n    \"sfr\": 120112,\n    \"tfr\": 120113,\n    \"ufr\": 120114,\n    \"vfr\": 120115,\n    \"wfr\": 120116,\n    \"xfr\": 120117,\n    \"yfr\": 120118,\n    \"zfr\": 120119,\n    \"Aopf\": 120120,\n    \"Bopf\": 120121,\n    \"Dopf\": 120123,\n    \"Eopf\": 120124,\n    \"Fopf\": 120125,\n    \"Gopf\": 120126,\n    \"Iopf\": 120128,\n    \"Jopf\": 120129,\n    \"Kopf\": 120130,\n    \"Lopf\": 120131,\n    \"Mopf\": 120132,\n    \"Oopf\": 120134,\n    \"Sopf\": 120138,\n    \"Topf\": 120139,\n    \"Uopf\": 120140,\n    \"Vopf\": 120141,\n    \"Wopf\": 120142,\n    \"Xopf\": 120143,\n    \"Yopf\": 120144,\n    \"aopf\": 120146,\n    \"bopf\": 120147,\n    \"copf\": 120148,\n    \"dopf\": 120149,\n    \"eopf\": 120150,\n    \"fopf\": 120151,\n    \"gopf\": 120152,\n    \"hopf\": 120153,\n    \"iopf\": 120154,\n    \"jopf\": 120155,\n    \"kopf\": 120156,\n    \"lopf\": 120157,\n    \"mopf\": 120158,\n    \"nopf\": 120159,\n    \"oopf\": 120160,\n    \"popf\": 120161,\n    \"qopf\": 120162,\n    \"ropf\": 120163,\n    \"sopf\": 120164,\n    \"topf\": 120165,\n    \"uopf\": 120166,\n    \"vopf\": 120167,\n    \"wopf\": 120168,\n    \"xopf\": 120169,\n    \"yopf\": 120170,\n    \"zopf\": 120171\n};\n\nexport default FromHTMLEntity;\n"
  },
  {
    "path": "src/core/operations/FromHex.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {fromHex, FROM_HEX_DELIM_OPTIONS} from \"../lib/Hex.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * From Hex operation\n */\nclass FromHex extends Operation {\n\n    /**\n     * FromHex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Hex\";\n        this.module = \"Default\";\n        this.description = \"Converts a hexadecimal byte string back into its raw value.<br><br>e.g. <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code> becomes the UTF-8 encoded string <code>Γειά σου</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Hexadecimal\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: FROM_HEX_DELIM_OPTIONS\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:[\\\\dA-F]{2})+$\",\n                flags: \"i\",\n                args: [\"None\"]\n            },\n            {\n                pattern: \"^[\\\\dA-F]{2}(?: [\\\\dA-F]{2})*$\",\n                flags: \"i\",\n                args: [\"Space\"]\n            },\n            {\n                pattern: \"^[\\\\dA-F]{2}(?:,[\\\\dA-F]{2})*$\",\n                flags: \"i\",\n                args: [\"Comma\"]\n            },\n            {\n                pattern: \"^[\\\\dA-F]{2}(?:;[\\\\dA-F]{2})*$\",\n                flags: \"i\",\n                args: [\"Semi-colon\"]\n            },\n            {\n                pattern: \"^[\\\\dA-F]{2}(?::[\\\\dA-F]{2})*$\",\n                flags: \"i\",\n                args: [\"Colon\"]\n            },\n            {\n                pattern: \"^[\\\\dA-F]{2}(?:\\\\n[\\\\dA-F]{2})*$\",\n                flags: \"i\",\n                args: [\"Line feed\"]\n            },\n            {\n                pattern: \"^[\\\\dA-F]{2}(?:\\\\r\\\\n[\\\\dA-F]{2})*$\",\n                flags: \"i\",\n                args: [\"CRLF\"]\n            },\n            {\n                pattern: \"^(?:0x[\\\\dA-F]{2})+$\",\n                flags: \"i\",\n                args: [\"0x\"]\n            },\n            {\n                pattern: \"^0x[\\\\dA-F]{2}(?:,0x[\\\\dA-F]{2})*$\",\n                flags: \"i\",\n                args: [\"0x with comma\"]\n            },\n            {\n                pattern: \"^(?:\\\\\\\\x[\\\\dA-F]{2})+$\",\n                flags: \"i\",\n                args: [\"\\\\x\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const delim = args[0] || \"Auto\";\n        return fromHex(input, delim, 2);\n    }\n\n    /**\n     * Highlight to Hex\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        if (args[0] === \"Auto\") return false;\n        const delim = Utils.charRep(args[0] || \"Space\"),\n            len = delim === \"\\r\\n\" ? 1 : delim.length,\n            width = len + 2;\n\n        // 0x and \\x are added to the beginning if they are selected, so increment the positions accordingly\n        if (delim === \"0x\" || delim === \"\\\\x\") {\n            if (pos[0].start > 1) pos[0].start -= 2;\n            else pos[0].start = 0;\n            if (pos[0].end > 1) pos[0].end -= 2;\n            else pos[0].end = 0;\n        }\n\n        pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width);\n        pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width);\n        return pos;\n    }\n\n    /**\n     * Highlight from Hex\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        const delim = Utils.charRep(args[0] || \"Space\"),\n            len = delim === \"\\r\\n\" ? 1 : delim.length;\n\n        pos[0].start = pos[0].start * (2 + len);\n        pos[0].end = pos[0].end * (2 + len) - len;\n\n        // 0x and \\x are added to the beginning if they are selected, so increment the positions accordingly\n        if (delim === \"0x\" || delim === \"\\\\x\") {\n            pos[0].start += 2;\n            pos[0].end += 2;\n        }\n        return pos;\n    }\n}\n\nexport default FromHex;\n"
  },
  {
    "path": "src/core/operations/FromHexContent.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {fromHex} from \"../lib/Hex.mjs\";\n\n/**\n * From Hex Content operation\n */\nclass FromHexContent extends Operation {\n\n    /**\n     * FromHexContent constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Hex Content\";\n        this.module = \"Default\";\n        this.description = \"Translates hexadecimal bytes in text back to raw bytes. This format is used by SNORT for representing hex within ASCII text.<br><br>e.g. <code>foo|3d|bar</code> becomes <code>foo=bar</code>.\";\n        this.infoURL = \"http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern:  \"\\\\|([\\\\da-f]{2} ?)+\\\\|\",\n                flags:  \"i\",\n                args:   []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const regex = /\\|([a-f\\d ]{2,})\\|/gi,\n            output = [];\n        let m, i = 0;\n        while ((m = regex.exec(input))) {\n            // Add up to match\n            for (; i < m.index;)\n                output.push(Utils.ord(input[i++]));\n\n            // Add match\n            const bytes = fromHex(m[1]);\n            if (bytes) {\n                for (let a = 0; a < bytes.length;)\n                    output.push(bytes[a++]);\n            } else {\n                // Not valid hex, print as normal\n                for (; i < regex.lastIndex;)\n                    output.push(Utils.ord(input[i++]));\n            }\n\n            i = regex.lastIndex;\n        }\n        // Add all after final match\n        for (; i < input.length;)\n            output.push(Utils.ord(input[i++]));\n\n        return output;\n    }\n\n}\n\nexport default FromHexContent;\n"
  },
  {
    "path": "src/core/operations/FromHexdump.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { fromHex } from \"../lib/Hex.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n\n/**\n * From Hexdump operation\n */\nclass FromHexdump extends Operation {\n\n    /**\n     * FromHexdump constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Hexdump\";\n        this.module = \"Default\";\n        this.description = \"Attempts to convert a hexdump back into raw data. This operation supports many different hexdump variations, but probably not all. Make sure you verify that the data it gives you is correct before continuing analysis.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Hex_dump\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern: \"^(?:(?:[\\\\dA-F]{4,16}h?:?)?[ \\\\t]*((?:[\\\\dA-F]{2} ){1,8}(?:[ \\\\t]|[\\\\dA-F]{2}-)(?:[\\\\dA-F]{2} ){1,8}|(?:[\\\\dA-F]{4} )*[\\\\dA-F]{4}|(?:[\\\\dA-F]{2} )*[\\\\dA-F]{2})[^\\\\n]*\\\\n?){2,}$\",\n                flags: \"i\",\n                args: []\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const output = [],\n            regex = /^\\s*(?:[\\dA-F]{4,16}h?:?)?[ \\t]+((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )+(?:[\\dA-F]{2})?|(?:[\\dA-F]{2} )*[\\dA-F]{2})/igm;\n        let block, line;\n\n        while ((block = regex.exec(input))) {\n            line = fromHex(block[1].replace(/-/g, \" \"));\n            for (let i = 0; i < line.length; i++) {\n                output.push(line[i]);\n            }\n        }\n        // Is this a CyberChef hexdump or is it from a different tool?\n        const width = input.indexOf(\"\\n\");\n        const w = (width - 13) / 4;\n        // w should be the specified width of the hexdump and therefore a round number\n        if (Math.floor(w) !== w || input.indexOf(\"\\r\") !== -1 || output.indexOf(13) !== -1) {\n            if (isWorkerEnvironment()) self.setOption(\"attemptHighlight\", false);\n        }\n        return output;\n    }\n\n    /**\n     * Highlight From Hexdump\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        const w = args[0] || 16;\n        const width = 14 + (w*4);\n\n        let line = Math.floor(pos[0].start / width);\n        let offset = pos[0].start % width;\n\n        if (offset < 10) { // In line number section\n            pos[0].start = line*w;\n        } else if (offset > 10+(w*3)) { // In ASCII section\n            pos[0].start = (line+1)*w;\n        } else { // In byte section\n            pos[0].start = line*w + Math.floor((offset-10)/3);\n        }\n\n        line = Math.floor(pos[0].end / width);\n        offset = pos[0].end % width;\n\n        if (offset < 10) { // In line number section\n            pos[0].end = line*w;\n        } else if (offset > 10+(w*3)) { // In ASCII section\n            pos[0].end = (line+1)*w;\n        } else { // In byte section\n            pos[0].end = line*w + Math.ceil((offset-10)/3);\n        }\n\n        return pos;\n    }\n\n    /**\n     * Highlight From Hexdump in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        // Calculate overall selection\n        const w = args[0] || 16,\n            width = 14 + (w*4);\n        let line = Math.floor(pos[0].start / w),\n            offset = pos[0].start % w,\n            start = 0,\n            end = 0;\n\n        pos[0].start = line*width + 10 + offset*3;\n\n        line = Math.floor(pos[0].end / w);\n        offset = pos[0].end % w;\n        if (offset === 0) {\n            line--;\n            offset = w;\n        }\n        pos[0].end = line*width + 10 + offset*3 - 1;\n\n        // Set up multiple selections for bytes\n        let startLineNum = Math.floor(pos[0].start / width);\n        const endLineNum = Math.floor(pos[0].end / width);\n\n        if (startLineNum === endLineNum) {\n            pos.push(pos[0]);\n        } else {\n            start = pos[0].start;\n            end = (startLineNum+1) * width - w - 5;\n            pos.push({ start: start, end: end });\n            while (end < pos[0].end) {\n                startLineNum++;\n                start = startLineNum * width + 10;\n                end = (startLineNum+1) * width - w - 5;\n                if (end > pos[0].end) end = pos[0].end;\n                pos.push({ start: start, end: end });\n            }\n        }\n\n        // Set up multiple selections for ASCII\n        const len = pos.length;\n        let lineNum = 0;\n        start = 0;\n        end = 0;\n        for (let i = 1; i < len; i++) {\n            lineNum = Math.floor(pos[i].start / width);\n            start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width);\n            end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width);\n            pos.push({ start: start, end: end });\n        }\n        return pos;\n    }\n\n}\n\nexport default FromHexdump;\n"
  },
  {
    "path": "src/core/operations/FromMessagePack.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport notepack from \"notepack.io\";\n\n/**\n * From MessagePack operation\n */\nclass FromMessagePack extends Operation {\n\n    /**\n     * FromMessagePack constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From MessagePack\";\n        this.module = \"Code\";\n        this.description = \"Converts MessagePack encoded data to JSON. MessagePack is a computer data interchange format. It is a binary form for representing simple data structures like arrays and associative arrays.\";\n        this.infoURL = \"https://wikipedia.org/wiki/MessagePack\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        try {\n            const buf = Buffer.from(new Uint8Array(input));\n            return notepack.decode(buf);\n        } catch (err) {\n            throw new OperationError(`Could not decode MessagePack to JSON: ${err}`);\n        }\n    }\n\n}\n\nexport default FromMessagePack;\n"
  },
  {
    "path": "src/core/operations/FromModhex.mjs",
    "content": "/**\n * @author linuxgemini [ilteris@asenkron.com.tr]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { FROM_MODHEX_DELIM_OPTIONS, fromModhex } from \"../lib/Modhex.mjs\";\n\n/**\n * From Modhex operation\n */\nclass FromModhex extends Operation {\n\n    /**\n     * FromModhex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Modhex\";\n        this.module = \"Default\";\n        this.description = \"Converts a modhex byte string back into its raw value.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/YubiKey#ModHex\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: FROM_MODHEX_DELIM_OPTIONS\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:[cbdefghijklnrtuv]{2})+$\",\n                flags: \"i\",\n                args: [\"None\"]\n            },\n            {\n                pattern: \"^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$\",\n                flags: \"i\",\n                args: [\"Space\"]\n            },\n            {\n                pattern: \"^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$\",\n                flags: \"i\",\n                args: [\"Comma\"]\n            },\n            {\n                pattern: \"^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$\",\n                flags: \"i\",\n                args: [\"Semi-colon\"]\n            },\n            {\n                pattern: \"^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$\",\n                flags: \"i\",\n                args: [\"Colon\"]\n            },\n            {\n                pattern: \"^[cbdefghijklnrtuv]{2}(?:\\\\n[cbdefghijklnrtuv]{2})*$\",\n                flags: \"i\",\n                args: [\"Line feed\"]\n            },\n            {\n                pattern: \"^[cbdefghijklnrtuv]{2}(?:\\\\r\\\\n[cbdefghijklnrtuv]{2})*$\",\n                flags: \"i\",\n                args: [\"CRLF\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const delim = args[0] || \"Auto\";\n        return fromModhex(input, delim, 2);\n    }\n}\n\nexport default FromModhex;\n"
  },
  {
    "path": "src/core/operations/FromMorseCode.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * From Morse Code operation\n */\nclass FromMorseCode extends Operation {\n\n    /**\n     * FromMorseCode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Morse Code\";\n        this.module = \"Default\";\n        this.description = \"Translates Morse Code into (upper case) alphanumeric characters.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Morse_code\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Letter delimiter\",\n                \"type\": \"option\",\n                \"value\": LETTER_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Word delimiter\",\n                \"type\": \"option\",\n                \"value\": WORD_DELIM_OPTIONS\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"(?:^[-. \\\\n]{5,}$|^[_. \\\\n]{5,}$|^(?:dash|dot| |\\\\n){5,}$)\",\n                flags: \"i\",\n                args: [\"Space\", \"Line feed\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!this.reversedTable) {\n            this.reverseTable();\n        }\n\n        const letterDelim = Utils.charRep(args[0]);\n        const wordDelim = Utils.charRep(args[1]);\n\n        input = input.replace(/-|‐|−|_|–|—|dash/ig, \"<dash>\"); // hyphen-minus|hyphen|minus-sign|undersore|en-dash|em-dash\n        input = input.replace(/\\.|·|dot/ig, \"<dot>\");\n\n        let words = input.split(wordDelim);\n        const self = this;\n        words = Array.prototype.map.call(words, function(word) {\n            const signals = word.split(letterDelim);\n\n            const letters = signals.map(function(signal) {\n                return self.reversedTable[signal];\n            });\n\n            return letters.join(\"\");\n        });\n        words = words.join(\" \");\n\n        return words;\n    }\n\n\n    /**\n     * Reverses the Morse Code lookup table\n     */\n    reverseTable() {\n        this.reversedTable = {};\n\n        for (const letter in MORSE_TABLE) {\n            const signal = MORSE_TABLE[letter];\n            this.reversedTable[signal] = letter;\n        }\n    }\n\n}\n\nconst MORSE_TABLE = {\n    \"A\": \"<dot><dash>\",\n    \"B\": \"<dash><dot><dot><dot>\",\n    \"C\": \"<dash><dot><dash><dot>\",\n    \"D\": \"<dash><dot><dot>\",\n    \"E\": \"<dot>\",\n    \"F\": \"<dot><dot><dash><dot>\",\n    \"G\": \"<dash><dash><dot>\",\n    \"H\": \"<dot><dot><dot><dot>\",\n    \"I\": \"<dot><dot>\",\n    \"J\": \"<dot><dash><dash><dash>\",\n    \"K\": \"<dash><dot><dash>\",\n    \"L\": \"<dot><dash><dot><dot>\",\n    \"M\": \"<dash><dash>\",\n    \"N\": \"<dash><dot>\",\n    \"O\": \"<dash><dash><dash>\",\n    \"P\": \"<dot><dash><dash><dot>\",\n    \"Q\": \"<dash><dash><dot><dash>\",\n    \"R\": \"<dot><dash><dot>\",\n    \"S\": \"<dot><dot><dot>\",\n    \"T\": \"<dash>\",\n    \"U\": \"<dot><dot><dash>\",\n    \"V\": \"<dot><dot><dot><dash>\",\n    \"W\": \"<dot><dash><dash>\",\n    \"X\": \"<dash><dot><dot><dash>\",\n    \"Y\": \"<dash><dot><dash><dash>\",\n    \"Z\": \"<dash><dash><dot><dot>\",\n    \"1\": \"<dot><dash><dash><dash><dash>\",\n    \"2\": \"<dot><dot><dash><dash><dash>\",\n    \"3\": \"<dot><dot><dot><dash><dash>\",\n    \"4\": \"<dot><dot><dot><dot><dash>\",\n    \"5\": \"<dot><dot><dot><dot><dot>\",\n    \"6\": \"<dash><dot><dot><dot><dot>\",\n    \"7\": \"<dash><dash><dot><dot><dot>\",\n    \"8\": \"<dash><dash><dash><dot><dot>\",\n    \"9\": \"<dash><dash><dash><dash><dot>\",\n    \"0\": \"<dash><dash><dash><dash><dash>\",\n    \".\": \"<dot><dash><dot><dash><dot><dash>\",\n    \",\": \"<dash><dash><dot><dot><dash><dash>\",\n    \":\": \"<dash><dash><dash><dot><dot><dot>\",\n    \";\": \"<dash><dot><dash><dot><dash><dot>\",\n    \"!\": \"<dash><dot><dash><dot><dash><dash>\",\n    \"?\": \"<dot><dot><dash><dash><dot><dot>\",\n    \"'\": \"<dot><dash><dash><dash><dash><dot>\",\n    \"\\\"\": \"<dot><dash><dot><dot><dash><dot>\",\n    \"/\": \"<dash><dot><dot><dash><dot>\",\n    \"-\": \"<dash><dot><dot><dot><dot><dash>\",\n    \"+\": \"<dot><dash><dot><dash><dot>\",\n    \"(\": \"<dash><dot><dash><dash><dot>\",\n    \")\": \"<dash><dot><dash><dash><dot><dash>\",\n    \"@\": \"<dot><dash><dash><dot><dash><dot>\",\n    \"=\": \"<dash><dot><dot><dot><dash>\",\n    \"&\": \"<dot><dash><dot><dot><dot>\",\n    \"_\": \"<dot><dot><dash><dash><dot><dash>\",\n    \"$\": \"<dot><dot><dot><dash><dot><dot><dash>\",\n    \" \": \"<dot><dot><dot><dot><dot><dot><dot>\"\n};\n\nexport default FromMorseCode;\n"
  },
  {
    "path": "src/core/operations/FromOctal.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * From Octal operation\n */\nclass FromOctal extends Operation {\n\n    /**\n     * FromOctal constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Octal\";\n        this.module = \"Default\";\n        this.description = \"Converts an octal byte string back into its raw value.<br><br>e.g. <code>316 223 316 265 316 271 316 254 40 317 203 316 277 317 205</code> becomes the UTF-8 encoded string <code>Γειά σου</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Octal\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$\",\n                flags: \"\",\n                args: [\"Space\"]\n            },\n            {\n                pattern: \"^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$\",\n                flags: \"\",\n                args: [\"Comma\"]\n            },\n            {\n                pattern: \"^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$\",\n                flags: \"\",\n                args: [\"Semi-colon\"]\n            },\n            {\n                pattern: \"^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$\",\n                flags: \"\",\n                args: [\"Colon\"]\n            },\n            {\n                pattern: \"^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$\",\n                flags: \"\",\n                args: [\"Line feed\"]\n            },\n            {\n                pattern: \"^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\\\r\\\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$\",\n                flags: \"\",\n                args: [\"CRLF\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n        if (input.length === 0) return [];\n        return input.split(delim).map(val => parseInt(val, 8));\n    }\n\n}\n\nexport default FromOctal;\n"
  },
  {
    "path": "src/core/operations/FromPunycode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport punycode from \"punycode\";\n\n/**\n * From Punycode operation\n */\nclass FromPunycode extends Operation {\n\n    /**\n     * FromPunycode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Punycode\";\n        this.module = \"Encodings\";\n        this.description = \"Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.<br><br>e.g. <code>mnchen-3ya</code> decodes to <code>m\\xfcnchen</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Punycode\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Internationalised domain name\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const idn = args[0];\n\n        if (idn) {\n            return punycode.toUnicode(input);\n        } else {\n            return punycode.decode(input);\n        }\n    }\n\n}\n\nexport default FromPunycode;\n"
  },
  {
    "path": "src/core/operations/FromQuotedPrintable.mjs",
    "content": "/**\n * Some parts taken from mimelib (http://github.com/andris9/mimelib)\n * @author Andris Reinman\n * @license MIT\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * From Quoted Printable operation\n */\nclass FromQuotedPrintable extends Operation {\n\n    /**\n     * FromQuotedPrintable constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From Quoted Printable\";\n        this.module = \"Default\";\n        this.description = \"Converts QP-encoded text back to standard text. This format is a content transfer encoding common in email messages.<br><br>e.g. The quoted-printable encoded string <code>hello=20world</code> becomes <code>hello world</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Quoted-printable\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern: \"^[\\\\x21-\\\\x3d\\\\x3f-\\\\x7e \\\\t]{0,76}(?:=[\\\\da-f]{2}|=\\\\r?\\\\n)(?:[\\\\x21-\\\\x3d\\\\x3f-\\\\x7e \\\\t]|=[\\\\da-f]{2}|=\\\\r?\\\\n)*$\",\n                flags: \"i\",\n                args: []\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const str = input.replace(/=(?:\\r?\\n|$)/g, \"\");\n\n        const encodedBytesCount = (str.match(/=[\\da-fA-F]{2}/g) || []).length,\n            bufferLength = str.length - encodedBytesCount * 2,\n            buffer = new Array(bufferLength);\n        let chr, hex,\n            bufferPos = 0;\n\n        for (let i = 0, len = str.length; i < len; i++) {\n            chr = str.charAt(i);\n            if (chr === \"=\" && (hex = str.substr(i + 1, 2)) && /[\\da-fA-F]{2}/.test(hex)) {\n                buffer[bufferPos++] = parseInt(hex, 16);\n                i += 2;\n                continue;\n            }\n            buffer[bufferPos++] = chr.charCodeAt(0);\n        }\n\n        return buffer;\n    }\n\n}\n\nexport default FromQuotedPrintable;\n"
  },
  {
    "path": "src/core/operations/FromUNIXTimestamp.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport moment from \"moment-timezone\";\nimport {UNITS} from \"../lib/DateTime.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * From UNIX Timestamp operation\n */\nclass FromUNIXTimestamp extends Operation {\n\n    /**\n     * FromUNIXTimestamp constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"From UNIX Timestamp\";\n        this.module = \"Default\";\n        this.description = \"Converts a UNIX timestamp to a datetime string.<br><br>e.g. <code>978346800</code> becomes <code>Mon 1 January 2001 11:00:00 UTC</code><br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Unix_time\";\n        this.inputType = \"number\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Units\",\n                \"type\": \"option\",\n                \"value\": UNITS\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^1?\\\\d{9}$\",\n                flags: \"\",\n                args: [\"Seconds (s)\"]\n            },\n            {\n                pattern: \"^1?\\\\d{12}$\",\n                flags: \"\",\n                args: [\"Milliseconds (ms)\"]\n            },\n            {\n                pattern: \"^1?\\\\d{15}$\",\n                flags: \"\",\n                args: [\"Microseconds (μs)\"]\n            },\n            {\n                pattern: \"^1?\\\\d{18}$\",\n                flags: \"\",\n                args: [\"Nanoseconds (ns)\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {number} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if invalid unit\n     */\n    run(input, args) {\n        const units = args[0];\n        let d;\n\n        input = parseFloat(input);\n\n        if (units === \"Seconds (s)\") {\n            d = moment.unix(input);\n            return d.tz(\"UTC\").format(\"ddd D MMMM YYYY HH:mm:ss\") + \" UTC\";\n        } else if (units === \"Milliseconds (ms)\") {\n            d = moment(input);\n            return d.tz(\"UTC\").format(\"ddd D MMMM YYYY HH:mm:ss.SSS\") + \" UTC\";\n        } else if (units === \"Microseconds (μs)\") {\n            d = moment(input / 1000);\n            return d.tz(\"UTC\").format(\"ddd D MMMM YYYY HH:mm:ss.SSS\") + \" UTC\";\n        } else if (units === \"Nanoseconds (ns)\") {\n            d = moment(input / 1000000);\n            return d.tz(\"UTC\").format(\"ddd D MMMM YYYY HH:mm:ss.SSS\") + \" UTC\";\n        } else {\n            throw new OperationError(\"Unrecognised unit\");\n        }\n    }\n\n}\n\nexport default FromUNIXTimestamp;\n"
  },
  {
    "path": "src/core/operations/FuzzyMatch.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {fuzzyMatch, calcMatchRanges, DEFAULT_WEIGHTS} from \"../lib/FuzzyMatch.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Fuzzy Match operation\n */\nclass FuzzyMatch extends Operation {\n\n    /**\n     * FuzzyMatch constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Fuzzy Match\";\n        this.module = \"Default\";\n        this.description = \"Conducts a fuzzy search to find a pattern within the input based on weighted criteria.<br><br>e.g. A search for <code>dpan</code> will match on <code><b>D</b>on't <b>Pan</b>ic</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Fuzzy_matching_(computer-assisted_translation)\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                name: \"Search\",\n                type: \"binaryString\",\n                value: \"\"\n            },\n            {\n                name: \"Sequential bonus\",\n                type: \"number\",\n                value: DEFAULT_WEIGHTS.sequentialBonus,\n                hint: \"Bonus for adjacent matches\"\n            },\n            {\n                name: \"Separator bonus\",\n                type: \"number\",\n                value: DEFAULT_WEIGHTS.separatorBonus,\n                hint: \"Bonus if match occurs after a separator\"\n            },\n            {\n                name: \"Camel bonus\",\n                type: \"number\",\n                value: DEFAULT_WEIGHTS.camelBonus,\n                hint: \"Bonus if match is uppercase and previous is lower\"\n            },\n            {\n                name: \"First letter bonus\",\n                type: \"number\",\n                value: DEFAULT_WEIGHTS.firstLetterBonus,\n                hint: \"Bonus if the first letter is matched\"\n            },\n            {\n                name: \"Leading letter penalty\",\n                type: \"number\",\n                value: DEFAULT_WEIGHTS.leadingLetterPenalty,\n                hint: \"Penalty applied for every letter in the input before the first match\"\n            },\n            {\n                name: \"Max leading letter penalty\",\n                type: \"number\",\n                value: DEFAULT_WEIGHTS.maxLeadingLetterPenalty,\n                hint: \"Maxiumum penalty for leading letters\"\n            },\n            {\n                name: \"Unmatched letter penalty\",\n                type: \"number\",\n                value: DEFAULT_WEIGHTS.unmatchedLetterPenalty\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const searchStr = args[0];\n        const weights = {\n            sequentialBonus: args[1],\n            separatorBonus: args[2],\n            camelBonus: args[3],\n            firstLetterBonus: args[4],\n            leadingLetterPenalty: args[5],\n            maxLeadingLetterPenalty: args[6],\n            unmatchedLetterPenalty: args[7]\n        };\n        const matches = fuzzyMatch(searchStr, input, true, weights);\n\n        if (!matches) {\n            return \"No matches.\";\n        }\n\n        let result = \"\", pos = 0, hlClass = \"hl1\";\n        matches.forEach(([matches, score, idxs]) => {\n            const matchRanges = calcMatchRanges(idxs);\n\n            matchRanges.forEach(([start, length], i) => {\n                result += Utils.escapeHtml(input.slice(pos, start));\n                if (i === 0) result += `<span class=\"${hlClass}\">`;\n                pos = start + length;\n                result += `<b>${Utils.escapeHtml(input.slice(start, pos))}</b>`;\n            });\n            result += \"</span>\";\n            hlClass = hlClass === \"hl1\" ? \"hl2\" : \"hl1\";\n        });\n\n        result += Utils.escapeHtml(input.slice(pos, input.length));\n\n        return result;\n    }\n\n}\n\nexport default FuzzyMatch;\n"
  },
  {
    "path": "src/core/operations/GOSTDecrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast, fromHex } from \"../lib/Hex.mjs\";\nimport { CryptoGost, GostEngine } from \"@wavesenterprise/crypto-gost-js/index.js\";\n\n/**\n * GOST Decrypt operation\n */\nclass GOSTDecrypt extends Operation {\n\n    /**\n     * GOSTDecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"GOST Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.<br><br>Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure.\";\n        this.infoURL = \"https://wikipedia.org/wiki/GOST_(block_cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"IV\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Input type\",\n                type: \"option\",\n                value: [\"Hex\", \"Raw\"]\n            },\n            {\n                name: \"Output type\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"GOST 28147 (1989)\",\n                        on: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Magma, 2015)\",\n                        off: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Kuznyechik, 2015)\",\n                        off: [5]\n                    }\n                ]\n            },\n            {\n                name: \"sBox\",\n                type: \"option\",\n                value: [\"E-TEST\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"E-SC\", \"E-Z\", \"D-TEST\", \"D-A\", \"D-SC\"]\n            },\n            {\n                name: \"Block mode\",\n                type: \"option\",\n                value: [\"ECB\", \"CFB\", \"OFB\", \"CTR\", \"CBC\"]\n            },\n            {\n                name: \"Key meshing mode\",\n                type: \"option\",\n                value: [\"NO\", \"CP\"]\n            },\n            {\n                name: \"Padding\",\n                type: \"option\",\n                value: [\"NO\", \"PKCS5\", \"ZERO\", \"RANDOM\", \"BIT\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args;\n\n        const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));\n        const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));\n        input = inputType === \"Hex\" ? input : toHexFast(Utils.strToArrayBuffer(input));\n\n        let blockLength, versionNum;\n        switch (version) {\n            case \"GOST 28147 (1989)\":\n                versionNum = 1989;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Magma, 2015)\":\n                versionNum = 2015;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Kuznyechik, 2015)\":\n                versionNum = 2015;\n                blockLength = 128;\n                break;\n            default:\n                throw new OperationError(`Unknown algorithm version: ${version}`);\n        }\n\n        const sBoxVal = versionNum === 1989 ? sBox : null;\n\n        const algorithm = {\n            version: versionNum,\n            length: blockLength,\n            mode: \"ES\",\n            sBox: sBoxVal,\n            block: blockMode,\n            keyMeshing: keyMeshing,\n            padding: padding\n        };\n\n        try {\n            const Hex = CryptoGost.coding.Hex;\n            if (iv) algorithm.iv = Hex.decode(iv);\n\n            const cipher = GostEngine.getGostCipher(algorithm);\n            const out = Hex.encode(cipher.decrypt(Hex.decode(key), Hex.decode(input)));\n\n            return outputType === \"Hex\" ? out : Utils.byteArrayToChars(fromHex(out));\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default GOSTDecrypt;\n"
  },
  {
    "path": "src/core/operations/GOSTEncrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast, fromHex } from \"../lib/Hex.mjs\";\nimport { CryptoGost, GostEngine } from \"@wavesenterprise/crypto-gost-js/index.js\";\n\n/**\n * GOST Encrypt operation\n */\nclass GOSTEncrypt extends Operation {\n\n    /**\n     * GOSTEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"GOST Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.<br><br>Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure.\";\n        this.infoURL = \"https://wikipedia.org/wiki/GOST_(block_cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"IV\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Input type\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\"]\n            },\n            {\n                name: \"Output type\",\n                type: \"option\",\n                value: [\"Hex\", \"Raw\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"GOST 28147 (1989)\",\n                        on: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Magma, 2015)\",\n                        off: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Kuznyechik, 2015)\",\n                        off: [5]\n                    }\n                ]\n            },\n            {\n                name: \"sBox\",\n                type: \"option\",\n                value: [\"E-TEST\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"E-SC\", \"E-Z\", \"D-TEST\", \"D-A\", \"D-SC\"]\n            },\n            {\n                name: \"Block mode\",\n                type: \"option\",\n                value: [\"ECB\", \"CFB\", \"OFB\", \"CTR\", \"CBC\"]\n            },\n            {\n                name: \"Key meshing mode\",\n                type: \"option\",\n                value: [\"NO\", \"CP\"]\n            },\n            {\n                name: \"Padding\",\n                type: \"option\",\n                value: [\"NO\", \"PKCS5\", \"ZERO\", \"RANDOM\", \"BIT\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args;\n\n        const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));\n        const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));\n        input = inputType === \"Hex\" ? input : toHexFast(Utils.strToArrayBuffer(input));\n\n        let blockLength, versionNum;\n        switch (version) {\n            case \"GOST 28147 (1989)\":\n                versionNum = 1989;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Magma, 2015)\":\n                versionNum = 2015;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Kuznyechik, 2015)\":\n                versionNum = 2015;\n                blockLength = 128;\n                break;\n            default:\n                throw new OperationError(`Unknown algorithm version: ${version}`);\n        }\n\n        const sBoxVal = versionNum === 1989 ? sBox : null;\n\n        const algorithm = {\n            version: versionNum,\n            length: blockLength,\n            mode: \"ES\",\n            sBox: sBoxVal,\n            block: blockMode,\n            keyMeshing: keyMeshing,\n            padding: padding\n        };\n\n        try {\n            const Hex = CryptoGost.coding.Hex;\n            if (iv) algorithm.iv = Hex.decode(iv);\n\n            const cipher = GostEngine.getGostCipher(algorithm);\n            const out = Hex.encode(cipher.encrypt(Hex.decode(key), Hex.decode(input)));\n\n            return outputType === \"Hex\" ? out : Utils.byteArrayToChars(fromHex(out));\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default GOSTEncrypt;\n"
  },
  {
    "path": "src/core/operations/GOSTHash.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport GostDigest from \"../vendor/gost/gostDigest.mjs\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\n\n/**\n * GOST hash operation\n */\nclass GOSTHash extends Operation {\n\n    /**\n     * GOSTHash constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"GOST Hash\";\n        this.module = \"Hashing\";\n        this.description = \"The GOST hash function, defined in the standards GOST R 34.11-94 and GOST 34.311-95 is a 256-bit cryptographic hash function. It was initially defined in the Russian national standard GOST R 34.11-94 <i>Information Technology – Cryptographic Information Security – Hash Function</i>. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.<br><br>This function must not be confused with a different Streebog hash function, which is defined in the new revision of the standard GOST R 34.11-2012.<br><br>The GOST hash function is based on the GOST block cipher.\";\n        this.infoURL = \"https://wikipedia.org/wiki/GOST_(hash_function)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"GOST 28147 (1994)\",\n                        off: [1],\n                        on: [2]\n                    },\n                    {\n                        name: \"GOST R 34.11 (Streebog, 2012)\",\n                        on: [1],\n                        off: [2]\n                    }\n                ]\n            },\n            {\n                name: \"Digest length\",\n                type: \"option\",\n                value: [\"256\", \"512\"]\n            },\n            {\n                name: \"sBox\",\n                type: \"option\",\n                value: [\"E-TEST\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"E-SC\", \"E-Z\", \"D-TEST\", \"D-A\", \"D-SC\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [version, length, sBox] = args;\n\n        const versionNum = version === \"GOST 28147 (1994)\" ? 1994 : 2012;\n        const algorithm = {\n            name: versionNum === 1994 ? \"GOST 28147\" : \"GOST R 34.10\",\n            version: versionNum,\n            mode: \"HASH\"\n        };\n\n        if (versionNum === 1994) {\n            algorithm.sBox = sBox;\n        } else {\n            algorithm.length = parseInt(length, 10);\n        }\n\n        try {\n            const gostDigest = new GostDigest(algorithm);\n\n            return toHexFast(gostDigest.digest(input));\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default GOSTHash;\n"
  },
  {
    "path": "src/core/operations/GOSTKeyUnwrap.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast, fromHex } from \"../lib/Hex.mjs\";\nimport { CryptoGost, GostEngine } from \"@wavesenterprise/crypto-gost-js/index.js\";\n\n/**\n * GOST Key Unwrap operation\n */\nclass GOSTKeyUnwrap extends Operation {\n\n    /**\n     * GOSTKeyUnwrap constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"GOST Key Unwrap\";\n        this.module = \"Ciphers\";\n        this.description = \"A decryptor for keys wrapped using one of the GOST block ciphers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/GOST_(block_cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"User Key Material\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Input type\",\n                type: \"option\",\n                value: [\"Hex\", \"Raw\"]\n            },\n            {\n                name: \"Output type\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"GOST 28147 (1989)\",\n                        on: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Magma, 2015)\",\n                        off: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Kuznyechik, 2015)\",\n                        off: [5]\n                    }\n                ]\n            },\n            {\n                name: \"sBox\",\n                type: \"option\",\n                value: [\"E-TEST\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"E-SC\", \"E-Z\", \"D-TEST\", \"D-A\", \"D-SC\"]\n            },\n            {\n                name: \"Key wrapping\",\n                type: \"option\",\n                value: [\"NO\", \"CP\", \"SC\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args;\n\n        const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));\n        const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));\n        input = inputType === \"Hex\" ? input : toHexFast(Utils.strToArrayBuffer(input));\n\n        let blockLength, versionNum;\n        switch (version) {\n            case \"GOST 28147 (1989)\":\n                versionNum = 1989;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Magma, 2015)\":\n                versionNum = 2015;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Kuznyechik, 2015)\":\n                versionNum = 2015;\n                blockLength = 128;\n                break;\n            default:\n                throw new OperationError(`Unknown algorithm version: ${version}`);\n        }\n\n        const sBoxVal = versionNum === 1989 ? sBox : null;\n\n        const algorithm = {\n            version: versionNum,\n            length: blockLength,\n            mode: \"KW\",\n            sBox: sBoxVal,\n            keyWrapping: keyWrapping\n        };\n\n        try {\n            const Hex = CryptoGost.coding.Hex;\n            algorithm.ukm = Hex.decode(ukm);\n\n            const cipher = GostEngine.getGostCipher(algorithm);\n            const out = Hex.encode(cipher.unwrapKey(Hex.decode(key), Hex.decode(input)));\n\n            return outputType === \"Hex\" ? out : Utils.byteArrayToChars(fromHex(out));\n        } catch (err) {\n            if (err.toString().includes(\"Invalid typed array length\")) {\n                throw new OperationError(\"Incorrect input length. Must be a multiple of the block size.\");\n            }\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default GOSTKeyUnwrap;\n"
  },
  {
    "path": "src/core/operations/GOSTKeyWrap.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast, fromHex } from \"../lib/Hex.mjs\";\nimport { CryptoGost, GostEngine } from \"@wavesenterprise/crypto-gost-js/index.js\";\n\n/**\n * GOST Key Wrap operation\n */\nclass GOSTKeyWrap extends Operation {\n\n    /**\n     * GOSTKeyWrap constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"GOST Key Wrap\";\n        this.module = \"Ciphers\";\n        this.description = \"A key wrapping algorithm for protecting keys in untrusted storage using one of the GOST block cipers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/GOST_(block_cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"User Key Material\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Input type\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\"]\n            },\n            {\n                name: \"Output type\",\n                type: \"option\",\n                value: [\"Hex\", \"Raw\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"GOST 28147 (1989)\",\n                        on: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Magma, 2015)\",\n                        off: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Kuznyechik, 2015)\",\n                        off: [5]\n                    }\n                ]\n            },\n            {\n                name: \"sBox\",\n                type: \"option\",\n                value: [\"E-TEST\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"E-SC\", \"E-Z\", \"D-TEST\", \"D-A\", \"D-SC\"]\n            },\n            {\n                name: \"Key wrapping\",\n                type: \"option\",\n                value: [\"NO\", \"CP\", \"SC\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args;\n\n        const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));\n        const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));\n        input = inputType === \"Hex\" ? input : toHexFast(Utils.strToArrayBuffer(input));\n\n        let blockLength, versionNum;\n        switch (version) {\n            case \"GOST 28147 (1989)\":\n                versionNum = 1989;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Magma, 2015)\":\n                versionNum = 2015;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Kuznyechik, 2015)\":\n                versionNum = 2015;\n                blockLength = 128;\n                break;\n            default:\n                throw new OperationError(`Unknown algorithm version: ${version}`);\n        }\n\n        const sBoxVal = versionNum === 1989 ? sBox : null;\n\n        const algorithm = {\n            version: versionNum,\n            length: blockLength,\n            mode: \"KW\",\n            sBox: sBoxVal,\n            keyWrapping: keyWrapping\n        };\n\n        try {\n            const Hex = CryptoGost.coding.Hex;\n            algorithm.ukm = Hex.decode(ukm);\n\n            const cipher = GostEngine.getGostCipher(algorithm);\n            const out = Hex.encode(cipher.wrapKey(Hex.decode(key), Hex.decode(input)));\n\n            return outputType === \"Hex\" ? out : Utils.byteArrayToChars(fromHex(out));\n        } catch (err) {\n            if (err.toString().includes(\"Invalid typed array length\")) {\n                throw new OperationError(\"Incorrect input length. Must be a multiple of the block size.\");\n            }\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default GOSTKeyWrap;\n"
  },
  {
    "path": "src/core/operations/GOSTSign.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast, fromHex } from \"../lib/Hex.mjs\";\nimport { CryptoGost, GostEngine } from \"@wavesenterprise/crypto-gost-js/index.js\";\n\n/**\n * GOST Sign operation\n */\nclass GOSTSign extends Operation {\n\n    /**\n     * GOSTSign constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"GOST Sign\";\n        this.module = \"Ciphers\";\n        this.description = \"Sign a plaintext message using one of the GOST block ciphers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/GOST_(block_cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"IV\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Input type\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\"]\n            },\n            {\n                name: \"Output type\",\n                type: \"option\",\n                value: [\"Hex\", \"Raw\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"GOST 28147 (1989)\",\n                        on: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Magma, 2015)\",\n                        off: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Kuznyechik, 2015)\",\n                        off: [5]\n                    }\n                ]\n            },\n            {\n                name: \"sBox\",\n                type: \"option\",\n                value: [\"E-TEST\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"E-SC\", \"E-Z\", \"D-TEST\", \"D-A\", \"D-SC\"]\n            },\n            {\n                name: \"MAC length\",\n                type: \"number\",\n                value: 32,\n                min: 8,\n                max: 64,\n                step: 8\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [keyObj, ivObj, inputType, outputType, version, sBox, macLength] = args;\n\n        const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));\n        const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));\n        input = inputType === \"Hex\" ? input : toHexFast(Utils.strToArrayBuffer(input));\n\n        let blockLength, versionNum;\n        switch (version) {\n            case \"GOST 28147 (1989)\":\n                versionNum = 1989;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Magma, 2015)\":\n                versionNum = 2015;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Kuznyechik, 2015)\":\n                versionNum = 2015;\n                blockLength = 128;\n                break;\n            default:\n                throw new OperationError(`Unknown algorithm version: ${version}`);\n        }\n\n        const sBoxVal = versionNum === 1989 ? sBox : null;\n\n        const algorithm = {\n            version: versionNum,\n            length: blockLength,\n            mode: \"MAC\",\n            sBox: sBoxVal,\n            macLength: macLength\n        };\n\n        try {\n            const Hex = CryptoGost.coding.Hex;\n            if (iv) algorithm.iv = Hex.decode(iv);\n\n            const cipher = GostEngine.getGostCipher(algorithm);\n            const out = Hex.encode(cipher.sign(Hex.decode(key), Hex.decode(input)));\n\n            return outputType === \"Hex\" ? out : Utils.byteArrayToChars(fromHex(out));\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default GOSTSign;\n"
  },
  {
    "path": "src/core/operations/GOSTVerify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\nimport { CryptoGost, GostEngine } from \"@wavesenterprise/crypto-gost-js/index.js\";\n\n/**\n * GOST Verify operation\n */\nclass GOSTVerify extends Operation {\n\n    /**\n     * GOSTVerify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"GOST Verify\";\n        this.module = \"Ciphers\";\n        this.description = \"Verify the signature of a plaintext message using one of the GOST block ciphers. Enter the signature in the MAC field.\";\n        this.infoURL = \"https://wikipedia.org/wiki/GOST_(block_cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"IV\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"MAC\",\n                type: \"toggleString\",\n                value: \"\",\n                toggleValues: [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                name: \"Input type\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\"]\n            },\n            {\n                name: \"Algorithm\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"GOST 28147 (1989)\",\n                        on: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Magma, 2015)\",\n                        off: [5]\n                    },\n                    {\n                        name: \"GOST R 34.12 (Kuznyechik, 2015)\",\n                        off: [5]\n                    }\n                ]\n            },\n            {\n                name: \"sBox\",\n                type: \"option\",\n                value: [\"E-TEST\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"E-SC\", \"E-Z\", \"D-TEST\", \"D-A\", \"D-SC\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [keyObj, ivObj, macObj, inputType, version, sBox] = args;\n\n        const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));\n        const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));\n        const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option));\n        input = inputType === \"Hex\" ? input : toHexFast(Utils.strToArrayBuffer(input));\n\n        let blockLength, versionNum;\n        switch (version) {\n            case \"GOST 28147 (1989)\":\n                versionNum = 1989;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Magma, 2015)\":\n                versionNum = 2015;\n                blockLength = 64;\n                break;\n            case \"GOST R 34.12 (Kuznyechik, 2015)\":\n                versionNum = 2015;\n                blockLength = 128;\n                break;\n            default:\n                throw new OperationError(`Unknown algorithm version: ${version}`);\n        }\n\n        const sBoxVal = versionNum === 1989 ? sBox : null;\n\n        const algorithm = {\n            version: versionNum,\n            length: blockLength,\n            mode: \"MAC\",\n            sBox: sBoxVal,\n            macLength: mac.length * 4\n        };\n\n        try {\n            const Hex = CryptoGost.coding.Hex;\n            if (iv) algorithm.iv = Hex.decode(iv);\n\n            const cipher = GostEngine.getGostCipher(algorithm);\n            const out = cipher.verify(Hex.decode(key), Hex.decode(mac), Hex.decode(input));\n\n            return out ? \"The signature matches\" : \"The signature does not match\";\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default GOSTVerify;\n"
  },
  {
    "path": "src/core/operations/GenerateAllChecksums.mjs",
    "content": "/**\n * @author r4mos [2k95ljkhg@mozmail.com]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Adler32Checksum from \"./Adler32Checksum.mjs\";\nimport CRCChecksum from \"./CRCChecksum.mjs\";\nimport Fletcher8Checksum from \"./Fletcher8Checksum.mjs\";\nimport Fletcher16Checksum from \"./Fletcher16Checksum.mjs\";\nimport Fletcher32Checksum from \"./Fletcher32Checksum.mjs\";\nimport Fletcher64Checksum from \"./Fletcher64Checksum.mjs\";\n\n/**\n * Generate all checksums operation\n */\nclass GenerateAllChecksums extends Operation {\n\n    /**\n     * GenerateAllChecksums constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate all checksums\";\n        this.module = \"Crypto\";\n        this.description = \"Generates all available checksums for the input.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Checksum\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Length (bits)\",\n                type: \"option\",\n                value: [\n                    \"All\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"21\", \"24\", \"30\", \"31\", \"32\", \"40\", \"64\", \"82\"\n                ]\n            },\n            {\n                name: \"Include names\",\n                type: \"boolean\",\n                value: true\n            },\n        ];\n\n        const adler32 = new Adler32Checksum;\n        const crc = new CRCChecksum;\n        const fletcher8 = new Fletcher8Checksum;\n        const fletcher16 = new Fletcher16Checksum;\n        const fletcher32 = new Fletcher32Checksum;\n        const fletcher64 = new Fletcher64Checksum;\n        this.checksums = [\n            {name: \"CRC-3/GSM\", algo: crc, params: [\"CRC-3/GSM\"]},\n            {name: \"CRC-3/ROHC\", algo: crc, params: [\"CRC-3/ROHC\"]},\n            {name: \"CRC-4/G-704\", algo: crc, params: [\"CRC-4/G-704\"]},\n            {name: \"CRC-4/INTERLAKEN\", algo: crc, params: [\"CRC-4/INTERLAKEN\"]},\n            {name: \"CRC-4/ITU\", algo: crc, params: [\"CRC-4/ITU\"]},\n            {name: \"CRC-5/EPC\", algo: crc, params: [\"CRC-5/EPC\"]},\n            {name: \"CRC-5/EPC-C1G2\", algo: crc, params: [\"CRC-5/EPC-C1G2\"]},\n            {name: \"CRC-5/G-704\", algo: crc, params: [\"CRC-5/G-704\"]},\n            {name: \"CRC-5/ITU\", algo: crc, params: [\"CRC-5/ITU\"]},\n            {name: \"CRC-5/USB\", algo: crc, params: [\"CRC-5/USB\"]},\n            {name: \"CRC-6/CDMA2000-A\", algo: crc, params: [\"CRC-6/CDMA2000-A\"]},\n            {name: \"CRC-6/CDMA2000-B\", algo: crc, params: [\"CRC-6/CDMA2000-B\"]},\n            {name: \"CRC-6/DARC\", algo: crc, params: [\"CRC-6/DARC\"]},\n            {name: \"CRC-6/G-704\", algo: crc, params: [\"CRC-6/G-704\"]},\n            {name: \"CRC-6/GSM\", algo: crc, params: [\"CRC-6/GSM\"]},\n            {name: \"CRC-6/ITU\", algo: crc, params: [\"CRC-6/ITU\"]},\n            {name: \"CRC-7/MMC\", algo: crc, params: [\"CRC-7/MMC\"]},\n            {name: \"CRC-7/ROHC\", algo: crc, params: [\"CRC-7/ROHC\"]},\n            {name: \"CRC-7/UMTS\", algo: crc, params: [\"CRC-7/UMTS\"]},\n            {name: \"CRC-8\", algo: crc, params: [\"CRC-8\"]},\n            {name: \"CRC-8/8H2F\", algo: crc, params: [\"CRC-8/8H2F\"]},\n            {name: \"CRC-8/AES\", algo: crc, params: [\"CRC-8/AES\"]},\n            {name: \"CRC-8/AUTOSAR\", algo: crc, params: [\"CRC-8/AUTOSAR\"]},\n            {name: \"CRC-8/BLUETOOTH\", algo: crc, params: [\"CRC-8/BLUETOOTH\"]},\n            {name: \"CRC-8/CDMA2000\", algo: crc, params: [\"CRC-8/CDMA2000\"]},\n            {name: \"CRC-8/DARC\", algo: crc, params: [\"CRC-8/DARC\"]},\n            {name: \"CRC-8/DVB-S2\", algo: crc, params: [\"CRC-8/DVB-S2\"]},\n            {name: \"CRC-8/EBU\", algo: crc, params: [\"CRC-8/EBU\"]},\n            {name: \"CRC-8/GSM-A\", algo: crc, params: [\"CRC-8/GSM-A\"]},\n            {name: \"CRC-8/GSM-B\", algo: crc, params: [\"CRC-8/GSM-B\"]},\n            {name: \"CRC-8/HITAG\", algo: crc, params: [\"CRC-8/HITAG\"]},\n            {name: \"CRC-8/I-432-1\", algo: crc, params: [\"CRC-8/I-432-1\"]},\n            {name: \"CRC-8/I-CODE\", algo: crc, params: [\"CRC-8/I-CODE\"]},\n            {name: \"CRC-8/ITU\", algo: crc, params: [\"CRC-8/ITU\"]},\n            {name: \"CRC-8/LTE\", algo: crc, params: [\"CRC-8/LTE\"]},\n            {name: \"CRC-8/MAXIM\", algo: crc, params: [\"CRC-8/MAXIM\"]},\n            {name: \"CRC-8/MAXIM-DOW\", algo: crc, params: [\"CRC-8/MAXIM-DOW\"]},\n            {name: \"CRC-8/MIFARE-MAD\", algo: crc, params: [\"CRC-8/MIFARE-MAD\"]},\n            {name: \"CRC-8/NRSC-5\", algo: crc, params: [\"CRC-8/NRSC-5\"]},\n            {name: \"CRC-8/OPENSAFETY\", algo: crc, params: [\"CRC-8/OPENSAFETY\"]},\n            {name: \"CRC-8/ROHC\", algo: crc, params: [\"CRC-8/ROHC\"]},\n            {name: \"CRC-8/SAE-J1850\", algo: crc, params: [\"CRC-8/SAE-J1850\"]},\n            {name: \"CRC-8/SAE-J1850-ZERO\", algo: crc, params: [\"CRC-8/SAE-J1850-ZERO\"]},\n            {name: \"CRC-8/SMBUS\", algo: crc, params: [\"CRC-8/SMBUS\"]},\n            {name: \"CRC-8/TECH-3250\", algo: crc, params: [\"CRC-8/TECH-3250\"]},\n            {name: \"CRC-8/WCDMA\", algo: crc, params: [\"CRC-8/WCDMA\"]},\n            {name: \"Fletcher-8\", algo: fletcher8, params: []},\n            {name: \"CRC-10/ATM\", algo: crc, params: [\"CRC-10/ATM\"]},\n            {name: \"CRC-10/CDMA2000\", algo: crc, params: [\"CRC-10/CDMA2000\"]},\n            {name: \"CRC-10/GSM\", algo: crc, params: [\"CRC-10/GSM\"]},\n            {name: \"CRC-10/I-610\", algo: crc, params: [\"CRC-10/I-610\"]},\n            {name: \"CRC-11/FLEXRAY\", algo: crc, params: [\"CRC-11/FLEXRAY\"]},\n            {name: \"CRC-11/UMTS\", algo: crc, params: [\"CRC-11/UMTS\"]},\n            {name: \"CRC-12/3GPP\", algo: crc, params: [\"CRC-12/3GPP\"]},\n            {name: \"CRC-12/CDMA2000\", algo: crc, params: [\"CRC-12/CDMA2000\"]},\n            {name: \"CRC-12/DECT\", algo: crc, params: [\"CRC-12/DECT\"]},\n            {name: \"CRC-12/GSM\", algo: crc, params: [\"CRC-12/GSM\"]},\n            {name: \"CRC-12/UMTS\", algo: crc, params: [\"CRC-12/UMTS\"]},\n            {name: \"CRC-13/BBC\", algo: crc, params: [\"CRC-13/BBC\"]},\n            {name: \"CRC-14/DARC\", algo: crc, params: [\"CRC-14/DARC\"]},\n            {name: \"CRC-14/GSM\", algo: crc, params: [\"CRC-14/GSM\"]},\n            {name: \"CRC-15/CAN\", algo: crc, params: [\"CRC-15/CAN\"]},\n            {name: \"CRC-15/MPT1327\", algo: crc, params: [\"CRC-15/MPT1327\"]},\n            {name: \"CRC-16\", algo: crc, params: [\"CRC-16\"]},\n            {name: \"CRC-16/A\", algo: crc, params: [\"CRC-16/A\"]},\n            {name: \"CRC-16/ACORN\", algo: crc, params: [\"CRC-16/ACORN\"]},\n            {name: \"CRC-16/ARC\", algo: crc, params: [\"CRC-16/ARC\"]},\n            {name: \"CRC-16/AUG-CCITT\", algo: crc, params: [\"CRC-16/AUG-CCITT\"]},\n            {name: \"CRC-16/AUTOSAR\", algo: crc, params: [\"CRC-16/AUTOSAR\"]},\n            {name: \"CRC-16/B\", algo: crc, params: [\"CRC-16/B\"]},\n            {name: \"CRC-16/BLUETOOTH\", algo: crc, params: [\"CRC-16/BLUETOOTH\"]},\n            {name: \"CRC-16/BUYPASS\", algo: crc, params: [\"CRC-16/BUYPASS\"]},\n            {name: \"CRC-16/CCITT\", algo: crc, params: [\"CRC-16/CCITT\"]},\n            {name: \"CRC-16/CCITT-FALSE\", algo: crc, params: [\"CRC-16/CCITT-FALSE\"]},\n            {name: \"CRC-16/CCITT-TRUE\", algo: crc, params: [\"CRC-16/CCITT-TRUE\"]},\n            {name: \"CRC-16/CCITT-ZERO\", algo: crc, params: [\"CRC-16/CCITT-ZERO\"]},\n            {name: \"CRC-16/CDMA2000\", algo: crc, params: [\"CRC-16/CDMA2000\"]},\n            {name: \"CRC-16/CMS\", algo: crc, params: [\"CRC-16/CMS\"]},\n            {name: \"CRC-16/DARC\", algo: crc, params: [\"CRC-16/DARC\"]},\n            {name: \"CRC-16/DDS-110\", algo: crc, params: [\"CRC-16/DDS-110\"]},\n            {name: \"CRC-16/DECT-R\", algo: crc, params: [\"CRC-16/DECT-R\"]},\n            {name: \"CRC-16/DECT-X\", algo: crc, params: [\"CRC-16/DECT-X\"]},\n            {name: \"CRC-16/DNP\", algo: crc, params: [\"CRC-16/DNP\"]},\n            {name: \"CRC-16/EN-13757\", algo: crc, params: [\"CRC-16/EN-13757\"]},\n            {name: \"CRC-16/EPC\", algo: crc, params: [\"CRC-16/EPC\"]},\n            {name: \"CRC-16/EPC-C1G2\", algo: crc, params: [\"CRC-16/EPC-C1G2\"]},\n            {name: \"CRC-16/GENIBUS\", algo: crc, params: [\"CRC-16/GENIBUS\"]},\n            {name: \"CRC-16/GSM\", algo: crc, params: [\"CRC-16/GSM\"]},\n            {name: \"CRC-16/I-CODE\", algo: crc, params: [\"CRC-16/I-CODE\"]},\n            {name: \"CRC-16/IBM\", algo: crc, params: [\"CRC-16/IBM\"]},\n            {name: \"CRC-16/IBM-3740\", algo: crc, params: [\"CRC-16/IBM-3740\"]},\n            {name: \"CRC-16/IBM-SDLC\", algo: crc, params: [\"CRC-16/IBM-SDLC\"]},\n            {name: \"CRC-16/IEC-61158-2\", algo: crc, params: [\"CRC-16/IEC-61158-2\"]},\n            {name: \"CRC-16/ISO-HDLC\", algo: crc, params: [\"CRC-16/ISO-HDLC\"]},\n            {name: \"CRC-16/ISO-IEC-14443-3-A\", algo: crc, params: [\"CRC-16/ISO-IEC-14443-3-A\"]},\n            {name: \"CRC-16/ISO-IEC-14443-3-B\", algo: crc, params: [\"CRC-16/ISO-IEC-14443-3-B\"]},\n            {name: \"CRC-16/KERMIT\", algo: crc, params: [\"CRC-16/KERMIT\"]},\n            {name: \"CRC-16/LHA\", algo: crc, params: [\"CRC-16/LHA\"]},\n            {name: \"CRC-16/LJ1200\", algo: crc, params: [\"CRC-16/LJ1200\"]},\n            {name: \"CRC-16/LTE\", algo: crc, params: [\"CRC-16/LTE\"]},\n            {name: \"CRC-16/M17\", algo: crc, params: [\"CRC-16/M17\"]},\n            {name: \"CRC-16/MAXIM\", algo: crc, params: [\"CRC-16/MAXIM\"]},\n            {name: \"CRC-16/MAXIM-DOW\", algo: crc, params: [\"CRC-16/MAXIM-DOW\"]},\n            {name: \"CRC-16/MCRF4XX\", algo: crc, params: [\"CRC-16/MCRF4XX\"]},\n            {name: \"CRC-16/MODBUS\", algo: crc, params: [\"CRC-16/MODBUS\"]},\n            {name: \"CRC-16/NRSC-5\", algo: crc, params: [\"CRC-16/NRSC-5\"]},\n            {name: \"CRC-16/OPENSAFETY-A\", algo: crc, params: [\"CRC-16/OPENSAFETY-A\"]},\n            {name: \"CRC-16/OPENSAFETY-B\", algo: crc, params: [\"CRC-16/OPENSAFETY-B\"]},\n            {name: \"CRC-16/PROFIBUS\", algo: crc, params: [\"CRC-16/PROFIBUS\"]},\n            {name: \"CRC-16/RIELLO\", algo: crc, params: [\"CRC-16/RIELLO\"]},\n            {name: \"CRC-16/SPI-FUJITSU\", algo: crc, params: [\"CRC-16/SPI-FUJITSU\"]},\n            {name: \"CRC-16/T10-DIF\", algo: crc, params: [\"CRC-16/T10-DIF\"]},\n            {name: \"CRC-16/TELEDISK\", algo: crc, params: [\"CRC-16/TELEDISK\"]},\n            {name: \"CRC-16/TMS37157\", algo: crc, params: [\"CRC-16/TMS37157\"]},\n            {name: \"CRC-16/UMTS\", algo: crc, params: [\"CRC-16/UMTS\"]},\n            {name: \"CRC-16/USB\", algo: crc, params: [\"CRC-16/USB\"]},\n            {name: \"CRC-16/V-41-LSB\", algo: crc, params: [\"CRC-16/V-41-LSB\"]},\n            {name: \"CRC-16/V-41-MSB\", algo: crc, params: [\"CRC-16/V-41-MSB\"]},\n            {name: \"CRC-16/VERIFONE\", algo: crc, params: [\"CRC-16/VERIFONE\"]},\n            {name: \"CRC-16/X-25\", algo: crc, params: [\"CRC-16/X-25\"]},\n            {name: \"CRC-16/XMODEM\", algo: crc, params: [\"CRC-16/XMODEM\"]},\n            {name: \"CRC-16/ZMODEM\", algo: crc, params: [\"CRC-16/ZMODEM\"]},\n            {name: \"Fletcher-16\", algo: fletcher16, params: []},\n            {name: \"CRC-17/CAN-FD\", algo: crc, params: [\"CRC-17/CAN-FD\"]},\n            {name: \"CRC-21/CAN-FD\", algo: crc, params: [\"CRC-21/CAN-FD\"]},\n            {name: \"CRC-24/BLE\", algo: crc, params: [\"CRC-24/BLE\"]},\n            {name: \"CRC-24/FLEXRAY-A\", algo: crc, params: [\"CRC-24/FLEXRAY-A\"]},\n            {name: \"CRC-24/FLEXRAY-B\", algo: crc, params: [\"CRC-24/FLEXRAY-B\"]},\n            {name: \"CRC-24/INTERLAKEN\", algo: crc, params: [\"CRC-24/INTERLAKEN\"]},\n            {name: \"CRC-24/LTE-A\", algo: crc, params: [\"CRC-24/LTE-A\"]},\n            {name: \"CRC-24/LTE-B\", algo: crc, params: [\"CRC-24/LTE-B\"]},\n            {name: \"CRC-24/OPENPGP\", algo: crc, params: [\"CRC-24/OPENPGP\"]},\n            {name: \"CRC-24/OS-9\", algo: crc, params: [\"CRC-24/OS-9\"]},\n            {name: \"CRC-30/CDMA\", algo: crc, params: [\"CRC-30/CDMA\"]},\n            {name: \"CRC-31/PHILIPS\", algo: crc, params: [\"CRC-31/PHILIPS\"]},\n            {name: \"Adler-32\", algo: adler32, params: []},\n            {name: \"CRC-32\", algo: crc, params: [\"CRC-32\"]},\n            {name: \"CRC-32/AAL5\", algo: crc, params: [\"CRC-32/AAL5\"]},\n            {name: \"CRC-32/ADCCP\", algo: crc, params: [\"CRC-32/ADCCP\"]},\n            {name: \"CRC-32/AIXM\", algo: crc, params: [\"CRC-32/AIXM\"]},\n            {name: \"CRC-32/AUTOSAR\", algo: crc, params: [\"CRC-32/AUTOSAR\"]},\n            {name: \"CRC-32/BASE91-C\", algo: crc, params: [\"CRC-32/BASE91-C\"]},\n            {name: \"CRC-32/BASE91-D\", algo: crc, params: [\"CRC-32/BASE91-D\"]},\n            {name: \"CRC-32/BZIP2\", algo: crc, params: [\"CRC-32/BZIP2\"]},\n            {name: \"CRC-32/C\", algo: crc, params: [\"CRC-32/C\"]},\n            {name: \"CRC-32/CASTAGNOLI\", algo: crc, params: [\"CRC-32/CASTAGNOLI\"]},\n            {name: \"CRC-32/CD-ROM-EDC\", algo: crc, params: [\"CRC-32/CD-ROM-EDC\"]},\n            {name: \"CRC-32/CKSUM\", algo: crc, params: [\"CRC-32/CKSUM\"]},\n            {name: \"CRC-32/D\", algo: crc, params: [\"CRC-32/D\"]},\n            {name: \"CRC-32/DECT-B\", algo: crc, params: [\"CRC-32/DECT-B\"]},\n            {name: \"CRC-32/INTERLAKEN\", algo: crc, params: [\"CRC-32/INTERLAKEN\"]},\n            {name: \"CRC-32/ISCSI\", algo: crc, params: [\"CRC-32/ISCSI\"]},\n            {name: \"CRC-32/ISO-HDLC\", algo: crc, params: [\"CRC-32/ISO-HDLC\"]},\n            {name: \"CRC-32/JAMCRC\", algo: crc, params: [\"CRC-32/JAMCRC\"]},\n            {name: \"CRC-32/MEF\", algo: crc, params: [\"CRC-32/MEF\"]},\n            {name: \"CRC-32/MPEG-2\", algo: crc, params: [\"CRC-32/MPEG-2\"]},\n            {name: \"CRC-32/NVME\", algo: crc, params: [\"CRC-32/NVME\"]},\n            {name: \"CRC-32/PKZIP\", algo: crc, params: [\"CRC-32/PKZIP\"]},\n            {name: \"CRC-32/POSIX\", algo: crc, params: [\"CRC-32/POSIX\"]},\n            {name: \"CRC-32/Q\", algo: crc, params: [\"CRC-32/Q\"]},\n            {name: \"CRC-32/SATA\", algo: crc, params: [\"CRC-32/SATA\"]},\n            {name: \"CRC-32/V-42\", algo: crc, params: [\"CRC-32/V-42\"]},\n            {name: \"CRC-32/XFER\", algo: crc, params: [\"CRC-32/XFER\"]},\n            {name: \"CRC-32/XZ\", algo: crc, params: [\"CRC-32/XZ\"]},\n            {name: \"Fletcher-32\", algo: fletcher32, params: []},\n            {name: \"CRC-40/GSM\", algo: crc, params: [\"CRC-40/GSM\"]},\n            {name: \"CRC-64/ECMA-182\", algo: crc, params: [\"CRC-64/ECMA-182\"]},\n            {name: \"CRC-64/GO-ECMA\", algo: crc, params: [\"CRC-64/GO-ECMA\"]},\n            {name: \"CRC-64/GO-ISO\", algo: crc, params: [\"CRC-64/GO-ISO\"]},\n            {name: \"CRC-64/MS\", algo: crc, params: [\"CRC-64/MS\"]},\n            {name: \"CRC-64/NVME\", algo: crc, params: [\"CRC-64/NVME\"]},\n            {name: \"CRC-64/REDIS\", algo: crc, params: [\"CRC-64/REDIS\"]},\n            {name: \"CRC-64/WE\", algo: crc, params: [\"CRC-64/WE\"]},\n            {name: \"CRC-64/XZ\", algo: crc, params: [\"CRC-64/XZ\"]},\n            {name: \"Fletcher-64\", algo: fletcher64, params: []},\n            {name: \"CRC-82/DARC\", algo: crc, params: [\"CRC-82/DARC\"]}\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [length, includeNames] = args;\n        let output = \"\";\n        this.checksums.forEach(checksum => {\n            const checksumLength = checksum.name.match(new RegExp(\"-(\\\\d{1,2})(\\\\/|$)\"))[1];\n            if (length === \"All\" || length === checksumLength) {\n                const value = checksum.algo.run(new Uint8Array(input), checksum.params || []);\n                output += includeNames ?\n                    `${checksum.name}:${\" \".repeat(25-checksum.name.length)}${value}\\n`:\n                    `${value}\\n`;\n            }\n        });\n        return output;\n    }\n}\n\nexport default GenerateAllChecksums;\n"
  },
  {
    "path": "src/core/operations/GenerateAllHashes.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author john19696 [john19696@protonmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport MD2 from \"./MD2.mjs\";\nimport MD4 from \"./MD4.mjs\";\nimport MD5 from \"./MD5.mjs\";\nimport MD6 from \"./MD6.mjs\";\nimport SHA0 from \"./SHA0.mjs\";\nimport SHA1 from \"./SHA1.mjs\";\nimport SHA2 from \"./SHA2.mjs\";\nimport SHA3 from \"./SHA3.mjs\";\nimport Keccak from \"./Keccak.mjs\";\nimport Shake from \"./Shake.mjs\";\nimport RIPEMD from \"./RIPEMD.mjs\";\nimport HAS160 from \"./HAS160.mjs\";\nimport Whirlpool from \"./Whirlpool.mjs\";\nimport SSDEEP from \"./SSDEEP.mjs\";\nimport CTPH from \"./CTPH.mjs\";\nimport BLAKE2b from \"./BLAKE2b.mjs\";\nimport BLAKE2s from \"./BLAKE2s.mjs\";\nimport Streebog from \"./Streebog.mjs\";\nimport GOSTHash from \"./GOSTHash.mjs\";\nimport LMHash from \"./LMHash.mjs\";\nimport NTHash from \"./NTHash.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Generate all hashes operation\n */\nclass GenerateAllHashes extends Operation {\n\n    /**\n     * GenerateAllHashes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate all hashes\";\n        this.module = \"Crypto\";\n        this.description = \"Generates all available hashes and checksums for the input.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Length (bits)\",\n                type: \"option\",\n                value: [\n                    \"All\", \"128\", \"160\", \"224\", \"256\", \"320\", \"384\", \"512\"\n                ]\n            },\n            {\n                name: \"Include names\",\n                type: \"boolean\",\n                value: true\n            },\n        ];\n        this.hashes = [\n            {name: \"MD2\", algo: (new MD2()), inputType: \"arrayBuffer\", params: []},\n            {name: \"MD4\", algo: (new MD4()), inputType: \"arrayBuffer\", params: []},\n            {name: \"MD5\", algo: (new MD5()), inputType: \"arrayBuffer\", params: []},\n            {name: \"MD6\", algo: (new MD6()), inputType: \"str\", params: []},\n            {name: \"SHA0\", algo: (new SHA0()), inputType: \"arrayBuffer\", params: []},\n            {name: \"SHA1\", algo: (new SHA1()), inputType: \"arrayBuffer\", params: []},\n            {name: \"SHA2 224\", algo: (new SHA2()), inputType: \"arrayBuffer\", params: [\"224\"]},\n            {name: \"SHA2 256\", algo: (new SHA2()), inputType: \"arrayBuffer\", params: [\"256\"]},\n            {name: \"SHA2 384\", algo: (new SHA2()), inputType: \"arrayBuffer\", params: [\"384\"]},\n            {name: \"SHA2 512\", algo: (new SHA2()), inputType: \"arrayBuffer\", params: [\"512\"]},\n            {name: \"SHA3 224\", algo: (new SHA3()), inputType: \"arrayBuffer\", params: [\"224\"]},\n            {name: \"SHA3 256\", algo: (new SHA3()), inputType: \"arrayBuffer\", params: [\"256\"]},\n            {name: \"SHA3 384\", algo: (new SHA3()), inputType: \"arrayBuffer\", params: [\"384\"]},\n            {name: \"SHA3 512\", algo: (new SHA3()), inputType: \"arrayBuffer\", params: [\"512\"]},\n            {name: \"Keccak 224\", algo: (new Keccak()), inputType: \"arrayBuffer\", params: [\"224\"]},\n            {name: \"Keccak 256\", algo: (new Keccak()), inputType: \"arrayBuffer\", params: [\"256\"]},\n            {name: \"Keccak 384\", algo: (new Keccak()), inputType: \"arrayBuffer\", params: [\"384\"]},\n            {name: \"Keccak 512\", algo: (new Keccak()), inputType: \"arrayBuffer\", params: [\"512\"]},\n            {name: \"Shake 128\", algo: (new Shake()), inputType: \"arrayBuffer\", params: [\"128\", 256]},\n            {name: \"Shake 256\", algo: (new Shake()), inputType: \"arrayBuffer\", params: [\"256\", 512]},\n            {name: \"RIPEMD-128\", algo: (new RIPEMD()), inputType: \"arrayBuffer\", params: [\"128\"]},\n            {name: \"RIPEMD-160\", algo: (new RIPEMD()), inputType: \"arrayBuffer\", params: [\"160\"]},\n            {name: \"RIPEMD-256\", algo: (new RIPEMD()), inputType: \"arrayBuffer\", params: [\"256\"]},\n            {name: \"RIPEMD-320\", algo: (new RIPEMD()), inputType: \"arrayBuffer\", params: [\"320\"]},\n            {name: \"HAS-160\", algo: (new HAS160()), inputType: \"arrayBuffer\", params: []},\n            {name: \"Whirlpool-0\", algo: (new Whirlpool()), inputType: \"arrayBuffer\", params: [\"Whirlpool-0\"]},\n            {name: \"Whirlpool-T\", algo: (new Whirlpool()), inputType: \"arrayBuffer\", params: [\"Whirlpool-T\"]},\n            {name: \"Whirlpool\", algo: (new Whirlpool()), inputType: \"arrayBuffer\", params: [\"Whirlpool\"]},\n            {name: \"BLAKE2b-128\", algo: (new BLAKE2b), inputType: \"arrayBuffer\", params: [\"128\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"BLAKE2b-160\", algo: (new BLAKE2b), inputType: \"arrayBuffer\", params: [\"160\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"BLAKE2b-256\", algo: (new BLAKE2b), inputType: \"arrayBuffer\", params: [\"256\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"BLAKE2b-384\", algo: (new BLAKE2b), inputType: \"arrayBuffer\", params: [\"384\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"BLAKE2b-512\", algo: (new BLAKE2b), inputType: \"arrayBuffer\", params: [\"512\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"BLAKE2s-128\", algo: (new BLAKE2s), inputType: \"arrayBuffer\", params: [\"128\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"BLAKE2s-160\", algo: (new BLAKE2s), inputType: \"arrayBuffer\", params: [\"160\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"BLAKE2s-256\", algo: (new BLAKE2s), inputType: \"arrayBuffer\", params: [\"256\", \"Hex\", {string: \"\", option: \"UTF8\"}]},\n            {name: \"Streebog-256\", algo: (new Streebog), inputType: \"arrayBuffer\", params: [\"256\"]},\n            {name: \"Streebog-512\", algo: (new Streebog), inputType: \"arrayBuffer\", params: [\"512\"]},\n            {name: \"GOST\", algo: (new GOSTHash), inputType: \"arrayBuffer\", params: [\"GOST 28147 (1994)\", \"256\", \"D-A\"]},\n            {name: \"LM Hash\", algo: (new LMHash), inputType: \"str\", params: []},\n            {name: \"NT Hash\", algo: (new NTHash), inputType: \"str\", params: []},\n            {name: \"SSDEEP\", algo: (new SSDEEP()), inputType: \"str\"},\n            {name: \"CTPH\", algo: (new CTPH()), inputType: \"str\"}\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [length, includeNames] = args;\n        this.inputArrayBuffer = input;\n        this.inputStr = Utils.arrayBufferToStr(input, false);\n        this.inputByteArray = new Uint8Array(input);\n\n        let digest, output = \"\";\n        // iterate over each of the hashes\n        this.hashes.forEach(hash => {\n            digest = this.executeAlgo(hash.algo, hash.inputType, hash.params || []);\n            output += this.formatDigest(digest, length, includeNames, hash.name);\n        });\n\n        return output;\n    }\n\n    /**\n     * Executes a hash or checksum algorithm\n     *\n     * @param {Function} algo - The hash or checksum algorithm\n     * @param {string} inputType\n     * @param {Object[]} [params=[]]\n     * @returns {string}\n     */\n    executeAlgo(algo, inputType, params=[]) {\n        let digest = null;\n        switch (inputType) {\n            case \"arrayBuffer\":\n                digest = algo.run(this.inputArrayBuffer, params);\n                break;\n            case \"str\":\n                digest = algo.run(this.inputStr, params);\n                break;\n            case \"byteArray\":\n                digest = algo.run(this.inputByteArray, params);\n                break;\n            default:\n                throw new OperationError(\"Unknown hash input type: \" + inputType);\n        }\n\n        return digest;\n    }\n\n    /**\n     * Formats the digest depending on user-specified arguments\n     * @param {string} digest\n     * @param {string} length\n     * @param {boolean} includeNames\n     * @param {string} name\n     * @returns {string}\n     */\n    formatDigest(digest, length, includeNames, name) {\n        if (length !== \"All\" && (digest.length * 4) !== parseInt(length, 10))\n            return \"\";\n\n        if (!includeNames)\n            return digest + \"\\n\";\n\n        return `${name}:${\" \".repeat(13-name.length)}${digest}\\n`;\n    }\n\n}\n\nexport default GenerateAllHashes;\n"
  },
  {
    "path": "src/core/operations/GenerateDeBruijnSequence.mjs",
    "content": "/**\n * @author gchq77703 [gchq77703@gchq.gov.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Generate De Bruijn Sequence operation\n */\nclass GenerateDeBruijnSequence extends Operation {\n\n    /**\n     * GenerateDeBruijnSequence constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate De Bruijn Sequence\";\n        this.module = \"Default\";\n        this.description = \"Generates rolling keycode combinations given a certain alphabet size and key length.\";\n        this.infoURL = \"https://wikipedia.org/wiki/De_Bruijn_sequence\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Alphabet size (k)\",\n                type: \"number\",\n                value: 2\n            },\n            {\n                name: \"Key length (n)\",\n                type: \"number\",\n                value: 3\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [k, n] = args;\n\n        if (k < 2 || k > 9) {\n            throw new OperationError(\"Invalid alphabet size, required to be between 2 and 9 (inclusive).\");\n        }\n\n        if (n < 2) {\n            throw new OperationError(\"Invalid key length, required to be at least 2.\");\n        }\n\n        if (Math.pow(k, n) > 50000) {\n            throw new OperationError(\"Too many permutations, please reduce k^n to under 50,000.\");\n        }\n\n        const a = new Array(k * n).fill(0);\n        const sequence = [];\n\n        (function db(t = 1, p = 1) {\n            if (t > n) {\n                if (n % p !== 0) return;\n                for (let j = 1; j <= p; j++) {\n                    sequence.push(a[j]);\n                }\n                return;\n            }\n\n            a[t] = a[t - p];\n            db(t + 1, p);\n            for (let j = a[t - p] + 1; j < k; j++) {\n                a[t] = j;\n                db(t + 1, t);\n            }\n        })();\n\n        return sequence.join(\"\");\n    }\n}\n\nexport default GenerateDeBruijnSequence;\n"
  },
  {
    "path": "src/core/operations/GenerateECDSAKeyPair.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { cryptNotice } from \"../lib/Crypt.mjs\";\nimport r from \"jsrsasign\";\n\n/**\n * Generate ECDSA Key Pair operation\n */\nclass GenerateECDSAKeyPair extends Operation {\n\n    /**\n     * GenerateECDSAKeyPair constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate ECDSA Key Pair\";\n        this.module = \"Ciphers\";\n        this.description = `Generate an ECDSA key pair with a given Curve.<br><br>${cryptNotice}`;\n        this.infoURL = \"https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Elliptic Curve\",\n                type: \"option\",\n                value: [\n                    \"P-256\",\n                    \"P-384\",\n                    \"P-521\"\n                ]\n            },\n            {\n                name: \"Output Format\",\n                type: \"option\",\n                value: [\n                    \"PEM\",\n                    \"DER\",\n                    \"JWK\"\n                ]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [curveName, outputFormat] = args;\n\n        return new Promise((resolve, reject) => {\n            let internalCurveName;\n            switch (curveName) {\n                case \"P-256\":\n                    internalCurveName = \"secp256r1\";\n                    break;\n                case \"P-384\":\n                    internalCurveName = \"secp384r1\";\n                    break;\n                case \"P-521\":\n                    internalCurveName = \"secp521r1\";\n                    break;\n            }\n            const keyPair = r.KEYUTIL.generateKeypair(\"EC\", internalCurveName);\n\n            let pubKey;\n            let privKey;\n            let result;\n            switch (outputFormat) {\n                case \"PEM\":\n                    pubKey = r.KEYUTIL.getPEM(keyPair.pubKeyObj).replace(/\\r/g, \"\");\n                    privKey = r.KEYUTIL.getPEM(keyPair.prvKeyObj, \"PKCS8PRV\").replace(/\\r/g, \"\");\n                    result = pubKey + \"\\n\" + privKey;\n                    break;\n                case \"DER\":\n                    result = keyPair.prvKeyObj.prvKeyHex;\n                    break;\n                case \"JWK\":\n                    pubKey = r.KEYUTIL.getJWKFromKey(keyPair.pubKeyObj);\n                    pubKey.key_ops = [\"verify\"]; // eslint-disable-line camelcase\n                    pubKey.kid = \"PublicKey\";\n                    privKey = r.KEYUTIL.getJWKFromKey(keyPair.prvKeyObj);\n                    privKey.key_ops = [\"sign\"]; // eslint-disable-line camelcase\n                    privKey.kid = \"PrivateKey\";\n                    result = JSON.stringify({keys: [privKey, pubKey]}, null, 4);\n                    break;\n            }\n\n            resolve(result);\n        });\n    }\n\n}\n\nexport default GenerateECDSAKeyPair;\n"
  },
  {
    "path": "src/core/operations/GenerateHOTP.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport * as OTPAuth from \"otpauth\";\n\n/**\n * Generate HOTP operation\n */\nclass GenerateHOTP extends Operation {\n    /**\n     *\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate HOTP\";\n        this.module = \"Default\";\n        this.description = \"The HMAC-based One-Time Password algorithm (HOTP) is an algorithm that computes a one-time password from a shared secret key and an incrementing counter. It has been adopted as Internet Engineering Task Force standard RFC 4226, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated.\";\n        this.infoURL = \"https://wikipedia.org/wiki/HMAC-based_One-time_Password_algorithm\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Name\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Code length\",\n                \"type\": \"number\",\n                \"value\": 6\n            },\n            {\n                \"name\": \"Counter\",\n                \"type\": \"number\",\n                \"value\": 0\n            }\n        ];\n    }\n\n    /**\n     *\n     */\n    run(input, args) {\n        const secretStr = new TextDecoder(\"utf-8\").decode(input).trim();\n        const secret = secretStr ? secretStr.toUpperCase().replace(/\\s+/g, \"\") : \"\";\n\n        const hotp = new OTPAuth.HOTP({\n            issuer: \"\",\n            label: args[0],\n            algorithm: \"SHA1\",\n            digits: args[1],\n            counter: args[2],\n            secret: OTPAuth.Secret.fromBase32(secret)\n        });\n\n        const uri = hotp.toString();\n        const code = hotp.generate();\n\n        return `URI: ${uri}\\n\\nPassword: ${code}`;\n    }\n}\n\nexport default GenerateHOTP;\n"
  },
  {
    "path": "src/core/operations/GenerateImage.mjs",
    "content": "/**\n * @author pointhi [thomas.pointhuber@gmx.at]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime, ResizeStrategy, rgbaToInt } from \"jimp\";\n\n/**\n * Generate Image operation\n */\nclass GenerateImage extends Operation {\n    /**\n     * GenerateImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Generates an image using the input as pixel values.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Mode\",\n                type: \"option\",\n                value: [\"Greyscale\", \"RG\", \"RGB\", \"RGBA\", \"Bits\"],\n            },\n            {\n                name: \"Pixel Scale Factor\",\n                type: \"number\",\n                value: 8,\n            },\n            {\n                name: \"Pixels per row\",\n                type: \"number\",\n                value: 64,\n            },\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    async run(input, args) {\n        const [mode, scale, width] = args;\n        input = new Uint8Array(input);\n\n        if (scale <= 0) {\n            throw new OperationError(\"Pixel Scale Factor needs to be > 0\");\n        }\n\n        if (width <= 0) {\n            throw new OperationError(\"Pixels per Row needs to be > 0\");\n        }\n\n        const bytePerPixelMap = {\n            Greyscale: 1,\n            RG: 2,\n            RGB: 3,\n            RGBA: 4,\n            Bits: 1 / 8,\n        };\n\n        const bytesPerPixel = bytePerPixelMap[mode];\n\n        if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) {\n            throw new OperationError(\n                `Number of bytes is not a divisor of ${bytesPerPixel}`,\n            );\n        }\n\n        const height = Math.ceil(input.length / bytesPerPixel / width);\n        const image = new Jimp({ width, height });\n\n        if (isWorkerEnvironment())\n            self.sendStatusMessage(\"Generating image from data...\");\n\n        if (mode === \"Bits\") {\n            let index = 0;\n            for (let j = 0; j < input.length; j++) {\n                const curByte = Utils.bin(input[j]);\n                for (let k = 0; k < 8; k++, index++) {\n                    const x = index % width;\n                    const y = Math.floor(index / width);\n\n                    const value = curByte[k] === \"0\" ? 0xff : 0x00;\n                    const pixel = rgbaToInt(value, value, value, 0xff);\n                    image.setPixelColor(pixel, x, y);\n                }\n            }\n        } else {\n            let i = 0;\n            while (i < input.length) {\n                const index = i / bytesPerPixel;\n                const x = index % width;\n                const y = Math.floor(index / width);\n\n                let red = 0x00;\n                let green = 0x00;\n                let blue = 0x00;\n                let alpha = 0xff;\n\n                switch (mode) {\n                    case \"Greyscale\":\n                        red = green = blue = input[i++];\n                        break;\n\n                    case \"RG\":\n                        red = input[i++];\n                        green = input[i++];\n                        break;\n\n                    case \"RGB\":\n                        red = input[i++];\n                        green = input[i++];\n                        blue = input[i++];\n                        break;\n\n                    case \"RGBA\":\n                        red = input[i++];\n                        green = input[i++];\n                        blue = input[i++];\n                        alpha = input[i++];\n                        break;\n\n                    default:\n                        throw new OperationError(`Unsupported Mode: (${mode})`);\n                }\n\n                try {\n                    const pixel = rgbaToInt(red, green, blue, alpha);\n                    image.setPixelColor(pixel, x, y);\n                } catch (err) {\n                    throw new OperationError(\n                        `Error while generating image from pixel values. (${err})`,\n                    );\n                }\n            }\n        }\n\n        if (scale !== 1) {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Scaling image...\");\n\n            image.scaleToFit({\n                w: width * scale,\n                h: height * scale,\n                mode: ResizeStrategy.NEAREST_NEIGHBOR,\n            });\n        }\n\n        try {\n            const imageBuffer = await image.getBuffer(JimpMime.png);\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error generating image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the generated image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default GenerateImage;\n"
  },
  {
    "path": "src/core/operations/GenerateLoremIpsum.mjs",
    "content": "/**\n * @author klaxon [klaxon@veyr.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from \"../lib/LoremIpsum.mjs\";\n\n/**\n * Generate Lorem Ipsum operation\n */\nclass GenerateLoremIpsum extends Operation {\n\n    /**\n     * GenerateLoremIpsum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate Lorem Ipsum\";\n        this.module = \"Default\";\n        this.description = \"Generate varying length lorem ipsum placeholder text.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Lorem_ipsum\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Length\",\n                \"type\": \"number\",\n                \"value\": \"3\"\n            },\n            {\n                \"name\": \"Length in\",\n                \"type\": \"option\",\n                \"value\": [\"Paragraphs\", \"Sentences\", \"Words\", \"Bytes\"]\n            }\n\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [length, lengthType] = args;\n        if (length < 1) {\n            throw new OperationError(\"Length must be greater than 0\");\n        }\n        switch (lengthType) {\n            case \"Paragraphs\":\n                return GenerateParagraphs(length);\n            case \"Sentences\":\n                return GenerateSentences(length);\n            case \"Words\":\n                return GenerateWords(length);\n            case \"Bytes\":\n                return GenerateBytes(length);\n            default:\n                throw new OperationError(\"Invalid length type\");\n\n        }\n    }\n\n}\n\nexport default GenerateLoremIpsum;\n"
  },
  {
    "path": "src/core/operations/GeneratePGPKeyPair.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author Matt C [matt@artemisbot.uk]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport kbpgp from \"kbpgp\";\nimport { getSubkeySize, ASP } from \"../lib/PGP.mjs\";\nimport { cryptNotice } from \"../lib/Crypt.mjs\";\nimport * as es6promisify from \"es6-promisify\";\nconst promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;\n\n\n/**\n * Generate PGP Key Pair operation\n */\nclass GeneratePGPKeyPair extends Operation {\n\n    /**\n     * GeneratePGPKeyPair constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate PGP Key Pair\";\n        this.module = \"PGP\";\n        this.description = `Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.<br><br>${cryptNotice}`;\n        this.infoURL = \"https://wikipedia.org/wiki/Pretty_Good_Privacy\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key type\",\n                \"type\": \"option\",\n                \"value\": [\"RSA-1024\", \"RSA-2048\", \"RSA-4096\", \"ECC-256\", \"ECC-384\", \"ECC-521\"]\n            },\n            {\n                \"name\": \"Password (optional)\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Name (optional)\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Email (optional)\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        let [keyType, keySize] = args[0].split(\"-\");\n        const password = args[1],\n            name = args[2],\n            email = args[3];\n        let userIdentifier = \"\";\n\n        keyType = keyType.toLowerCase();\n        keySize = parseInt(keySize, 10);\n\n        if (name) userIdentifier += name;\n        if (email) userIdentifier += ` <${email}>`;\n\n        let flags = kbpgp.const.openpgp.certify_keys;\n        flags |= kbpgp.const.openpgp.sign_data;\n        flags |= kbpgp.const.openpgp.auth;\n        flags |= kbpgp.const.openpgp.encrypt_comm;\n        flags |= kbpgp.const.openpgp.encrypt_storage;\n\n        const keyGenerationOptions = {\n            userid: userIdentifier,\n            ecc: keyType === \"ecc\",\n            primary: {\n                \"nbits\": keySize,\n                \"flags\": flags,\n                \"expire_in\": 0\n            },\n            subkeys: [{\n                \"nbits\": getSubkeySize(keySize),\n                \"flags\": kbpgp.const.openpgp.sign_data,\n                \"expire_in\": 86400 * 365 * 8\n            }, {\n                \"nbits\": getSubkeySize(keySize),\n                \"flags\": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage,\n                \"expire_in\": 86400 * 365 * 2\n            }],\n            asp: ASP\n        };\n\n        return new Promise(async (resolve, reject) => {\n            try {\n                const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions);\n                await promisify(unsignedKey.sign.bind(unsignedKey))({});\n\n                const signedKey = unsignedKey,\n                    privateKeyExportOptions = {};\n\n                if (password) privateKeyExportOptions.passphrase = password;\n                const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions);\n                const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({});\n                resolve(privateKey + \"\\n\" + publicKey.trim());\n            } catch (err) {\n                reject(`Error whilst generating key pair: ${err}`);\n            }\n        });\n    }\n\n}\n\nexport default GeneratePGPKeyPair;\n"
  },
  {
    "path": "src/core/operations/GenerateQRCode.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { generateQrCode } from \"../lib/QRCode.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Generate QR Code operation\n */\nclass GenerateQRCode extends Operation {\n\n    /**\n     * GenerateQRCode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate QR Code\";\n        this.module = \"Image\";\n        this.description = \"Generates a Quick Response (QR) code from the input text.<br><br>A QR code is a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached.\";\n        this.infoURL = \"https://wikipedia.org/wiki/QR_code\";\n        this.inputType = \"string\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Image Format\",\n                \"type\": \"option\",\n                \"value\": [\"PNG\", \"SVG\", \"EPS\", \"PDF\"]\n            },\n            {\n                \"name\": \"Module size (px)\",\n                \"type\": \"number\",\n                \"value\": 5,\n                \"min\": 1\n            },\n            {\n                \"name\": \"Margin (num modules)\",\n                \"type\": \"number\",\n                \"value\": 4,\n                \"min\": 0\n            },\n            {\n                \"name\": \"Error correction\",\n                \"type\": \"option\",\n                \"value\": [\"Low\", \"Medium\", \"Quartile\", \"High\"],\n                \"defaultIndex\": 1\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const [format, size, margin, errorCorrection] = args;\n\n        return generateQrCode(input, format, size, margin, errorCorrection);\n    }\n\n    /**\n     * Displays the QR image using HTML for web apps\n     *\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data, args) {\n        if (!data.byteLength && !data.length) return \"\";\n        const dataArray = new Uint8Array(data),\n            [format] = args;\n        if (format === \"PNG\") {\n            const type = isImage(dataArray);\n            if (!type) {\n                throw new OperationError(\"Invalid file type.\");\n            }\n\n            return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n        }\n\n        return Utils.arrayBufferToStr(data);\n    }\n\n}\n\nexport default GenerateQRCode;\n"
  },
  {
    "path": "src/core/operations/GenerateRSAKeyPair.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @author gchq77703 []\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport forge from \"node-forge\";\nimport { cryptNotice } from \"../lib/Crypt.mjs\";\n\n/**\n * Generate RSA Key Pair operation\n */\nclass GenerateRSAKeyPair extends Operation {\n\n    /**\n     * GenerateRSAKeyPair constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate RSA Key Pair\";\n        this.module = \"Ciphers\";\n        this.description = `Generate an RSA key pair with a given number of bits.<br><br>${cryptNotice}`;\n        this.infoURL = \"https://wikipedia.org/wiki/RSA_(cryptosystem)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"RSA Key Length\",\n                type: \"option\",\n                value: [\n                    \"1024\",\n                    \"2048\",\n                    \"4096\"\n                ]\n            },\n            {\n                name: \"Output Format\",\n                type: \"option\",\n                value: [\n                    \"PEM\",\n                    \"JSON\",\n                    \"DER\"\n                ]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [keyLength, outputFormat] = args;\n\n        return new Promise((resolve, reject) => {\n            forge.pki.rsa.generateKeyPair({\n                bits: Number(keyLength),\n                workers: -1,\n                workerScript: \"assets/forge/prime.worker.min.js\"\n            }, (err, keypair) => {\n                if (err) return reject(err);\n\n                let result;\n\n                switch (outputFormat) {\n                    case \"PEM\":\n                        result = forge.pki.publicKeyToPem(keypair.publicKey) + \"\\n\" + forge.pki.privateKeyToPem(keypair.privateKey);\n                        break;\n                    case \"JSON\":\n                        result = JSON.stringify(keypair);\n                        break;\n                    case \"DER\":\n                        result = forge.asn1.toDer(forge.pki.privateKeyToAsn1(keypair.privateKey)).getBytes();\n                        break;\n                }\n\n                resolve(result);\n            });\n        });\n    }\n\n}\n\nexport default GenerateRSAKeyPair;\n"
  },
  {
    "path": "src/core/operations/GenerateTOTP.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport * as OTPAuth from \"otpauth\";\n\n/**\n * Generate TOTP operation\n */\nclass GenerateTOTP extends Operation {\n    /**\n     *\n     */\n    constructor() {\n        super();\n        this.name = \"Generate TOTP\";\n        this.module = \"Default\";\n        this.description = \"The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Time-based_One-time_Password_algorithm\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Name\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Code length\",\n                \"type\": \"number\",\n                \"value\": 6\n            },\n            {\n                \"name\": \"Epoch offset (T0)\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Interval (T1)\",\n                \"type\": \"number\",\n                \"value\": 30\n            }\n        ];\n    }\n\n    /**\n     *\n     */\n    run(input, args) {\n        const secretStr = new TextDecoder(\"utf-8\").decode(input).trim();\n        const secret = secretStr ? secretStr.toUpperCase().replace(/\\s+/g, \"\") : \"\";\n\n        const totp = new OTPAuth.TOTP({\n            issuer: \"\",\n            label: args[0],\n            algorithm: \"SHA1\",\n            digits: args[1],\n            period: args[3],\n            epoch: args[2] * 1000, // Convert seconds to milliseconds\n            secret: OTPAuth.Secret.fromBase32(secret)\n        });\n\n        const uri = totp.toString();\n        const code = totp.generate();\n\n        return `URI: ${uri}\\n\\nPassword: ${code}`;\n    }\n}\n\nexport default GenerateTOTP;\n"
  },
  {
    "path": "src/core/operations/GenerateUUID.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport * as uuid from \"uuid\";\nimport OperationError from \"../errors/OperationError.mjs\";\n/**\n * Generate UUID operation\n */\nclass GenerateUUID extends Operation {\n\n    /**\n     * GenerateUUID constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generate UUID\";\n        this.module = \"Crypto\";\n        this.description =\n            \"Generates an RFC 9562 (formerly RFC 4122) compliant Universally Unique Identifier (UUID), \" +\n            \"also known as a Globally Unique Identifier (GUID).<br>\" +\n            \"<br>\" +\n            \"We currently support generating the following UUID versions:<br>\" +\n            \"<ul>\" +\n            \"<li><strong>v1</strong>: Timestamp-based</li>\" +\n            \"<li><strong>v3</strong>: Namespace w/ MD5</li>\" +\n            \"<li><strong>v4</strong>: Random (default)</li>\" +\n            \"<li><strong>v5</strong>: Namespace w/ SHA-1</li>\" +\n            \"<li><strong>v6</strong>: Timestamp, reordered</li>\" +\n            \"<li><strong>v7</strong>: Unix Epoch time-based</li>\" +\n            \"</ul>\" +\n            \"UUIDs are generated using the <a href='https://npmjs.org/uuid/'><code>uuid</code><a> package.<br>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Universally_unique_identifier\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Version\",\n                hint: \"UUID version\",\n                type: \"option\",\n                value: [\"v1\", \"v3\", \"v4\", \"v5\", \"v6\", \"v7\"],\n                defaultIndex: 2,\n            },\n            {\n                name: \"Namespace\",\n                hint: \"UUID namespace (UUID; valid for v3 and v5)\",\n                type: \"string\",\n                value: \"1b671a64-40d5-491e-99b0-da01ff1f3341\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [version, namespace] = args;\n        const hasDesiredVersion = typeof uuid[version] === \"function\";\n        if (!hasDesiredVersion) throw new OperationError(\"Invalid UUID version\");\n\n        const requiresNamespace = [\"v3\", \"v5\"].includes(version);\n        if (!requiresNamespace) return uuid[version]();\n\n        const hasValidNamespace = typeof namespace === \"string\" && uuid.validate(namespace);\n        if (!hasValidNamespace) throw new OperationError(\"Invalid UUID namespace\");\n\n        return uuid[version](input, namespace);\n    }\n\n}\n\nexport default GenerateUUID;\n"
  },
  {
    "path": "src/core/operations/GenericCodeBeautify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Generic Code Beautify operation\n */\nclass GenericCodeBeautify extends Operation {\n\n    /**\n     * GenericCodeBeautify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Generic Code Beautify\";\n        this.module = \"Code\";\n        this.description = \"Attempts to pretty print C-style languages such as C, C++, C#, Java, PHP, JavaScript etc.<br><br>This will not do a perfect job, and the resulting code may not work any more. This operation is designed purely to make obfuscated or minified code more easy to read and understand.<br><br>Things which will not work properly:<ul><li>For loop formatting</li><li>Do-While loop formatting</li><li>Switch/Case indentation</li><li>Certain bit shift operators</li></ul>\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const preservedTokens = [];\n        let code = input,\n            t = 0,\n            m;\n\n        // Remove strings\n        const sstrings = /'([^'\\\\]|\\\\.)*'/g;\n        while ((m = sstrings.exec(code))) {\n            code = preserveToken(code, m, t++);\n            sstrings.lastIndex = m.index;\n        }\n\n        const dstrings = /\"([^\"\\\\]|\\\\.)*\"/g;\n        while ((m = dstrings.exec(code))) {\n            code = preserveToken(code, m, t++);\n            dstrings.lastIndex = m.index;\n        }\n\n        // Remove comments\n        const scomments = /\\/\\/[^\\n\\r]*/g;\n        while ((m = scomments.exec(code))) {\n            code = preserveToken(code, m, t++);\n            scomments.lastIndex = m.index;\n        }\n\n        const mcomments = /\\/\\*[\\s\\S]*?\\*\\//gm;\n        while ((m = mcomments.exec(code))) {\n            code = preserveToken(code, m, t++);\n            mcomments.lastIndex = m.index;\n        }\n\n        const hcomments = /(^|\\n)#[^\\n\\r#]+/g;\n        while ((m = hcomments.exec(code))) {\n            code = preserveToken(code, m, t++);\n            hcomments.lastIndex = m.index;\n        }\n\n        // Remove regexes\n        const regexes = /\\/.*?[^\\\\]\\/[gim]{0,3}/gi;\n        while ((m = regexes.exec(code))) {\n            code = preserveToken(code, m, t++);\n            regexes.lastIndex = m.index;\n        }\n\n        code = code\n            // Create newlines after ;\n            .replace(/;/g, \";\\n\")\n            // Create newlines after { and around }\n            .replace(/{/g, \"{\\n\")\n            .replace(/}/g, \"\\n}\\n\")\n            // Remove carriage returns\n            .replace(/\\r/g, \"\")\n            // Remove all indentation\n            .replace(/^\\s+/g, \"\")\n            .replace(/\\n\\s+/g, \"\\n\")\n            // Remove trailing spaces\n            .replace(/\\s*$/g, \"\")\n            .replace(/\\n{/g, \"{\");\n\n        // Indent\n        let i = 0,\n            level = 0,\n            indent;\n        while (i < code.length) {\n            switch (code[i]) {\n                case \"{\":\n                    level++;\n                    break;\n                case \"\\n\":\n                    if (i+1 >= code.length) break;\n\n                    if (code[i+1] === \"}\") level--;\n                    indent = (level >= 0) ? Array(level*4+1).join(\" \") : \"\";\n\n                    code = code.substring(0, i+1) + indent + code.substring(i+1);\n                    if (level > 0) i += level*4;\n                    break;\n            }\n            i++;\n        }\n\n        code = code\n            // Add strategic spaces\n            .replace(/\\s*([!<>=+-/*]?)=\\s*/g, \" $1= \")\n            .replace(/\\s*<([=]?)\\s*/g, \" <$1 \")\n            .replace(/\\s*>([=]?)\\s*/g, \" >$1 \")\n            .replace(/([^+])\\+([^+=])/g, \"$1 + $2\")\n            .replace(/([^-])-([^-=])/g, \"$1 - $2\")\n            .replace(/([^*])\\*([^*=])/g, \"$1 * $2\")\n            .replace(/([^/])\\/([^/=])/g, \"$1 / $2\")\n            .replace(/\\s*,\\s*/g, \", \")\n            .replace(/\\s*{/g, \" {\")\n            .replace(/}\\n/g, \"}\\n\\n\")\n            // Hacky horribleness\n            .replace(/(if|for|while|with|elif|elseif)\\s*\\(([^\\n]*)\\)\\s*\\n([^{])/gim, \"$1 ($2)\\n    $3\")\n            .replace(/(if|for|while|with|elif|elseif)\\s*\\(([^\\n]*)\\)([^{])/gim, \"$1 ($2) $3\")\n            .replace(/else\\s*\\n([^{])/gim, \"else\\n    $1\")\n            .replace(/else\\s+([^{])/gim, \"else $1\")\n            // Remove strategic spaces\n            .replace(/\\s+;/g, \";\")\n            .replace(/\\{\\s+\\}/g, \"{}\")\n            .replace(/\\[\\s+\\]/g, \"[]\")\n            .replace(/}\\s*(else|catch|except|finally|elif|elseif|else if)/gi, \"} $1\");\n\n        // Replace preserved tokens\n        const ptokens = /###preservedToken(\\d+)###/g;\n        while ((m = ptokens.exec(code))) {\n            const ti = parseInt(m[1], 10);\n            code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length);\n            ptokens.lastIndex = m.index;\n        }\n\n        return code;\n\n        /**\n         * Replaces a matched token with a placeholder value.\n         */\n        function preserveToken(str, match, t) {\n            preservedTokens[t] = match[0];\n            return str.substring(0, match.index) +\n                \"###preservedToken\" + t + \"###\" +\n                str.substring(match.index + match[0].length);\n        }\n    }\n\n}\n\nexport default GenericCodeBeautify;\n"
  },
  {
    "path": "src/core/operations/GetAllCasings.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Permutate String operation\n */\nclass GetAllCasings extends Operation {\n\n    /**\n     * GetAllCasings constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Get All Casings\";\n        this.module = \"Default\";\n        this.description = \"Outputs all possible casing variations of a string.\";\n        this.infoURL = \"\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const length = input.length;\n        const max = 1 << length;\n        input = input.toLowerCase();\n        let result = \"\";\n\n        for (let i = 0; i < max; i++) {\n            const temp = input.split(\"\");\n            for (let j = 0; j < length; j++) {\n                if (((i >> j) & 1) === 1) {\n                    temp[j] = temp[j].toUpperCase();\n                }\n            }\n            result += temp.join(\"\") + \"\\n\";\n        }\n        return result.slice(0, -1);\n    }\n}\n\nexport default GetAllCasings;\n"
  },
  {
    "path": "src/core/operations/GetTime.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {UNITS} from \"../lib/DateTime.mjs\";\n\n/**\n * Get Time operation\n */\nclass GetTime extends Operation {\n\n    /**\n     * GetTime constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Get Time\";\n        this.module = \"Default\";\n        this.description = \"Generates a timestamp showing the amount of time since the UNIX epoch (1970-01-01 00:00:00 UTC). Uses the W3C High Resolution Time API.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Unix_time\";\n        this.inputType = \"string\";\n        this.outputType = \"number\";\n        this.args = [\n            {\n                name: \"Granularity\",\n                type: \"option\",\n                value: UNITS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        const nowMs = (performance.timeOrigin + performance.now()),\n            granularity = args[0];\n\n        switch (granularity) {\n            case \"Nanoseconds (ns)\":\n                return Math.round(nowMs * 1000 * 1000);\n            case \"Microseconds (μs)\":\n                return Math.round(nowMs * 1000);\n            case \"Milliseconds (ms)\":\n                return Math.round(nowMs);\n            case \"Seconds (s)\":\n                return Math.round(nowMs / 1000);\n            default:\n                throw new OperationError(\"Unknown granularity value: \" + granularity);\n        }\n    }\n\n}\n\nexport default GetTime;\n"
  },
  {
    "path": "src/core/operations/GroupIPAddresses.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {IP_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport {ipv6ToStr, genIpv6Mask, IPV4_REGEX, strToIpv6,  ipv4ToStr, IPV6_REGEX, strToIpv4} from \"../lib/IP.mjs\";\n\n/**\n * Group IP addresses operation\n */\nclass GroupIPAddresses extends Operation {\n\n    /**\n     * GroupIPAddresses constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Group IP addresses\";\n        this.module = \"Default\";\n        this.description = \"Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Subnetwork\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": IP_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Subnet (CIDR)\",\n                \"type\": \"number\",\n                \"value\": 24\n            },\n            {\n                \"name\": \"Only show the subnets\",\n                \"type\": \"boolean\",\n                \"value\": false,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0]),\n            cidr = args[1],\n            onlySubnets = args[2],\n            ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF,\n            ipv6Mask = genIpv6Mask(cidr),\n            ips = input.split(delim),\n            ipv4Networks = {},\n            ipv6Networks = {};\n        let match = null,\n            output = \"\",\n            ip = null,\n            network = null,\n            networkStr = \"\",\n            i;\n\n        if (cidr < 0 || cidr > 127) {\n            throw new OperationError(\"CIDR must be less than 32 for IPv4 or 128 for IPv6\");\n        }\n\n        // Parse all IPs and add to network dictionary\n        for (i = 0; i < ips.length; i++) {\n            if ((match = IPV4_REGEX.exec(ips[i]))) {\n                ip = strToIpv4(match[1]) >>> 0;\n                network = ip & ipv4Mask;\n\n                if (network in ipv4Networks) {\n                    ipv4Networks[network].push(ip);\n                } else {\n                    ipv4Networks[network] = [ip];\n                }\n            } else if ((match = IPV6_REGEX.exec(ips[i]))) {\n                ip = strToIpv6(match[1]);\n                network = [];\n                networkStr = \"\";\n\n                for (let j = 0; j < 8; j++) {\n                    network.push(ip[j] & ipv6Mask[j]);\n                }\n\n                networkStr = ipv6ToStr(network, true);\n\n                if (networkStr in ipv6Networks) {\n                    ipv6Networks[networkStr].push(ip);\n                } else {\n                    ipv6Networks[networkStr] = [ip];\n                }\n            }\n        }\n\n        // Sort IPv4 network dictionaries and print\n        for (network in ipv4Networks) {\n            ipv4Networks[network] = ipv4Networks[network].sort();\n\n            output += ipv4ToStr(network) + \"/\" + cidr + \"\\n\";\n\n            if (!onlySubnets) {\n                for (i = 0; i < ipv4Networks[network].length; i++) {\n                    output += \"  \" + ipv4ToStr(ipv4Networks[network][i]) + \"\\n\";\n                }\n                output += \"\\n\";\n            }\n        }\n\n        // Sort IPv6 network dictionaries and print\n        for (networkStr in ipv6Networks) {\n            // ipv6Networks[networkStr] = ipv6Networks[networkStr].sort();  TODO\n\n            output += networkStr + \"/\" + cidr + \"\\n\";\n\n            if (!onlySubnets) {\n                for (i = 0; i < ipv6Networks[networkStr].length; i++) {\n                    output += \"  \" + ipv6ToStr(ipv6Networks[networkStr][i], true) + \"\\n\";\n                }\n                output += \"\\n\";\n            }\n        }\n\n        return output;\n    }\n\n}\n\nexport default GroupIPAddresses;\n"
  },
  {
    "path": "src/core/operations/Gunzip.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport gunzip from \"zlibjs/bin/gunzip.min.js\";\n\nconst Zlib = gunzip.Zlib;\n\n/**\n * Gunzip operation\n */\nclass Gunzip extends Operation {\n\n    /**\n     * Gunzip constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Gunzip\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data which has been compressed using the deflate algorithm with gzip headers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Gzip\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern: \"^\\\\x1f\\\\x8b\\\\x08\",\n                flags: \"\",\n                args: []\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {File}\n     */\n    run(input, args) {\n        const gzipObj = new Zlib.Gunzip(new Uint8Array(input));\n        return new Uint8Array(gzipObj.decompress()).buffer;\n    }\n\n}\n\nexport default Gunzip;\n"
  },
  {
    "path": "src/core/operations/Gzip.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from \"../lib/Zlib.mjs\";\nimport gzip from \"zlibjs/bin/gzip.min.js\";\n\nconst Zlib = gzip.Zlib;\n\n/**\n * Gzip operation\n */\nclass Gzip extends Operation {\n\n    /**\n     * Gzip constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Gzip\";\n        this.module = \"Compression\";\n        this.description = \"Compresses data using the deflate algorithm with gzip headers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Gzip\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Compression type\",\n                type: \"option\",\n                value: COMPRESSION_TYPE\n            },\n            {\n                name: \"Filename (optional)\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Comment (optional)\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Include file checksum\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const filename = args[1],\n            comment = args[2],\n            options = {\n                deflateOptions: {\n                    compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]]\n                },\n                flags: {\n                    fhcrc: args[3]\n                }\n            };\n\n        if (filename.length) {\n            options.flags.fname = true;\n            options.filename = filename;\n        }\n        if (comment.length) {\n            options.flags.comment = true;\n            options.comment = comment;\n        }\n        const gzipObj = new Zlib.Gzip(new Uint8Array(input), options);\n        const compressed = new Uint8Array(gzipObj.compress());\n        if (options.flags.comment && !(compressed[3] & 0x10)) {\n            compressed[3] |= 0x10;\n        }\n        return compressed.buffer;\n    }\n\n}\n\nexport default Gzip;\n"
  },
  {
    "path": "src/core/operations/HAS160.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * HAS-160 operation\n */\nclass HAS160 extends Operation {\n\n    /**\n     * HAS-160 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"HAS-160\";\n        this.module = \"Crypto\";\n        this.description = \"HAS-160 is a cryptographic hash function designed for use with the Korean KCDSA digital signature algorithm. It is derived from SHA-1, with assorted changes intended to increase its security. It produces a 160-bit output.<br><br>HAS-160 is used in the same way as SHA-1. First it divides input in blocks of 512 bits each and pads the final block. A digest function updates the intermediate hash value by processing the input blocks in turn.<br><br>The message digest algorithm consists, by default, of 80 rounds.\";\n        this.infoURL = \"https://wikipedia.org/wiki/HAS-160\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 80,\n                min: 1,\n                max: 80\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return runHash(\"has160\", input, {rounds: args[0]});\n    }\n\n}\n\nexport default HAS160;\n"
  },
  {
    "path": "src/core/operations/HASSHClientFingerprint.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n *\n * HASSH created by Salesforce\n *   Ben Reardon (@benreardon)\n *   Adel Karimi (@0x4d31)\n *   and the JA3 crew:\n *     John B. Althouse\n *     Jeff Atkinson\n *     Josh Atkins\n *\n * Algorithm released under the BSD-3-clause licence\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * HASSH Client Fingerprint operation\n */\nclass HASSHClientFingerprint extends Operation {\n\n    /**\n     * HASSHClientFingerprint constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"HASSH Client Fingerprint\";\n        this.module = \"Crypto\";\n        this.description = \"Generates a HASSH fingerprint to help identify SSH clients based on hashing together values from the Client Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Client to Server.\";\n        this.infoURL = \"https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Base64\", \"Raw\"]\n            },\n            {\n                name: \"Output format\",\n                type: \"option\",\n                value: [\"Hash digest\", \"HASSH algorithms string\", \"Full details\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat, outputFormat] = args;\n\n        input = Utils.convertToByteArray(input, inputFormat);\n        const s = new Stream(new Uint8Array(input));\n\n        // Length\n        const length = s.readInt(4);\n        if (s.length !== length + 4)\n            throw new OperationError(\"Incorrect packet length.\");\n\n        // Padding length\n        const paddingLength = s.readInt(1);\n\n        // Message code\n        const messageCode = s.readInt(1);\n        if (messageCode !== 20)\n            throw new OperationError(\"Not a Key Exchange Init.\");\n\n        // Cookie\n        s.moveForwardsBy(16);\n\n        // KEX Algorithms\n        const kexAlgosLength = s.readInt(4);\n        const kexAlgos = s.readString(kexAlgosLength);\n\n        // Server Host Key Algorithms\n        const serverHostKeyAlgosLength = s.readInt(4);\n        s.moveForwardsBy(serverHostKeyAlgosLength);\n\n        // Encryption Algorithms Client to Server\n        const encAlgosC2SLength = s.readInt(4);\n        const encAlgosC2S = s.readString(encAlgosC2SLength);\n\n        // Encryption Algorithms Server to Client\n        const encAlgosS2CLength = s.readInt(4);\n        s.moveForwardsBy(encAlgosS2CLength);\n\n        // MAC Algorithms Client to Server\n        const macAlgosC2SLength = s.readInt(4);\n        const macAlgosC2S = s.readString(macAlgosC2SLength);\n\n        // MAC Algorithms Server to Client\n        const macAlgosS2CLength = s.readInt(4);\n        s.moveForwardsBy(macAlgosS2CLength);\n\n        // Compression Algorithms Client to Server\n        const compAlgosC2SLength = s.readInt(4);\n        const compAlgosC2S = s.readString(compAlgosC2SLength);\n\n        // Compression Algorithms Server to Client\n        const compAlgosS2CLength = s.readInt(4);\n        s.moveForwardsBy(compAlgosS2CLength);\n\n        // Languages Client to Server\n        const langsC2SLength = s.readInt(4);\n        s.moveForwardsBy(langsC2SLength);\n\n        // Languages Server to Client\n        const langsS2CLength = s.readInt(4);\n        s.moveForwardsBy(langsS2CLength);\n\n        // First KEX packet follows\n        s.moveForwardsBy(1);\n\n        // Reserved\n        s.moveForwardsBy(4);\n\n        // Padding string\n        s.moveForwardsBy(paddingLength);\n\n        // Output\n        const hassh = [\n            kexAlgos,\n            encAlgosC2S,\n            macAlgosC2S,\n            compAlgosC2S\n        ];\n        const hasshStr = hassh.join(\";\");\n        const hasshHash = runHash(\"md5\", Utils.strToArrayBuffer(hasshStr));\n\n        switch (outputFormat) {\n            case \"HASSH algorithms string\":\n                return hasshStr;\n            case \"Full details\":\n                return `Hash digest:\n${hasshHash}\n\nFull HASSH algorithms string:\n${hasshStr}\n\nKey Exchange Algorithms:\n${kexAlgos}\nEncryption Algorithms Client to Server:\n${encAlgosC2S}\nMAC Algorithms Client to Server:\n${macAlgosC2S}\nCompression Algorithms Client to Server:\n${compAlgosC2S}`;\n            case \"Hash digest\":\n            default:\n                return hasshHash;\n        }\n    }\n\n}\n\nexport default HASSHClientFingerprint;\n"
  },
  {
    "path": "src/core/operations/HASSHServerFingerprint.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n *\n * HASSH created by Salesforce\n *   Ben Reardon (@benreardon)\n *   Adel Karimi (@0x4d31)\n *   and the JA3 crew:\n *     John B. Althouse\n *     Jeff Atkinson\n *     Josh Atkins\n *\n * Algorithm released under the BSD-3-clause licence\n*/\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * HASSH Server Fingerprint operation\n */\nclass HASSHServerFingerprint extends Operation {\n\n    /**\n     * HASSHServerFingerprint constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"HASSH Server Fingerprint\";\n        this.module = \"Crypto\";\n        this.description = \"Generates a HASSH fingerprint to help identify SSH servers based on hashing together values from the Server Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Server to Client.\";\n        this.infoURL = \"https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Base64\", \"Raw\"]\n            },\n            {\n                name: \"Output format\",\n                type: \"option\",\n                value: [\"Hash digest\", \"HASSH algorithms string\", \"Full details\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat, outputFormat] = args;\n\n        input = Utils.convertToByteArray(input, inputFormat);\n        const s = new Stream(new Uint8Array(input));\n\n        // Length\n        const length = s.readInt(4);\n        if (s.length !== length + 4)\n            throw new OperationError(\"Incorrect packet length.\");\n\n        // Padding length\n        const paddingLength = s.readInt(1);\n\n        // Message code\n        const messageCode = s.readInt(1);\n        if (messageCode !== 20)\n            throw new OperationError(\"Not a Key Exchange Init.\");\n\n        // Cookie\n        s.moveForwardsBy(16);\n\n        // KEX Algorithms\n        const kexAlgosLength = s.readInt(4);\n        const kexAlgos = s.readString(kexAlgosLength);\n\n        // Server Host Key Algorithms\n        const serverHostKeyAlgosLength = s.readInt(4);\n        s.moveForwardsBy(serverHostKeyAlgosLength);\n\n        // Encryption Algorithms Client to Server\n        const encAlgosC2SLength = s.readInt(4);\n        s.moveForwardsBy(encAlgosC2SLength);\n\n        // Encryption Algorithms Server to Client\n        const encAlgosS2CLength = s.readInt(4);\n        const encAlgosS2C = s.readString(encAlgosS2CLength);\n\n        // MAC Algorithms Client to Server\n        const macAlgosC2SLength = s.readInt(4);\n        s.moveForwardsBy(macAlgosC2SLength);\n\n        // MAC Algorithms Server to Client\n        const macAlgosS2CLength = s.readInt(4);\n        const macAlgosS2C = s.readString(macAlgosS2CLength);\n\n        // Compression Algorithms Client to Server\n        const compAlgosC2SLength = s.readInt(4);\n        s.moveForwardsBy(compAlgosC2SLength);\n\n        // Compression Algorithms Server to Client\n        const compAlgosS2CLength = s.readInt(4);\n        const compAlgosS2C = s.readString(compAlgosS2CLength);\n\n        // Languages Client to Server\n        const langsC2SLength = s.readInt(4);\n        s.moveForwardsBy(langsC2SLength);\n\n        // Languages Server to Client\n        const langsS2CLength = s.readInt(4);\n        s.moveForwardsBy(langsS2CLength);\n\n        // First KEX packet follows\n        s.moveForwardsBy(1);\n\n        // Reserved\n        s.moveForwardsBy(4);\n\n        // Padding string\n        s.moveForwardsBy(paddingLength);\n\n        // Output\n        const hassh = [\n            kexAlgos,\n            encAlgosS2C,\n            macAlgosS2C,\n            compAlgosS2C\n        ];\n        const hasshStr = hassh.join(\";\");\n        const hasshHash = runHash(\"md5\", Utils.strToArrayBuffer(hasshStr));\n\n        switch (outputFormat) {\n            case \"HASSH algorithms string\":\n                return hasshStr;\n            case \"Full details\":\n                return `Hash digest:\n${hasshHash}\n\nFull HASSH algorithms string:\n${hasshStr}\n\nKey Exchange Algorithms:\n${kexAlgos}\nEncryption Algorithms Server to Client:\n${encAlgosS2C}\nMAC Algorithms Server to Client:\n${macAlgosS2C}\nCompression Algorithms Server to Client:\n${compAlgosS2C}`;\n            case \"Hash digest\":\n            default:\n                return hasshHash;\n        }\n    }\n\n}\n\nexport default HASSHServerFingerprint;\n"
  },
  {
    "path": "src/core/operations/HMAC.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport CryptoApi from \"crypto-api/src/crypto-api.mjs\";\n\n/**\n * HMAC operation\n */\nclass HMAC extends Operation {\n\n    /**\n     * HMAC constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"HMAC\";\n        this.module = \"Crypto\";\n        this.description = \"Keyed-Hash Message Authentication Codes (HMAC) are a mechanism for message authentication using cryptographic hash functions.\";\n        this.infoURL = \"https://wikipedia.org/wiki/HMAC\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"Decimal\", \"Base64\", \"UTF8\", \"Latin1\"]\n            },\n            {\n                \"name\": \"Hashing function\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"MD2\",\n                    \"MD4\",\n                    \"MD5\",\n                    \"SHA0\",\n                    \"SHA1\",\n                    \"SHA224\",\n                    \"SHA256\",\n                    \"SHA384\",\n                    \"SHA512\",\n                    \"SHA512/224\",\n                    \"SHA512/256\",\n                    \"RIPEMD128\",\n                    \"RIPEMD160\",\n                    \"RIPEMD256\",\n                    \"RIPEMD320\",\n                    \"HAS160\",\n                    \"Whirlpool\",\n                    \"Whirlpool-0\",\n                    \"Whirlpool-T\",\n                    \"Snefru\"\n                ]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string || \"\", args[0].option),\n            hashFunc = args[1].toLowerCase(),\n            msg = Utils.arrayBufferToStr(input, false),\n            hasher = CryptoApi.getHasher(hashFunc);\n\n        const mac = CryptoApi.getHmac(key, hasher);\n        mac.update(msg);\n        return CryptoApi.encoder.toHex(mac.finalize());\n    }\n\n}\n\nexport default HMAC;\n"
  },
  {
    "path": "src/core/operations/HTMLToText.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * HTML To Text operation\n */\nclass HTMLToText extends Operation {\n\n    /**\n     * HTMLToText constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"HTML To Text\";\n        this.module = \"Default\";\n        this.description = \"Converts an HTML output from an operation to a readable string instead of being rendered in the DOM.\";\n        this.infoURL = \"\";\n        this.inputType = \"html\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {html} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return input;\n    }\n\n}\n\nexport default HTMLToText;\n"
  },
  {
    "path": "src/core/operations/HTTPRequest.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * HTTP request operation\n */\nclass HTTPRequest extends Operation {\n\n    /**\n     * HTTPRequest constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"HTTP request\";\n        this.module = \"Default\";\n        this.description = [\n            \"Makes an HTTP request and returns the response.\",\n            \"<br><br>\",\n            \"This operation supports different HTTP verbs like GET, POST, PUT, etc.\",\n            \"<br><br>\",\n            \"You can add headers line by line in the format <code>Key: Value</code>\",\n            \"<br><br>\",\n            \"The status code of the response, along with a limited selection of exposed headers, can be viewed by checking the 'Show response metadata' option. Only a limited set of response headers are exposed by the browser for security reasons.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.manualBake = true;\n        this.args = [\n            {\n                \"name\": \"Method\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"GET\", \"POST\", \"HEAD\",\n                    \"PUT\", \"PATCH\", \"DELETE\",\n                    \"CONNECT\", \"TRACE\", \"OPTIONS\"\n                ]\n            },\n            {\n                \"name\": \"URL\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Headers\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Cross-Origin Resource Sharing\",\n                    \"No CORS (limited to HEAD, GET or POST)\",\n                ]\n            },\n            {\n                \"name\": \"Show response metadata\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [method, url, headersText, mode, showResponseMetadata] = args;\n\n        if (url.length === 0) return \"\";\n\n        const headers = new Headers();\n        headersText.split(/\\r?\\n/).forEach(line => {\n            line = line.trim();\n\n            if (line.length === 0) return;\n\n            const split = line.split(\":\");\n            if (split.length !== 2) throw `Could not parse header in line: ${line}`;\n\n            headers.set(split[0].trim(), split[1].trim());\n        });\n\n        const config = {\n            method: method,\n            headers: headers,\n            mode: modeLookup[mode],\n            cache: \"no-cache\",\n        };\n\n        if (method !== \"GET\" && method !== \"HEAD\") {\n            config.body = input;\n        }\n\n        return fetch(url, config)\n            .then(r => {\n                if (r.status === 0 && r.type === \"opaque\") {\n                    throw new OperationError(\"Error: Null response. Try setting the connection mode to CORS.\");\n                }\n\n                if (showResponseMetadata) {\n                    let headers = \"\";\n                    for (const pair of r.headers.entries()) {\n                        headers += \"    \" + pair[0] + \": \" + pair[1] + \"\\n\";\n                    }\n                    return r.text().then(b => {\n                        return \"####\\n  Status: \" + r.status + \" \" + r.statusText +\n                            \"\\n  Exposed headers:\\n\" + headers + \"####\\n\\n\" + b;\n                    });\n                }\n                return r.text();\n            })\n            .catch(e => {\n                throw new OperationError(e.toString() +\n                    \"\\n\\nThis error could be caused by one of the following:\\n\" +\n                    \" - An invalid URL\\n\" +\n                    \" - Making a request to an insecure resource (HTTP) from a secure source (HTTPS)\\n\" +\n                    \" - Making a cross-origin request to a server which does not support CORS\\n\");\n            });\n    }\n\n}\n\n\n/**\n * Lookup table for HTTP modes\n *\n * @private\n */\nconst modeLookup = {\n    \"Cross-Origin Resource Sharing\": \"cors\",\n    \"No CORS (limited to HEAD, GET or POST)\": \"no-cors\",\n};\n\n\nexport default HTTPRequest;\n"
  },
  {
    "path": "src/core/operations/HammingDistance.mjs",
    "content": "/**\n * @author GCHQ Contributor [2]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {fromHex} from \"../lib/Hex.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Hamming Distance operation\n */\nclass HammingDistance extends Operation {\n\n    /**\n     * HammingDistance constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Hamming Distance\";\n        this.module = \"Default\";\n        this.description = \"In information theory, the Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different. In other words, it measures the minimum number of substitutions required to change one string into the other, or the minimum number of errors that could have transformed one string into the other. In a more general context, the Hamming distance is one of several string metrics for measuring the edit distance between two sequences.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Hamming_distance\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\n\\\\n\"\n            },\n            {\n                \"name\": \"Unit\",\n                \"type\": \"option\",\n                \"value\": [\"Byte\", \"Bit\"]\n            },\n            {\n                \"name\": \"Input type\",\n                \"type\": \"option\",\n                \"value\": [\"Raw string\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = args[0],\n            byByte = args[1] === \"Byte\",\n            inputType = args[2],\n            samples = input.split(delim);\n\n        if (samples.length !== 2) {\n            throw new OperationError(\"Error: You can only calculate the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter.\");\n        }\n\n        if (samples[0].length !== samples[1].length) {\n            throw new OperationError(\"Error: Both inputs must be of the same length.\");\n        }\n\n        if (inputType === \"Hex\") {\n            samples[0] = fromHex(samples[0]);\n            samples[1] = fromHex(samples[1]);\n        } else {\n            samples[0] = new Uint8Array(Utils.strToArrayBuffer(samples[0]));\n            samples[1] = new Uint8Array(Utils.strToArrayBuffer(samples[1]));\n        }\n\n        let dist = 0;\n\n        for (let i = 0; i < samples[0].length; i++) {\n            const lhs = samples[0][i],\n                rhs = samples[1][i];\n\n            if (byByte && lhs !== rhs) {\n                dist++;\n            } else if (!byByte) {\n                let xord = lhs ^ rhs;\n\n                while (xord) {\n                    dist++;\n                    xord &= xord - 1;\n                }\n            }\n        }\n\n        return dist.toString();\n    }\n\n}\n\nexport default HammingDistance;\n"
  },
  {
    "path": "src/core/operations/HaversineDistance.mjs",
    "content": "/**\n * @author Dachande663 [dachande663@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * HaversineDistance operation\n */\nclass HaversineDistance extends Operation {\n\n    /**\n     * HaversineDistance constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Haversine distance\";\n        this.module = \"Default\";\n        this.description = \"Returns the distance between two pairs of GPS latitude and longitude co-ordinates in metres.<br><br>e.g. <code>51.487263,-0.124323, 38.9517,-77.1467</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Haversine_formula\";\n        this.inputType = \"string\";\n        this.outputType = \"number\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n\n        const values = input.match(/^(-?\\d+(\\.\\d+)?), ?(-?\\d+(\\.\\d+)?), ?(-?\\d+(\\.\\d+)?), ?(-?\\d+(\\.\\d+)?)$/);\n        if (!values) {\n            throw new OperationError(\"Input must in the format lat1, lng1, lat2, lng2\");\n        }\n\n        const lat1 = parseFloat(values[1]);\n        const lng1 = parseFloat(values[3]);\n        const lat2 = parseFloat(values[5]);\n        const lng2 = parseFloat(values[7]);\n\n        const TO_RAD = Math.PI / 180;\n        const dLat = (lat2-lat1) * TO_RAD;\n        const dLng = (lng2-lng1) * TO_RAD;\n        const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * TO_RAD) * Math.cos(lat2 * TO_RAD) * Math.sin(dLng/2) * Math.sin(dLng/2);\n        const metres = 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n\n        return metres;\n\n    }\n\n}\n\nexport default HaversineDistance;\n"
  },
  {
    "path": "src/core/operations/Head.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {INPUT_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * Head operation\n */\nclass Head extends Operation {\n\n    /**\n     * Head constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Head\";\n        this.module = \"Default\";\n        this.description = \"Like the UNIX head utility.<br>Gets the first n lines.<br>You can select all but the last n lines by entering a negative value for n.<br>The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": INPUT_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Number\",\n                \"type\": \"number\",\n                \"value\": 10\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let delimiter = args[0];\n        const number = args[1];\n\n        delimiter = Utils.charRep(delimiter);\n        const splitInput = input.split(delimiter);\n\n        return splitInput\n            .filter((line, lineIndex) => {\n                lineIndex += 1;\n\n                if (number < 0) {\n                    return lineIndex <= splitInput.length + number;\n                } else {\n                    return lineIndex <= number;\n                }\n            })\n            .join(delimiter);\n    }\n\n}\n\nexport default Head;\n"
  },
  {
    "path": "src/core/operations/HeatmapChart.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport * as d3temp from \"d3\";\nimport * as nodomtemp from \"nodom\";\nimport { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from \"../lib/Charts.mjs\";\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\n\nconst d3 = d3temp.default ? d3temp.default : d3temp;\nconst nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;\n\n/**\n * Heatmap chart operation\n */\nclass HeatmapChart extends Operation {\n\n    /**\n     * HeatmapChart constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Heatmap chart\";\n        this.module = \"Charts\";\n        this.description = \"A heatmap is a graphical representation of data where the individual values contained in a matrix are represented as colors.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Heat_map\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                name: \"Record delimiter\",\n                type: \"option\",\n                value: RECORD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"Field delimiter\",\n                type: \"option\",\n                value: FIELD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"Number of vertical bins\",\n                type: \"number\",\n                value: 25,\n            },\n            {\n                name: \"Number of horizontal bins\",\n                type: \"number\",\n                value: 25,\n            },\n            {\n                name: \"Use column headers as labels\",\n                type: \"boolean\",\n                value: true,\n            },\n            {\n                name: \"X label\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Y label\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Draw bin edges\",\n                type: \"boolean\",\n                value: false,\n            },\n            {\n                name: \"Min colour value\",\n                type: \"string\",\n                value: COLOURS.min,\n            },\n            {\n                name: \"Max colour value\",\n                type: \"string\",\n                value: COLOURS.max,\n            },\n        ];\n    }\n\n    /**\n     * Heatmap chart operation.\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const recordDelimiter = Utils.charRep(args[0]),\n            fieldDelimiter = Utils.charRep(args[1]),\n            vBins = args[2],\n            hBins = args[3],\n            columnHeadingsAreIncluded = args[4],\n            drawEdges = args[7],\n            minColour = args[8],\n            maxColour = args[9],\n            dimension = 500;\n        if (vBins <= 0) throw new OperationError(\"Number of vertical bins must be greater than 0\");\n        if (hBins <= 0) throw new OperationError(\"Number of horizontal bins must be greater than 0\");\n\n        let xLabel = args[5],\n            yLabel = args[6];\n        const { headings, values } = getScatterValues(\n            input,\n            recordDelimiter,\n            fieldDelimiter,\n            columnHeadingsAreIncluded\n        );\n\n        if (headings) {\n            xLabel = headings.x;\n            yLabel = headings.y;\n        }\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${dimension} ${dimension}`);\n\n        const margin = {\n                top: 10,\n                right: 0,\n                bottom: 40,\n                left: 30,\n            },\n            width = dimension - margin.left - margin.right,\n            height = dimension - margin.top - margin.bottom,\n            binWidth = width / hBins,\n            binHeight = height/ vBins,\n            marginedSpace = svg.append(\"g\")\n                .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\");\n\n        const bins = this.getHeatmapPacking(values, vBins, hBins),\n            maxCount = Math.max(...bins.map(row => {\n                const lengths = row.map(cell => cell.length);\n                return Math.max(...lengths);\n            }));\n\n        const xExtent = d3.extent(values, d => d[0]),\n            yExtent = d3.extent(values, d => d[1]);\n\n        const xAxis = d3.scaleLinear()\n            .domain(xExtent)\n            .range([0, width]);\n        const yAxis = d3.scaleLinear()\n            .domain(yExtent)\n            .range([height, 0]);\n\n        const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))\n            .domain([0, maxCount]);\n\n        marginedSpace.append(\"clipPath\")\n            .attr(\"id\", \"clip\")\n            .append(\"rect\")\n            .attr(\"width\", width)\n            .attr(\"height\", height);\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"bins\")\n            .attr(\"clip-path\", \"url(#clip)\")\n            .selectAll(\"g\")\n            .data(bins)\n            .enter()\n            .append(\"g\")\n            .selectAll(\"rect\")\n            .data(d => d)\n            .enter()\n            .append(\"rect\")\n            .attr(\"x\", (d) => binWidth * d.x)\n            .attr(\"y\", (d) => (height - binHeight * (d.y + 1)))\n            .attr(\"width\", binWidth)\n            .attr(\"height\", binHeight)\n            .attr(\"fill\", (d) => colour(d.length))\n            .attr(\"stroke\", drawEdges ? \"rgba(0, 0, 0, 0.5)\" : \"none\")\n            .attr(\"stroke-width\", drawEdges ? \"0.5\" : \"none\")\n            .append(\"title\")\n            .text(d => {\n                const count = d.length,\n                    perc = 100.0 * d.length / values.length,\n                    tooltip = `Count: ${count}\\n\n                               Percentage: ${perc.toFixed(2)}%\\n\n                    `.replace(/\\s{2,}/g, \"\\n\");\n                return tooltip;\n            });\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"axis axis--y\")\n            .call(d3.axisLeft(yAxis).tickSizeOuter(-width));\n\n        svg.append(\"text\")\n            .attr(\"transform\", \"rotate(-90)\")\n            .attr(\"y\", -margin.left)\n            .attr(\"x\", -(height / 2))\n            .attr(\"dy\", \"1em\")\n            .style(\"text-anchor\", \"middle\")\n            .text(yLabel);\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"axis axis--x\")\n            .attr(\"transform\", \"translate(0,\" + height + \")\")\n            .call(d3.axisBottom(xAxis).tickSizeOuter(-height));\n\n        svg.append(\"text\")\n            .attr(\"x\", width / 2)\n            .attr(\"y\", dimension)\n            .style(\"text-anchor\", \"middle\")\n            .text(xLabel);\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n    /**\n     * Packs a list of x, y coordinates into a number of bins for use in a heatmap.\n     *\n     * @param {Object[]} points\n     * @param {number} number of vertical bins\n     * @param {number} number of horizontal bins\n     * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points\n     */\n    getHeatmapPacking(values, vBins, hBins) {\n        const xBounds = d3.extent(values, d => d[0]),\n            yBounds = d3.extent(values, d => d[1]),\n            bins = [];\n\n        if (xBounds[0] === xBounds[1]) throw \"Cannot pack points. There is no difference between the minimum and maximum X coordinate.\";\n        if (yBounds[0] === yBounds[1]) throw \"Cannot pack points. There is no difference between the minimum and maximum Y coordinate.\";\n\n        for (let y = 0; y < vBins; y++) {\n            bins.push([]);\n            for (let x = 0; x < hBins; x++) {\n                const item = [];\n                item.y = y;\n                item.x = x;\n\n                bins[y].push(item);\n            } // x\n        } // y\n\n        const epsilon = 0.000000001; // This is to clamp values that are exactly the maximum;\n\n        values.forEach(v => {\n            const fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]),\n                fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]),\n                y = Math.floor(vBins * fractionOfY),\n                x = Math.floor(hBins * fractionOfX);\n\n            bins[y][x].push({x: v[0], y: v[1]});\n        });\n\n        return bins;\n    }\n\n}\n\nexport default HeatmapChart;\n"
  },
  {
    "path": "src/core/operations/HexDensityChart.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport * as d3temp from \"d3\";\nimport * as d3hexbintemp from \"d3-hexbin\";\nimport * as nodomtemp from \"nodom\";\nimport { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from \"../lib/Charts.mjs\";\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\nconst d3 = d3temp.default ? d3temp.default : d3temp;\nconst d3hexbin = d3hexbintemp.default ? d3hexbintemp.default : d3hexbintemp;\nconst nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;\n\n\n/**\n * Hex Density chart operation\n */\nclass HexDensityChart extends Operation {\n\n    /**\n     * HexDensityChart constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Hex Density chart\";\n        this.module = \"Charts\";\n        this.description = \"Hex density charts are used in a similar way to scatter charts, however rather than rendering tens of thousands of points, it groups the points into a few hundred hexagons to show the distribution.\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                name: \"Record delimiter\",\n                type: \"option\",\n                value: RECORD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"Field delimiter\",\n                type: \"option\",\n                value: FIELD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"Pack radius\",\n                type: \"number\",\n                value: 25,\n            },\n            {\n                name: \"Draw radius\",\n                type: \"number\",\n                value: 15,\n            },\n            {\n                name: \"Use column headers as labels\",\n                type: \"boolean\",\n                value: true,\n            },\n            {\n                name: \"X label\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Y label\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Draw hexagon edges\",\n                type: \"boolean\",\n                value: false,\n            },\n            {\n                name: \"Min colour value\",\n                type: \"string\",\n                value: COLOURS.min,\n            },\n            {\n                name: \"Max colour value\",\n                type: \"string\",\n                value: COLOURS.max,\n            },\n            {\n                name: \"Draw empty hexagons within data boundaries\",\n                type: \"boolean\",\n                value: false,\n            }\n        ];\n    }\n\n\n    /**\n     * Hex Bin chart operation.\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const recordDelimiter = Utils.charRep(args[0]),\n            fieldDelimiter = Utils.charRep(args[1]),\n            packRadius = args[2],\n            drawRadius = args[3],\n            columnHeadingsAreIncluded = args[4],\n            drawEdges = args[7],\n            minColour = args[8],\n            maxColour = args[9],\n            drawEmptyHexagons = args[10],\n            dimension = 500;\n\n        let xLabel = args[5],\n            yLabel = args[6];\n        const { headings, values } = getScatterValues(\n            input,\n            recordDelimiter,\n            fieldDelimiter,\n            columnHeadingsAreIncluded\n        );\n\n        if (headings) {\n            xLabel = headings.x;\n            yLabel = headings.y;\n        }\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${dimension} ${dimension}`);\n\n        const margin = {\n                top: 10,\n                right: 0,\n                bottom: 40,\n                left: 30,\n            },\n            width = dimension - margin.left - margin.right,\n            height = dimension - margin.top - margin.bottom,\n            marginedSpace = svg.append(\"g\")\n                .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\");\n\n        const hexbin = d3hexbin.hexbin()\n            .radius(packRadius)\n            .extent([0, 0], [width, height]);\n\n        const hexPoints = hexbin(values),\n            maxCount = Math.max(...hexPoints.map(b => b.length));\n\n        const xExtent = d3.extent(hexPoints, d => d.x),\n            yExtent = d3.extent(hexPoints, d => d.y);\n        xExtent[0] -= 2 * packRadius;\n        xExtent[1] += 3 * packRadius;\n        yExtent[0] -= 2 * packRadius;\n        yExtent[1] += 2 * packRadius;\n\n        const xAxis = d3.scaleLinear()\n            .domain(xExtent)\n            .range([0, width]);\n        const yAxis = d3.scaleLinear()\n            .domain(yExtent)\n            .range([height, 0]);\n\n        const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))\n            .domain([0, maxCount]);\n\n        marginedSpace.append(\"clipPath\")\n            .attr(\"id\", \"clip\")\n            .append(\"rect\")\n            .attr(\"width\", width)\n            .attr(\"height\", height);\n\n        if (drawEmptyHexagons) {\n            marginedSpace.append(\"g\")\n                .attr(\"class\", \"empty-hexagon\")\n                .selectAll(\"path\")\n                .data(this.getEmptyHexagons(hexPoints, packRadius))\n                .enter()\n                .append(\"path\")\n                .attr(\"d\", d => {\n                    return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;\n                })\n                .attr(\"fill\", (d) => colour(0))\n                .attr(\"stroke\", drawEdges ? \"black\" : \"none\")\n                .attr(\"stroke-width\", drawEdges ? \"0.5\" : \"none\")\n                .append(\"title\")\n                .text(d => {\n                    const count = 0,\n                        perc = 0,\n                        tooltip = `Count: ${count}\\n\n                                Percentage: ${perc.toFixed(2)}%\\n\n                                Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\\n\n                        `.replace(/\\s{2,}/g, \"\\n\");\n                    return tooltip;\n                });\n        }\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"hexagon\")\n            .attr(\"clip-path\", \"url(#clip)\")\n            .selectAll(\"path\")\n            .data(hexPoints)\n            .enter()\n            .append(\"path\")\n            .attr(\"d\", d => {\n                return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;\n            })\n            .attr(\"fill\", (d) => colour(d.length))\n            .attr(\"stroke\", drawEdges ? \"black\" : \"none\")\n            .attr(\"stroke-width\", drawEdges ? \"0.5\" : \"none\")\n            .append(\"title\")\n            .text(d => {\n                const count = d.length,\n                    perc = 100.0 * d.length / values.length,\n                    CX = d.x,\n                    CY = d.y,\n                    xMin = Math.min(...d.map(d => d[0])),\n                    xMax = Math.max(...d.map(d => d[0])),\n                    yMin = Math.min(...d.map(d => d[1])),\n                    yMax = Math.max(...d.map(d => d[1])),\n                    tooltip = `Count: ${count}\\n\n                               Percentage: ${perc.toFixed(2)}%\\n\n                               Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\\n\n                               Min X: ${xMin.toFixed(2)}\\n\n                               Max X: ${xMax.toFixed(2)}\\n\n                               Min Y: ${yMin.toFixed(2)}\\n\n                               Max Y: ${yMax.toFixed(2)}\n                    `.replace(/\\s{2,}/g, \"\\n\");\n                return tooltip;\n            });\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"axis axis--y\")\n            .call(d3.axisLeft(yAxis).tickSizeOuter(-width));\n\n        svg.append(\"text\")\n            .attr(\"transform\", \"rotate(-90)\")\n            .attr(\"y\", -margin.left)\n            .attr(\"x\", -(height / 2))\n            .attr(\"dy\", \"1em\")\n            .style(\"text-anchor\", \"middle\")\n            .text(yLabel);\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"axis axis--x\")\n            .attr(\"transform\", \"translate(0,\" + height + \")\")\n            .call(d3.axisBottom(xAxis).tickSizeOuter(-height));\n\n        svg.append(\"text\")\n            .attr(\"x\", width / 2)\n            .attr(\"y\", dimension)\n            .style(\"text-anchor\", \"middle\")\n            .text(xLabel);\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n\n    /**\n     * Hex Bin chart operation.\n     *\n     * @param {Object[]} - centres\n     * @param {number} - radius\n     * @returns {Object[]}\n     */\n    getEmptyHexagons(centres, radius) {\n        const emptyCentres = [],\n            boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)],\n            hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius,\n            hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius;\n        let indent = false;\n\n        for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) {\n            for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) {\n                let cx = x;\n                const cy = y;\n\n                if (indent && x >= boundingRect[0][1]) break;\n                if (indent) cx += hexagonCenterToEdge;\n\n                emptyCentres.push({x: cx, y: cy});\n            }\n            indent = !indent;\n        }\n\n        return emptyCentres;\n    }\n\n}\n\nexport default HexDensityChart;\n"
  },
  {
    "path": "src/core/operations/HexToObjectIdentifier.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Hex to Object Identifier operation\n */\nclass HexToObjectIdentifier extends Operation {\n\n    /**\n     * HexToObjectIdentifier constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Hex to Object Identifier\";\n        this.module = \"PublicKey\";\n        this.description = \"Converts a hexadecimal string into an object identifier (OID).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Object_identifier\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\\s/g, \"\"));\n    }\n\n}\n\nexport default HexToObjectIdentifier;\n"
  },
  {
    "path": "src/core/operations/HexToPEM.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Hex to PEM operation\n */\nclass HexToPEM extends Operation {\n\n    /**\n     * HexToPEM constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Hex to PEM\";\n        this.module = \"PublicKey\";\n        this.description = \"Converts a hexadecimal DER (Distinguished Encoding Rules) string into PEM (Privacy Enhanced Mail) format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Privacy-Enhanced_Mail\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Header string\",\n                \"type\": \"string\",\n                \"value\": \"CERTIFICATE\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\\s/g, \"\"), args[0]);\n    }\n\n}\n\nexport default HexToPEM;\n"
  },
  {
    "path": "src/core/operations/IPv6TransitionAddresses.mjs",
    "content": "/**\n * @author jb30795 [jb30795@proton.me]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * IPv6 Transition Addresses operation\n */\nclass IPv6TransitionAddresses extends Operation {\n\n    /**\n     * IPv6TransitionAddresses constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"IPv6 Transition Addresses\";\n        this.module = \"Default\";\n        this.description = \"Converts IPv4 addresses to their IPv6 Transition addresses. IPv6 Transition addresses can also be converted back into their original IPv4 address. MAC addresses can also be converted into the EUI-64 format, this can them be appended to your IPv6 /64 range to obtain a full /128 address.<br><br>Transition technologies enable translation between IPv4 and IPv6 addresses or tunneling to allow traffic to pass through the incompatible network, allowing the two standards to coexist.<br><br>Only /24 ranges and currently handled. Remove headers to easily copy out results.\";\n        this.infoURL = \"https://wikipedia.org/wiki/IPv6_transition_mechanism\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n\t    {\n                \"name\": \"Ignore ranges\",\n                \"type\": \"boolean\",\n\t        \"value\": true\n\t    },\n            {\n                \"name\": \"Remove headers\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const XOR = {\"0\": \"2\", \"1\": \"3\", \"2\": \"0\", \"3\": \"1\", \"4\": \"6\", \"5\": \"7\", \"6\": \"4\", \"7\": \"5\", \"8\": \"a\", \"9\": \"b\", \"a\": \"8\", \"b\": \"9\", \"c\": \"e\", \"d\": \"f\", \"e\": \"c\", \"f\": \"d\"};\n\n        /**\n\t * Function to convert to hex\n\t */\n        function hexify(octet) {\n            return Number(octet).toString(16).padStart(2, \"0\");\n        }\n\n        /**\n\t * Function to convert Hex to Int\n\t */\n        function intify(hex) {\n            return parseInt(hex, 16);\n        }\n\n        /**\n\t * Function converts IPv4 to IPv6 Transtion address\n\t */\n        function ipTransition(input, range) {\n            let output = \"\";\n            const HEXIP = input.split(\".\");\n\n            /**\n\t     * 6to4\n\t     */\n            if (!args[1]) {\n                output += \"6to4: \";\n            }\n            output += \"2002:\" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + \":\" + hexify(HEXIP[2]);\n            if (range) {\n                output += \"00::/40\\n\";\n            } else {\n                output += hexify(HEXIP[3]) + \"::/48\\n\";\n            }\n\n            /**\n\t     * Mapped\n\t     */\n            if (!args[1]) {\n                output += \"IPv4 Mapped: \";\n            }\n            output += \"::ffff:\" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + \":\" + hexify(HEXIP[2]);\n            if (range) {\n                output += \"00/120\\n\";\n            } else {\n                output += hexify(HEXIP[3]) + \"\\n\";\n            }\n\n            /**\n\t     * Translated\n\t     */\n            if (!args[1]) {\n                output += \"IPv4 Translated: \";\n            }\n            output += \"::ffff:0:\" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + \":\" + hexify(HEXIP[2]);\n            if (range) {\n                output += \"00/120\\n\";\n            } else {\n                output += hexify(HEXIP[3]) + \"\\n\";\n            }\n\n            /**\n\t     * Nat64\n\t     */\n            if (!args[1]) {\n                output += \"Nat 64: \";\n            }\n            output += \"64:ff9b::\" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + \":\" + hexify(HEXIP[2]);\n            if (range) {\n                output += \"00/120\\n\";\n            } else {\n                output += hexify(HEXIP[3]) + \"\\n\";\n            }\n\n            return output;\n        }\n\n        /**\n\t * Convert MAC to EUI-64\n\t */\n        function macTransition(input) {\n            let output = \"\";\n            const MACPARTS = input.split(\":\");\n            if (!args[1]) {\n                output += \"EUI-64 Interface ID: \";\n            }\n            const MAC = MACPARTS[0] + MACPARTS[1] + \":\" + MACPARTS[2] + \"ff:fe\" + MACPARTS[3] + \":\" + MACPARTS[4] + MACPARTS[5];\n            output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2);\n\n            return output;\n        }\n\n\n        /**\n\t * Convert IPv6 address to its original IPv4 or MAC address\n\t */\n        function unTransition(input) {\n            let output = \"\";\n            let hextets = \"\";\n\n            /**\n\t     * 6to4\n\t     */\n            if (input.startsWith(\"2002:\")) {\n                if (!args[1]) {\n                    output += \"IPv4: \";\n                }\n                output += String(intify(input.slice(5, 7))) + \".\" + String(intify(input.slice(7, 9)))+ \".\" + String(intify(input.slice(10, 12)))+ \".\" + String(intify(input.slice(12, 14))) + \"\\n\";\n            } else if (input.startsWith(\"::ffff:\") || input.startsWith(\"0000:0000:0000:0000:0000:ffff:\") || input.startsWith(\"::ffff:0000:\") || input.startsWith(\"0000:0000:0000:0000:ffff:0000:\") || input.startsWith(\"64:ff9b::\") || input.startsWith(\"0064:ff9b:0000:0000:0000:0000:\")) {\n\t\t/**\n\t\t * Mapped/Translated/Nat64\n\t\t */\n                hextets = /:([0-9a-z]{1,4}):[0-9a-z]{1,4}$/.exec(input)[1].padStart(4, \"0\") + /:([0-9a-z]{1,4})$/.exec(input)[1].padStart(4, \"0\");\n                if (!args[1]) {\n                    output += \"IPv4: \";\n                }\n                output += intify(hextets.slice(-8, -7) +  hextets.slice(-7, -6)) + \".\" +intify(hextets.slice(-6, -5) +  hextets.slice(-5, -4)) + \".\" +intify(hextets.slice(-4, -3) +  hextets.slice(-3, -2)) + \".\" +intify(hextets.slice(-2, -1) +  hextets.slice(-1,)) + \"\\n\";\n            } else if (input.slice(-12, -7).toUpperCase() === \"FF:FE\") {\n\t\t/**\n\t\t * EUI-64\n\t\t */\n                if (!args[1]) {\n                    output += \"Mac Address: \";\n                }\n                const MAC = (input.slice(-19, -17) + \":\" + input.slice(-17, -15) + \":\" + input.slice(-14, -12) + \":\" + input.slice(-7, -5) + \":\" + input.slice(-4, -2) + \":\" + input.slice(-2,)).toUpperCase();\n                output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + \"\\n\";\n            }\n\n            return output;\n        }\n\n\n        /**\n\t * Main\n\t */\n        let output = \"\";\n        let inputs = input.split(\"\\n\");\n        // Remove blank rows\n        inputs = inputs.filter(Boolean);\n\n        for (let i = 0; i < inputs.length; i++) {\n            // if ignore ranges is checked and input is a range, skip\n            if ((args[0] && !inputs[i].includes(\"/\")) || (!args[0])) {\n                if (/^[0-9]{1,3}(?:\\.[0-9]{1,3}){3}$/.test(inputs[i])) {\n                    output += ipTransition(inputs[i], false);\n                } else if (/\\/24$/.test(inputs[i])) {\n                    output += ipTransition(inputs[i], true);\n                } else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) {\n                    output += macTransition(inputs[i]);\n                } else if (/^((?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(inputs[i])) {\n                    output += unTransition(inputs[i]);\n                } else {\n                    output = \"Enter compressed or expanded IPv6 address, IPv4 address or MAC Address.\";\n                }\n            }\n        }\n\n        return output;\n    }\n\n}\n\nexport default IPv6TransitionAddresses;\n"
  },
  {
    "path": "src/core/operations/ImageBrightnessContrast.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Image Brightness / Contrast operation\n */\nclass ImageBrightnessContrast extends Operation {\n    /**\n     * ImageBrightnessContrast constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Image Brightness / Contrast\";\n        this.module = \"Image\";\n        this.description = \"Adjust the brightness or contrast of an image.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Brightness\",\n                type: \"number\",\n                value: 0,\n                min: -100,\n                max: 100,\n            },\n            {\n                name: \"Contrast\",\n                type: \"number\",\n                value: 0,\n                min: -100,\n                max: 100,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [brightness, contrast] = args;\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (brightness !== 0) {\n                if (isWorkerEnvironment())\n                    self.sendStatusMessage(\"Changing image brightness...\");\n                image.brightness(brightness / 100);\n            }\n            if (contrast !== 0) {\n                if (isWorkerEnvironment())\n                    self.sendStatusMessage(\"Changing image contrast...\");\n                image.contrast(contrast / 100);\n            }\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(\n                `Error adjusting image brightness or contrast. (${err})`,\n            );\n        }\n    }\n\n    /**\n     * Displays the image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default ImageBrightnessContrast;\n"
  },
  {
    "path": "src/core/operations/ImageFilter.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Image Filter operation\n */\nclass ImageFilter extends Operation {\n    /**\n     * ImageFilter constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Image Filter\";\n        this.module = \"Image\";\n        this.description = \"Applies a greyscale or sepia filter to an image.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Filter type\",\n                type: \"option\",\n                value: [\"Greyscale\", \"Sepia\"],\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [filterType] = args;\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\n                    \"Applying \" +\n                        filterType.toLowerCase() +\n                        \" filter to image...\",\n                );\n            if (filterType === \"Greyscale\") {\n                image.greyscale();\n            } else {\n                image.sepia();\n            }\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(\n                `Error applying filter to image. (${err})`,\n            );\n        }\n    }\n\n    /**\n     * Displays the blurred image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default ImageFilter;\n"
  },
  {
    "path": "src/core/operations/ImageHueSaturationLightness.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Image Hue/Saturation/Lightness operation\n */\nclass ImageHueSaturationLightness extends Operation {\n    /**\n     * ImageHueSaturationLightness constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Image Hue/Saturation/Lightness\";\n        this.module = \"Image\";\n        this.description =\n            \"Adjusts the hue / saturation / lightness (HSL) values of an image.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Hue\",\n                type: \"number\",\n                value: 0,\n                min: -360,\n                max: 360,\n            },\n            {\n                name: \"Saturation\",\n                type: \"number\",\n                value: 0,\n                min: -100,\n                max: 100,\n            },\n            {\n                name: \"Lightness\",\n                type: \"number\",\n                value: 0,\n                min: -100,\n                max: 100,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [hue, saturation, lightness] = args;\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (hue !== 0) {\n                if (isWorkerEnvironment())\n                    self.sendStatusMessage(\"Changing image hue...\");\n                image.color([\n                    {\n                        apply: \"hue\",\n                        params: [hue],\n                    },\n                ]);\n            }\n            if (saturation !== 0) {\n                if (isWorkerEnvironment())\n                    self.sendStatusMessage(\"Changing image saturation...\");\n                image.color([\n                    {\n                        apply: \"saturate\",\n                        params: [saturation],\n                    },\n                ]);\n            }\n            if (lightness !== 0) {\n                if (isWorkerEnvironment())\n                    self.sendStatusMessage(\"Changing image lightness...\");\n                image.color([\n                    {\n                        apply: \"lighten\",\n                        params: [lightness],\n                    },\n                ]);\n            }\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(\n                `Error adjusting image hue / saturation / lightness. (${err})`,\n            );\n        }\n    }\n\n    /**\n     * Displays the image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default ImageHueSaturationLightness;\n"
  },
  {
    "path": "src/core/operations/ImageOpacity.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Image Opacity operation\n */\nclass ImageOpacity extends Operation {\n    /**\n     * ImageOpacity constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Image Opacity\";\n        this.module = \"Image\";\n        this.description = \"Adjust the opacity of an image.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Opacity (%)\",\n                type: \"number\",\n                value: 100,\n                min: 0,\n                max: 100,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [opacity] = args;\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Changing image opacity...\");\n            image.opacity(opacity / 100);\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error changing image opacity. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default ImageOpacity;\n"
  },
  {
    "path": "src/core/operations/IndexOfCoincidence.mjs",
    "content": "/**\n * @author George O [georgeomnet+cyberchef@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Index of Coincidence operation\n */\nclass IndexOfCoincidence extends Operation {\n\n    /**\n     * IndexOfCoincidence constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Index of Coincidence\";\n        this.module = \"Default\";\n        this.description = \"Index of Coincidence (IC) is the probability of two randomly selected characters being the same. This can be used to determine whether text is readable or random, with English text having an IC of around 0.066. IC can therefore be a sound method to automate frequency analysis.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Index_of_coincidence\";\n        this.inputType = \"string\";\n        this.outputType = \"number\";\n        this.presentType = \"html\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        const text = input.toLowerCase().replace(/[^a-z]/g, \"\"),\n            frequencies = new Array(26).fill(0),\n            alphabet = Utils.expandAlphRange(\"a-z\");\n        let coincidence = 0.00,\n            density = 0.00,\n            result = 0.00,\n            i;\n\n        for (i=0; i < alphabet.length; i++) {\n            frequencies[i] = text.count(alphabet[i]);\n        }\n\n        for (i=0; i < frequencies.length; i++) {\n            coincidence += frequencies[i] * (frequencies[i] - 1);\n        }\n\n        density = frequencies.sum();\n\n        // Ensure that we don't divide by 0\n        if (density < 2) density = 2;\n\n        result = coincidence / (density * (density - 1));\n\n        return result;\n    }\n\n    /**\n     * Displays the IC as a scale bar for web apps.\n     *\n     * @param {number} ic\n     * @returns {html}\n     */\n    present(ic) {\n        return `Index of Coincidence: ${ic}\nNormalized: ${ic * 26}\n<br><canvas id='chart-area'></canvas><br>\n- 0 represents complete randomness (all characters are unique), whereas 1 represents no randomness (all characters are identical).\n- English text generally has an IC of between 0.67 to 0.78.\n- 'Random' text is determined by the probability that each letter occurs the same number of times as another.\n\nThe graph shows the IC of the input data. A low IC generally means that the text is random, compressed or encrypted.\n\n<script type='application/javascript'>\n  var canvas = document.getElementById(\"chart-area\"),\n      parentRect = canvas.closest(\".cm-scroller\").getBoundingClientRect(),\n      ic = ${ic};\n\n  canvas.width = parentRect.width * 0.95;\n  canvas.height = parentRect.height * 0.25;\n\n  ic = ic > 0.25 ? 0.25 : ic;\n\n  CanvasComponents.drawScaleBar(canvas, ic, 0.25, [\n    {\n      label: \"English text\",\n      min: 0.05,\n      max: 0.08\n    },\n    {\n      label: \"> 0.25\",\n      min: 0.24,\n      max: 0.25\n    }\n  ]);\n</script>\n     `;\n    }\n\n}\n\nexport default IndexOfCoincidence;\n"
  },
  {
    "path": "src/core/operations/InvertImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Invert Image operation\n */\nclass InvertImage extends Operation {\n    /**\n     * InvertImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Invert Image\";\n        this.module = \"Image\";\n        this.description = \"Invert the colours of an image.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid input file format.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Inverting image...\");\n            image.invert();\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error inverting image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the inverted image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default InvertImage;\n"
  },
  {
    "path": "src/core/operations/JA3Fingerprint.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n *\n * JA3 created by Salesforce\n *   John B. Althouse\n *   Jeff Atkinson\n *   Josh Atkins\n *\n * Algorithm released under the BSD-3-clause licence\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * JA3 Fingerprint operation\n */\nclass JA3Fingerprint extends Operation {\n\n    /**\n     * JA3Fingerprint constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JA3 Fingerprint\";\n        this.module = \"Crypto\";\n        this.description = \"Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS Client Hello packet application layer.\";\n        this.infoURL = \"https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Base64\", \"Raw\"]\n            },\n            {\n                name: \"Output format\",\n                type: \"option\",\n                value: [\"Hash digest\", \"JA3 string\", \"Full details\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat, outputFormat] = args;\n\n        input = Utils.convertToByteArray(input, inputFormat);\n        const s = new Stream(new Uint8Array(input));\n\n        const handshake = s.readInt(1);\n        if (handshake !== 0x16)\n            throw new OperationError(\"Not handshake data.\");\n\n        // Version\n        s.moveForwardsBy(2);\n\n        // Length\n        const length = s.readInt(2);\n        if (s.length !== length + 5)\n            throw new OperationError(\"Incorrect handshake length.\");\n\n        // Handshake type\n        const handshakeType = s.readInt(1);\n        if (handshakeType !== 1)\n            throw new OperationError(\"Not a Client Hello.\");\n\n        // Handshake length\n        const handshakeLength = s.readInt(3);\n        if (s.length !== handshakeLength + 9)\n            throw new OperationError(\"Not enough data in Client Hello.\");\n\n        // Hello version\n        const helloVersion = s.readInt(2);\n\n        // Random\n        s.moveForwardsBy(32);\n\n        // Session ID\n        const sessionIDLength = s.readInt(1);\n        s.moveForwardsBy(sessionIDLength);\n\n        // Cipher suites\n        const cipherSuitesLength = s.readInt(2);\n        const cipherSuites = s.getBytes(cipherSuitesLength);\n        const cs = new Stream(cipherSuites);\n        const cipherSegment = parseJA3Segment(cs, 2);\n\n        // Compression Methods\n        const compressionMethodsLength = s.readInt(1);\n        s.moveForwardsBy(compressionMethodsLength);\n\n        // Extensions\n        const extensionsLength = s.readInt(2);\n        const extensions = s.getBytes(extensionsLength);\n        const es = new Stream(extensions);\n        let ecsLen, ecs, ellipticCurves = \"\", ellipticCurvePointFormats = \"\";\n        const exts = [];\n        while (es.hasMore()) {\n            const type = es.readInt(2);\n            const length = es.readInt(2);\n            switch (type) {\n                case 0x0a: // Elliptic curves\n                    ecsLen = es.readInt(2);\n                    ecs = new Stream(es.getBytes(ecsLen));\n                    ellipticCurves = parseJA3Segment(ecs, 2);\n                    break;\n                case 0x0b: // Elliptic curve point formats\n                    ecsLen = es.readInt(1);\n                    ecs = new Stream(es.getBytes(ecsLen));\n                    ellipticCurvePointFormats = parseJA3Segment(ecs, 1);\n                    break;\n                default:\n                    es.moveForwardsBy(length);\n            }\n            if (!GREASE_CIPHERSUITES.includes(type))\n                exts.push(type);\n        }\n\n        // Output\n        const ja3 = [\n            helloVersion.toString(),\n            cipherSegment,\n            exts.join(\"-\"),\n            ellipticCurves,\n            ellipticCurvePointFormats\n        ];\n        const ja3Str = ja3.join(\",\");\n        const ja3Hash = runHash(\"md5\", Utils.strToArrayBuffer(ja3Str));\n\n        switch (outputFormat) {\n            case \"JA3 string\":\n                return ja3Str;\n            case \"Full details\":\n                return `Hash digest:\n${ja3Hash}\n\nFull JA3 string:\n${ja3Str}\n\nTLS Version:\n${helloVersion.toString()}\nCipher Suites:\n${cipherSegment}\nExtensions:\n${exts.join(\"-\")}\nElliptic Curves:\n${ellipticCurves}\nElliptic Curve Point Formats:\n${ellipticCurvePointFormats}`;\n            case \"Hash digest\":\n            default:\n                return ja3Hash;\n        }\n    }\n\n}\n\n/**\n * Parses a JA3 segment, returning a \"-\" separated list\n *\n * @param {Stream} stream\n * @returns {string}\n */\nfunction parseJA3Segment(stream, size=2) {\n    const segment = [];\n    while (stream.hasMore()) {\n        const element = stream.readInt(size);\n        if (!GREASE_CIPHERSUITES.includes(element))\n            segment.push(element);\n    }\n    return segment.join(\"-\");\n}\n\nconst GREASE_CIPHERSUITES = [\n    0x0a0a,\n    0x1a1a,\n    0x2a2a,\n    0x3a3a,\n    0x4a4a,\n    0x5a5a,\n    0x6a6a,\n    0x7a7a,\n    0x8a8a,\n    0x9a9a,\n    0xaaaa,\n    0xbaba,\n    0xcaca,\n    0xdada,\n    0xeaea,\n    0xfafa\n];\n\nexport default JA3Fingerprint;\n"
  },
  {
    "path": "src/core/operations/JA3SFingerprint.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n *\n * JA3S created by Salesforce\n *   John B. Althouse\n *   Jeff Atkinson\n *   Josh Atkins\n *\n * Algorithm released under the BSD-3-clause licence\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * JA3S Fingerprint operation\n */\nclass JA3SFingerprint extends Operation {\n\n    /**\n     * JA3SFingerprint constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JA3S Fingerprint\";\n        this.module = \"Crypto\";\n        this.description = \"Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS Server Hello record application layer.\";\n        this.infoURL = \"https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Base64\", \"Raw\"]\n            },\n            {\n                name: \"Output format\",\n                type: \"option\",\n                value: [\"Hash digest\", \"JA3S string\", \"Full details\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat, outputFormat] = args;\n\n        input = Utils.convertToByteArray(input, inputFormat);\n        const s = new Stream(new Uint8Array(input));\n\n        const handshake = s.readInt(1);\n        if (handshake !== 0x16)\n            throw new OperationError(\"Not handshake data.\");\n\n        // Version\n        s.moveForwardsBy(2);\n\n        // Length\n        const length = s.readInt(2);\n        if (s.length !== length + 5)\n            throw new OperationError(\"Incorrect handshake length.\");\n\n        // Handshake type\n        const handshakeType = s.readInt(1);\n        if (handshakeType !== 2)\n            throw new OperationError(\"Not a Server Hello.\");\n\n        // Handshake length\n        const handshakeLength = s.readInt(3);\n        if (s.length !== handshakeLength + 9)\n            throw new OperationError(\"Not enough data in Server Hello.\");\n\n        // Hello version\n        const helloVersion = s.readInt(2);\n\n        // Random\n        s.moveForwardsBy(32);\n\n        // Session ID\n        const sessionIDLength = s.readInt(1);\n        s.moveForwardsBy(sessionIDLength);\n\n        // Cipher suite\n        const cipherSuite = s.readInt(2);\n\n        // Compression Method\n        s.moveForwardsBy(1);\n\n        // Extensions\n        const extensionsLength = s.readInt(2);\n        const extensions = s.getBytes(extensionsLength);\n        const es = new Stream(extensions);\n        const exts = [];\n        while (es.hasMore()) {\n            const type = es.readInt(2);\n            const length = es.readInt(2);\n            es.moveForwardsBy(length);\n            exts.push(type);\n        }\n\n        // Output\n        const ja3s = [\n            helloVersion.toString(),\n            cipherSuite,\n            exts.join(\"-\")\n        ];\n        const ja3sStr = ja3s.join(\",\");\n        const ja3sHash = runHash(\"md5\", Utils.strToArrayBuffer(ja3sStr));\n\n        switch (outputFormat) {\n            case \"JA3S string\":\n                return ja3sStr;\n            case \"Full details\":\n                return `Hash digest:\n${ja3sHash}\n\nFull JA3S string:\n${ja3sStr}\n\nTLS Version:\n${helloVersion.toString()}\nCipher Suite:\n${cipherSuite}\nExtensions:\n${exts.join(\"-\")}`;\n            case \"Hash digest\":\n            default:\n                return ja3sHash;\n        }\n    }\n\n}\n\nexport default JA3SFingerprint;\n"
  },
  {
    "path": "src/core/operations/JA4Fingerprint.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {toJA4} from \"../lib/JA4.mjs\";\n\n/**\n * JA4 Fingerprint operation\n */\nclass JA4Fingerprint extends Operation {\n\n    /**\n     * JA4Fingerprint constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JA4 Fingerprint\";\n        this.module = \"Crypto\";\n        this.description = \"Generates a JA4 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS or QUIC Client Hello packet application layer.\";\n        this.infoURL = \"https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Base64\", \"Raw\"]\n            },\n            {\n                name: \"Output format\",\n                type: \"option\",\n                value: [\"JA4\", \"JA4 Original Rendering\", \"JA4 Raw\", \"JA4 Raw Original Rendering\", \"All\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat, outputFormat] = args;\n        input = Utils.convertToByteArray(input, inputFormat);\n        const ja4 = toJA4(new Uint8Array(input));\n\n        // Output\n        switch (outputFormat) {\n            case \"JA4\":\n                return ja4.JA4;\n            case \"JA4 Original Rendering\":\n                return ja4.JA4_o;\n            case \"JA4 Raw\":\n                return ja4.JA4_r;\n            case \"JA4 Raw Original Rendering\":\n                return ja4.JA4_ro;\n            case \"All\":\n            default:\n                return `JA4:    ${ja4.JA4}\nJA4_o:  ${ja4.JA4_o}\nJA4_r:  ${ja4.JA4_r}\nJA4_ro: ${ja4.JA4_ro}`;\n        }\n    }\n\n}\n\nexport default JA4Fingerprint;\n"
  },
  {
    "path": "src/core/operations/JA4ServerFingerprint.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {toJA4S} from \"../lib/JA4.mjs\";\n\n/**\n * JA4Server Fingerprint operation\n */\nclass JA4ServerFingerprint extends Operation {\n\n    /**\n     * JA4ServerFingerprint constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JA4Server Fingerprint\";\n        this.module = \"Crypto\";\n        this.description = \"Generates a JA4Server Fingerprint (JA4S) to help identify TLS servers or sessions based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS or QUIC Server Hello packet application layer.\";\n        this.infoURL = \"https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Base64\", \"Raw\"]\n            },\n            {\n                name: \"Output format\",\n                type: \"option\",\n                value: [\"JA4S\", \"JA4S Raw\", \"Both\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat, outputFormat] = args;\n        input = Utils.convertToByteArray(input, inputFormat);\n        const ja4s = toJA4S(new Uint8Array(input));\n\n        // Output\n        switch (outputFormat) {\n            case \"JA4S\":\n                return ja4s.JA4S;\n            case \"JA4S Raw\":\n                return ja4s.JA4S_r;\n            case \"Both\":\n            default:\n                return `JA4S:   ${ja4s.JA4S}\\nJA4S_r: ${ja4s.JA4S_r}`;\n        }\n    }\n\n}\n\nexport default JA4ServerFingerprint;\n"
  },
  {
    "path": "src/core/operations/JPathExpression.mjs",
    "content": "/**\n * @author Matt C (matt@artemisbot.uk)\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport {JSONPath} from \"jsonpath-plus\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * JPath expression operation\n */\nclass JPathExpression extends Operation {\n\n    /**\n     * JPathExpression constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JPath expression\";\n        this.module = \"Code\";\n        this.description = \"Extract information from a JSON object with a JPath query.\";\n        this.infoURL = \"http://goessner.net/articles/JsonPath/\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Query\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Result delimiter\",\n                type: \"binaryShortString\",\n                value: \"\\\\n\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [query, delimiter] = args;\n        let results, jsonObj;\n\n        try {\n            jsonObj = JSON.parse(input);\n        } catch (err) {\n            throw new OperationError(`Invalid input JSON: ${err.message}`);\n        }\n\n        try {\n            results = JSONPath({\n                path: query,\n                json: jsonObj\n            });\n        } catch (err) {\n            throw new OperationError(`Invalid JPath expression: ${err.message}`);\n        }\n\n        return results.map(result => JSON.stringify(result)).join(delimiter);\n    }\n\n}\n\nexport default JPathExpression;\n"
  },
  {
    "path": "src/core/operations/JSONBeautify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author Phillip Nordwall [phillip.nordwall@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport JSON5 from \"json5\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * JSON Beautify operation\n */\nclass JSONBeautify extends Operation {\n\n    /**\n     * JSONBeautify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JSON Beautify\";\n        this.module = \"Code\";\n        this.description = \"Indents and pretty prints JavaScript Object Notation (JSON) code.<br><br>Tags: json viewer, prettify, syntax highlighting\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Indent string\",\n                type: \"binaryShortString\",\n                value: \"    \"\n            },\n            {\n                name: \"Sort Object Keys\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Formatted\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input) return \"\";\n\n        const [indentStr, sortBool] = args;\n        let json = null;\n\n        try {\n            json = JSON5.parse(input);\n        } catch (err) {\n            throw new OperationError(\"Unable to parse input as JSON.\\n\" + err);\n        }\n\n        if (sortBool) json = sortKeys(json);\n\n        return JSON.stringify(json, null, indentStr);\n    }\n\n    /**\n     * Adds various dynamic features to the JSON blob\n     *\n     * @param {string} data\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    present(data, args) {\n        const formatted = args[2];\n        if (!formatted) return Utils.escapeHtml(data);\n\n        const json = JSON5.parse(data);\n        const options = {\n            withLinks: true,\n            bigNumbers: true\n        };\n        let html = '<div class=\"json-document\">';\n\n        if (isCollapsable(json)) {\n            const isArr = json instanceof Array;\n            html += '<details open class=\"json-details\">' +\n                `<summary class=\"json-summary ${isArr ? \"json-arr\" : \"json-obj\"}\"></summary>` +\n                json2html(json, options) +\n                \"</details>\";\n        } else {\n            html += json2html(json, options);\n        }\n\n        html += \"</div>\";\n        return html;\n    }\n}\n\n/**\n * Sort keys in a JSON object\n *\n * @author Phillip Nordwall [phillip.nordwall@gmail.com]\n * @param {object} o\n * @returns {object}\n */\nfunction sortKeys(o) {\n    if (Array.isArray(o)) {\n        return o.map(sortKeys);\n    } else if (\"[object Object]\" === Object.prototype.toString.call(o)) {\n        return Object.keys(o).sort().reduce(function(a, k) {\n            a[k] = sortKeys(o[k]);\n            return a;\n        }, {});\n    }\n    return o;\n}\n\n\n/**\n * Check if arg is either an array with at least 1 element, or a dict with at least 1 key\n * @returns {boolean}\n */\nfunction isCollapsable(arg) {\n    return arg instanceof Object && Object.keys(arg).length > 0;\n}\n\n/**\n * Check if a string looks like a URL, based on protocol\n * @returns {boolean}\n */\nfunction isUrl(string) {\n    const protocols = [\"http\", \"https\", \"ftp\", \"ftps\"];\n    for (let i = 0; i < protocols.length; i++) {\n        if (string.startsWith(protocols[i] + \"://\")) {\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n * Transform a json object into html representation\n *\n * Adapted for CyberChef by @n1474335 from jQuery json-viewer\n * @author Alexandre Bodelot <alexandre.bodelot@gmail.com>\n * @link https://github.com/abodelot/jquery.json-viewer\n * @license MIT\n *\n * @returns {string}\n */\nfunction json2html(json, options) {\n    let html = \"\";\n    if (typeof json === \"string\") {\n        // Escape tags and quotes\n        json = Utils.escapeHtml(json);\n\n        if (options.withLinks && isUrl(json)) {\n            html += `<a href=\"${json}\" class=\"json-string\" target=\"_blank\">${json}</a>`;\n        } else {\n            // Escape double quotes in the rendered non-URL string.\n            json = json.replace(/&quot;/g, \"\\\\&quot;\");\n            html += `<span class=\"json-string\">\"${json}\"</span>`;\n        }\n    } else if (typeof json === \"number\" || typeof json === \"bigint\") {\n        html += `<span class=\"json-literal\">${json}</span>`;\n    } else if (typeof json === \"boolean\") {\n        html += `<span class=\"json-literal\">${json}</span>`;\n    } else if (json === null) {\n        html += '<span class=\"json-literal\">null</span>';\n    } else if (json instanceof Array) {\n        if (json.length > 0) {\n            html += '<span class=\"json-bracket\">[</span><ol class=\"json-array\">';\n            for (let i = 0; i < json.length; i++) {\n                html += \"<li>\";\n\n                // Add toggle button if item is collapsable\n                if (isCollapsable(json[i])) {\n                    const isArr = json[i] instanceof Array;\n                    html += '<details open class=\"json-details\">' +\n                        `<summary class=\"json-summary ${isArr ? \"json-arr\" : \"json-obj\"}\"></summary>` +\n                        json2html(json[i], options) +\n                        \"</details>\";\n                } else {\n                    html += json2html(json[i], options);\n                }\n\n                // Add comma if item is not last\n                if (i < json.length - 1) {\n                    html += '<span class=\"json-comma\">,</span>';\n                }\n                html += \"</li>\";\n            }\n            html += '</ol><span class=\"json-bracket\">]</span>';\n        } else {\n            html += '<span class=\"json-bracket\">[]</span>';\n        }\n    } else if (typeof json === \"object\") {\n        // Optional support different libraries for big numbers\n        // json.isLosslessNumber: package lossless-json\n        // json.toExponential(): packages bignumber.js, big.js, decimal.js, decimal.js-light, others?\n        if (options.bigNumbers && (typeof json.toExponential === \"function\" || json.isLosslessNumber)) {\n            html += `<span class=\"json-literal\">${json.toString()}</span>`;\n        } else {\n            let keyCount = Object.keys(json).length;\n            if (keyCount > 0) {\n                html += '<span class=\"json-brace\">{</span><ul class=\"json-dict\">';\n                for (const key in json) {\n                    if (Object.prototype.hasOwnProperty.call(json, key)) {\n                        const safeKey = Utils.escapeHtml(key);\n                        html += \"<li>\";\n\n                        // Add toggle button if item is collapsable\n                        if (isCollapsable(json[key])) {\n                            const isArr = json[key] instanceof Array;\n                            html += '<details open class=\"json-details\">' +\n                                `<summary class=\"json-summary ${isArr ? \"json-arr\" : \"json-obj\"}\">${safeKey}<span class=\"json-colon\">:</span> </summary>` +\n                                json2html(json[key], options) +\n                                \"</details>\";\n                        } else {\n                            html += safeKey + '<span class=\"json-colon\">:</span> ' + json2html(json[key], options);\n                        }\n\n                        // Add comma if item is not last\n                        if (--keyCount > 0) {\n                            html += '<span class=\"json-comma\">,</span>';\n                        }\n                        html += \"</li>\";\n                    }\n                }\n                html += '</ul><span class=\"json-brace\">}</span>';\n            } else {\n                html += '<span class=\"json-brace\">{}</span>';\n            }\n        }\n    }\n    return html;\n}\n\nexport default JSONBeautify;\n"
  },
  {
    "path": "src/core/operations/JSONMinify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport vkbeautify from \"vkbeautify\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * JSON Minify operation\n */\nclass JSONMinify extends Operation {\n\n    /**\n     * JSONMinify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JSON Minify\";\n        this.module = \"Code\";\n        this.description = \"Compresses JavaScript Object Notation (JSON) code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input) return \"\";\n        return vkbeautify.jsonmin(input);\n    }\n\n}\n\nexport default JSONMinify;\n"
  },
  {
    "path": "src/core/operations/JSONToCSV.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport * as flat from \"flat\";\nconst flatten = flat.default ? flat.default.flatten : flat.flatten;\n\n/**\n * JSON to CSV operation\n */\nclass JSONToCSV extends Operation {\n\n    /**\n     * JSONToCSV constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JSON to CSV\";\n        this.module = \"Default\";\n        this.description = \"Converts JSON data to a CSV based on the definition in RFC 4180.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Comma-separated_values\";\n        this.inputType = \"JSON\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Cell delimiter\",\n                type: \"binaryShortString\",\n                value: \",\"\n            },\n            {\n                name: \"Row delimiter\",\n                type: \"binaryShortString\",\n                value: \"\\\\r\\\\n\"\n            }\n        ];\n    }\n\n    /**\n     * Converts JSON to a CSV equivalent.\n     *\n     * @param {boolean} force - Whether to force conversion of data to fit in a cell\n     * @returns {string}\n     */\n    toCSV(force=false) {\n        const self = this;\n        // If the JSON is an array of arrays, this is easy\n        if (this.flattened[0] instanceof Array) {\n            return this.flattened\n                .map(row => row\n                    .map(d => self.escapeCellContents(d, force))\n                    .join(this.cellDelim)\n                )\n                .join(this.rowDelim) +\n                this.rowDelim;\n        }\n\n        // If it's an array of dictionaries...\n        const header = Object.keys(this.flattened[0]);\n        return header\n            .map(d => self.escapeCellContents(d, force))\n            .join(this.cellDelim) +\n            this.rowDelim +\n            this.flattened\n                .map(row => header\n                    .map(h => row[h])\n                    .map(d => self.escapeCellContents(d, force))\n                    .join(this.cellDelim)\n                )\n                .join(this.rowDelim) +\n                this.rowDelim;\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [cellDelim, rowDelim] = args;\n\n        // Record values so they don't have to be passed to other functions explicitly\n        this.cellDelim = cellDelim;\n        this.rowDelim = rowDelim;\n        this.flattened = input;\n        if (!(this.flattened instanceof Array)) {\n            this.flattened = [input];\n        }\n\n        try {\n            return this.toCSV();\n        } catch (err) {\n            try {\n                this.flattened = flatten(input);\n                if (!(this.flattened instanceof Array)) {\n                    this.flattened = [this.flattened];\n                }\n                return this.toCSV(true);\n            } catch (err) {\n                throw new OperationError(\"Unable to parse JSON to CSV: \" + err.toString());\n            }\n        }\n    }\n\n    /**\n     * Correctly escapes a cell's contents based on the cell and row delimiters.\n     *\n     * @param {string} data\n     * @param {boolean} force - Whether to force conversion of data to fit in a cell\n     * @returns {string}\n     */\n    escapeCellContents(data, force=false) {\n        if (data !== \"string\") {\n            const isPrimitive = data == null || typeof data !== \"object\";\n            if (isPrimitive) data = `${data}`;\n            else if (force) data = JSON.stringify(data);\n        }\n\n        // Double quotes should be doubled up\n        data = data.replace(/\"/g, '\"\"');\n\n        // If the cell contains a cell or row delimiter or a double quote, it must be enclosed in double quotes\n        if (\n            data.indexOf(this.cellDelim) >= 0 ||\n            data.indexOf(this.rowDelim) >= 0 ||\n            data.indexOf(\"\\n\") >= 0 ||\n            data.indexOf(\"\\r\") >= 0 ||\n            data.indexOf('\"') >= 0\n        ) {\n            data = `\"${data}\"`;\n        }\n\n        return data;\n    }\n\n}\n\nexport default JSONToCSV;\n"
  },
  {
    "path": "src/core/operations/JSONtoYAML.mjs",
    "content": "/**\n * @author ccarpo [ccarpo@gmx.net]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport YAML from \"yaml\";\n\n/**\n * JSON to YAML operation\n */\nclass JSONtoYAML extends Operation {\n\n    /**\n     * JSONtoYAML constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JSON to YAML\";\n        this.module = \"Default\";\n        this.description = \"Format a JSON object into YAML\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/YAML\";\n        this.inputType = \"JSON\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        try {\n            return YAML.stringify(input);\n        } catch (err) {\n            throw new OperationError(\"Test\");\n        }\n    }\n\n}\n\nexport default JSONtoYAML;\n"
  },
  {
    "path": "src/core/operations/JWKToPem.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * PEM to JWK operation\n */\nclass PEMToJWK extends Operation {\n\n    /**\n     * PEMToJWK constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JWK to PEM\";\n        this.module = \"PublicKey\";\n        this.description = \"Converts Keys in JSON Web Key format to PEM format (PKCS#8).\";\n        this.infoURL = \"https://datatracker.ietf.org/doc/html/rfc7517\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                \"pattern\": \"\\\"kty\\\":\\\\s*\\\"(EC|RSA)\\\"\",\n                \"flags\": \"gm\",\n                \"args\": []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const inputJson = JSON.parse(input);\n\n        let keys = [];\n        if (Array.isArray(inputJson)) {\n            // list of keys => transform all keys\n            keys = inputJson;\n        } else if (Array.isArray(inputJson.keys)) {\n            // JSON Web Key Set => transform all keys\n            keys = inputJson.keys;\n        } else if (typeof inputJson === \"object\") {\n            // single key\n            keys.push(inputJson);\n        } else {\n            throw new OperationError(\"Input is not a JSON Web Key\");\n        }\n\n        let output = \"\";\n        for (let i=0; i<keys.length; i++) {\n            const jwk = keys[i];\n            if (typeof jwk.kty !== \"string\") {\n                throw new OperationError(\"Invalid JWK format\");\n            } else if (\"|RSA|EC|\".indexOf(jwk.kty) === -1) {\n                throw new OperationError(`Unsupported JWK key type '${inputJson.kty}'`);\n            }\n\n            const key = r.KEYUTIL.getKey(jwk);\n            const pem = key.isPrivate ? r.KEYUTIL.getPEM(key, \"PKCS8PRV\") : r.KEYUTIL.getPEM(key);\n\n            // PEM ends with '\\n', so a new key always starts on a new line\n            output += pem;\n        }\n\n        return output;\n    }\n}\n\nexport default PEMToJWK;\n"
  },
  {
    "path": "src/core/operations/JWTDecode.mjs",
    "content": "/**\n * @author gchq77703 []\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport jwt from \"jsonwebtoken\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * JWT Decode operation\n */\nclass JWTDecode extends Operation {\n\n    /**\n     * JWTDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JWT Decode\";\n        this.module = \"Crypto\";\n        this.description = \"Decodes a JSON Web Token <b>without</b> checking whether the provided secret / private key is valid. Use 'JWT Verify' to check if the signature is valid as well.\";\n        this.infoURL = \"https://wikipedia.org/wiki/JSON_Web_Token\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern: \"^ey([A-Za-z0-9_-]+)\\\\.ey([A-Za-z0-9_-]+)\\\\.([A-Za-z0-9_-]+)$\",\n                flags: \"\",\n                args: []\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        try {\n            const decoded = jwt.decode(input, {\n                json: true,\n                complete: true\n            });\n\n            return decoded.payload;\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default JWTDecode;\n"
  },
  {
    "path": "src/core/operations/JWTSign.mjs",
    "content": "/**\n * @author gchq77703 []\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport Operation from \"../Operation.mjs\";\nimport jwt from \"jsonwebtoken\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {JWT_ALGORITHMS} from \"../lib/JWT.mjs\";\n\n\n/**\n * JWT Sign operation\n */\nclass JWTSign extends Operation {\n\n    /**\n     * JWTSign constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JWT Sign\";\n        this.module = \"Crypto\";\n        this.description = \"Signs a JSON object as a JSON Web Token using a provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.\";\n        this.infoURL = \"https://wikipedia.org/wiki/JSON_Web_Token\";\n        this.inputType = \"JSON\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Private/Secret Key\",\n                type: \"text\",\n                value: \"secret\"\n            },\n            {\n                name: \"Signing algorithm\",\n                type: \"option\",\n                value: JWT_ALGORITHMS\n            },\n            {\n                name: \"Header\",\n                type: \"text\",\n                value: \"{}\"\n            }\n        ];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [key, algorithm, header] = args;\n\n        try {\n            return jwt.sign(input, key, {\n                algorithm: algorithm === \"None\" ? \"none\" : algorithm,\n                header: JSON.parse(header || \"{}\")\n            });\n        } catch (err) {\n            throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.\n\n${err}`);\n        }\n    }\n\n}\n\nexport default JWTSign;\n"
  },
  {
    "path": "src/core/operations/JWTVerify.mjs",
    "content": "/**\n * @author gchq77703 []\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport Operation from \"../Operation.mjs\";\nimport jwt from \"jsonwebtoken\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {JWT_ALGORITHMS} from \"../lib/JWT.mjs\";\n\n\n/**\n * JWT Verify operation\n */\nclass JWTVerify extends Operation {\n\n    /**\n     * JWTVerify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JWT Verify\";\n        this.module = \"Crypto\";\n        this.description = \"Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded public key for RSA and ECDSA.\";\n        this.infoURL = \"https://wikipedia.org/wiki/JSON_Web_Token\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.args = [\n            {\n                name: \"Public/Secret Key\",\n                type: \"text\",\n                value: \"secret\"\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [key] = args;\n        const algos = JWT_ALGORITHMS;\n        algos[algos.indexOf(\"None\")] = \"none\";\n\n        try {\n            const verified = jwt.verify(input, key, { algorithms: algos });\n\n            if (Object.prototype.hasOwnProperty.call(verified, \"name\") && verified.name === \"JsonWebTokenError\") {\n                throw new OperationError(verified.message);\n            }\n\n            return verified;\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default JWTVerify;\n"
  },
  {
    "path": "src/core/operations/JavaScriptBeautify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport escodegen from \"escodegen\";\nimport * as esprima from \"esprima\";\n\n/**\n * JavaScript Beautify operation\n */\nclass JavaScriptBeautify extends Operation {\n\n    /**\n     * JavaScriptBeautify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JavaScript Beautify\";\n        this.module = \"Code\";\n        this.description = \"Parses and pretty prints valid JavaScript code. Also works with JavaScript Object Notation (JSON).\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Indent string\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\t\"\n            },\n            {\n                \"name\": \"Quotes\",\n                \"type\": \"option\",\n                \"value\": [\"Auto\", \"Single\", \"Double\"]\n            },\n            {\n                \"name\": \"Semicolons before closing braces\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Include comments\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const beautifyIndent = args[0] || \"\\\\t\",\n            quotes = args[1].toLowerCase(),\n            [,, beautifySemicolons, beautifyComment] = args;\n        let result = \"\",\n            AST;\n\n        try {\n            AST = esprima.parseScript(input, {\n                range: true,\n                tokens: true,\n                comment: true\n            });\n\n            const options = {\n                format: {\n                    indent: {\n                        style: beautifyIndent\n                    },\n                    quotes: quotes,\n                    semicolons: beautifySemicolons,\n                },\n                comment: beautifyComment\n            };\n\n            if (options.comment)\n                AST = escodegen.attachComments(AST, AST.comments, AST.tokens);\n\n            result = escodegen.generate(AST, options);\n        } catch (e) {\n            // Leave original error so the user can see the detail\n            throw new OperationError(\"Unable to parse JavaScript.<br>\" + e.message);\n        }\n        return result;\n    }\n\n}\n\nexport default JavaScriptBeautify;\n"
  },
  {
    "path": "src/core/operations/JavaScriptMinify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport * as terser from \"terser\";\n\n/**\n * JavaScript Minify operation\n */\nclass JavaScriptMinify extends Operation {\n\n    /**\n     * JavaScriptMinify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JavaScript Minify\";\n        this.module = \"Code\";\n        this.description = \"Compresses JavaScript code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const result = await terser.minify(input);\n        if (result.error) {\n            throw new OperationError(`Error minifying JavaScript. (${result.error})`);\n        }\n        return result.code;\n    }\n\n}\n\nexport default JavaScriptMinify;\n"
  },
  {
    "path": "src/core/operations/JavaScriptParser.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport * as esprima from \"esprima\";\n\n/**\n * JavaScript Parser operation\n */\nclass JavaScriptParser extends Operation {\n\n    /**\n     * JavaScriptParser constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"JavaScript Parser\";\n        this.module = \"Code\";\n        this.description = \"Returns an Abstract Syntax Tree for valid JavaScript code.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Abstract_syntax_tree\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Location info\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Range info\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Include tokens array\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Include comments array\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Report errors and try to continue\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [parseLoc, parseRange, parseTokens, parseComment, parseTolerant] = args,\n            options = {\n                loc:      parseLoc,\n                range:    parseRange,\n                tokens:   parseTokens,\n                comment:  parseComment,\n                tolerant: parseTolerant\n            };\n        let result = {};\n\n        result = esprima.parseScript(input, options);\n        return JSON.stringify(result, null, 2);\n    }\n\n}\n\nexport default JavaScriptParser;\n"
  },
  {
    "path": "src/core/operations/Jq.mjs",
    "content": "/**\n * @author zhzy0077 [zhzy0077@hotmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport jq from \"jq-web\";\n\n/**\n * jq operation\n */\nclass Jq extends Operation {\n\n    /**\n     * Jq constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Jq\";\n        this.module = \"Jq\";\n        this.description = \"jq is a lightweight and flexible command-line JSON processor.\";\n        this.infoURL = \"https://github.com/jqlang/jq\";\n        this.inputType = \"JSON\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Query\",\n                type: \"string\",\n                value: \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [query] = args;\n        let result;\n\n        try {\n            result = jq.json(input, query);\n        } catch (err) {\n            throw new OperationError(`Invalid jq expression: ${err.message}`);\n        }\n\n        return JSON.stringify(result);\n    }\n\n}\n\nexport default Jq;\n"
  },
  {
    "path": "src/core/operations/Jsonata.mjs",
    "content": "/**\n * @author Jon K (jon@ajarsoftware.com)\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport jsonata from \"jsonata\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Jsonata Query operation\n */\nclass JsonataQuery extends Operation {\n    /**\n     * JsonataQuery constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Jsonata Query\";\n        this.module = \"Code\";\n        this.description =\n            \"Query and transform JSON data with a jsonata query.\";\n        this.infoURL = \"https://docs.jsonata.org/overview.html\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Query\",\n                type: \"text\",\n                value: \"string\",\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [query] = args;\n        let result, jsonObj;\n\n        try {\n            jsonObj = JSON.parse(input);\n        } catch (err) {\n            throw new OperationError(`Invalid input JSON: ${err.message}`);\n        }\n\n        try {\n            const expression = jsonata(query);\n            result = await expression.evaluate(jsonObj);\n        } catch (err) {\n            throw new OperationError(\n                `Invalid Jsonata Expression: ${err.message}`\n            );\n        }\n\n        return JSON.stringify(result === undefined ? \"\" : result);\n    }\n}\n\nexport default JsonataQuery;\n"
  },
  {
    "path": "src/core/operations/Jump.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { getLabelIndex } from \"../lib/FlowControl.mjs\";\n\n/**\n * Jump operation\n */\nclass Jump extends Operation {\n\n    /**\n     * Jump constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Jump\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"Jump forwards or backwards to the specified Label\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Label name\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Maximum jumps (if jumping backwards)\",\n                \"type\": \"number\",\n                \"value\": 10\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @param {number} state.numJumps - The number of jumps taken so far.\n     * @returns {Object} The updated state of the recipe.\n     */\n    run(state) {\n        const ings = state.opList[state.progress].ingValues;\n        const [label, maxJumps] = ings;\n        const jmpIndex = getLabelIndex(label, state);\n\n        if (state.numJumps >= maxJumps || jmpIndex === -1) {\n            state.numJumps = 0;\n            return state;\n        }\n\n        state.progress = jmpIndex;\n        state.numJumps++;\n        return state;\n    }\n\n}\n\nexport default Jump;\n"
  },
  {
    "path": "src/core/operations/Keccak.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport JSSHA3 from \"js-sha3\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Keccak operation\n */\nclass Keccak extends Operation {\n\n    /**\n     * Keccak constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Keccak\";\n        this.module = \"Crypto\";\n        this.description = \"The Keccak hash algorithm was designed by Guido Bertoni, Joan Daemen, Micha\\xebl Peeters, and Gilles Van Assche, building upon RadioGat\\xfan. It was selected as the winner of the SHA-3 design competition.<br><br>This version of the algorithm is Keccak[c=2d] and differs from the SHA-3 specification.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SHA-3\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Size\",\n                \"type\": \"option\",\n                \"value\": [\"512\", \"384\", \"256\", \"224\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const size = parseInt(args[0], 10);\n        let algo;\n\n        switch (size) {\n            case 224:\n                algo = JSSHA3.keccak224;\n                break;\n            case 384:\n                algo = JSSHA3.keccak384;\n                break;\n            case 256:\n                algo = JSSHA3.keccak256;\n                break;\n            case 512:\n                algo = JSSHA3.keccak512;\n                break;\n            default:\n                throw new OperationError(\"Invalid size\");\n        }\n\n        return algo(input);\n    }\n\n}\n\nexport default Keccak;\n"
  },
  {
    "path": "src/core/operations/LMHash.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {smbhash} from \"ntlm\";\n\n/**\n * LM Hash operation\n */\nclass LMHash extends Operation {\n\n    /**\n     * LMHash constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LM Hash\";\n        this.module = \"Crypto\";\n        this.description = \"An LM Hash, or LAN Manager Hash, is a deprecated way of storing passwords on old Microsoft operating systems. It is particularly weak and can be cracked in seconds on modern hardware using rainbow tables.\";\n        this.infoURL = \"https://wikipedia.org/wiki/LAN_Manager#Password_hashing_algorithm\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return smbhash.lmhash(input);\n    }\n\n}\n\nexport default LMHash;\n"
  },
  {
    "path": "src/core/operations/LS47Decrypt.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport * as LS47 from \"../lib/LS47.mjs\";\n\n/**\n * LS47 Decrypt operation\n */\nclass LS47Decrypt extends Operation {\n\n    /**\n     * LS47Decrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LS47 Decrypt\";\n        this.module = \"Crypto\";\n        this.description = \"This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.<br>The LS47 alphabet consists of following characters: <code>_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()</code><br>An LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption.\";\n        this.infoURL = \"https://github.com/exaexa/ls47\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Password\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Padding\",\n                type: \"number\",\n                value: 10\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        this.paddingSize = parseInt(args[1], 10);\n\n        LS47.initTiles();\n\n        const key = LS47.deriveKey(args[0]);\n        return LS47.decryptPad(key, input, this.paddingSize);\n    }\n\n}\n\nexport default LS47Decrypt;\n"
  },
  {
    "path": "src/core/operations/LS47Encrypt.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport * as LS47 from \"../lib/LS47.mjs\";\n\n/**\n * LS47 Encrypt operation\n */\nclass LS47Encrypt extends Operation {\n\n    /**\n     * LS47Encrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LS47 Encrypt\";\n        this.module = \"Crypto\";\n        this.description = \"This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.<br>The LS47 alphabet consists of following characters: <code>_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()</code><br>A LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption.\";\n        this.infoURL = \"https://github.com/exaexa/ls47\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Password\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Padding\",\n                type: \"number\",\n                value: 10\n            },\n            {\n                name: \"Signature\",\n                type: \"string\",\n                value: \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        this.paddingSize = parseInt(args[1], 10);\n\n        LS47.initTiles();\n\n        const key = LS47.deriveKey(args[0]);\n        return LS47.encryptPad(key, input, args[2], this.paddingSize);\n    }\n\n}\n\nexport default LS47Encrypt;\n"
  },
  {
    "path": "src/core/operations/LZ4Compress.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport lz4 from \"lz4js\";\n\n/**\n * LZ4 Compress operation\n */\nclass LZ4Compress extends Operation {\n\n    /**\n     * LZ4Compress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LZ4 Compress\";\n        this.module = \"Compression\";\n        this.description = \"LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/LZ4_(compression_algorithm)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const inBuf = new Uint8Array(input);\n        const compressed = lz4.compress(inBuf);\n        return compressed.buffer;\n    }\n\n}\n\nexport default LZ4Compress;\n"
  },
  {
    "path": "src/core/operations/LZ4Decompress.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport lz4 from \"lz4js\";\n\n/**\n * LZ4 Decompress operation\n */\nclass LZ4Decompress extends Operation {\n\n    /**\n     * LZ4Decompress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LZ4 Decompress\";\n        this.module = \"Compression\";\n        this.description = \"LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/LZ4_(compression_algorithm)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const inBuf = new Uint8Array(input);\n        const decompressed = lz4.decompress(inBuf);\n        return decompressed.buffer;\n    }\n\n}\n\nexport default LZ4Decompress;\n"
  },
  {
    "path": "src/core/operations/LZMACompress.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\nimport { compress } from \"@blu3r4y/lzma\";\nimport {isWorkerEnvironment} from \"../Utils.mjs\";\n\n/**\n * LZMA Compress operation\n */\nclass LZMACompress extends Operation {\n\n    /**\n     * LZMACompress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LZMA Compress\";\n        this.module = \"Compression\";\n        this.description = \"Compresses data using the Lempel\\u2013Ziv\\u2013Markov chain algorithm. Compression mode determines the speed and effectiveness of the compression: 1 is fastest and less effective, 9 is slowest and most effective\";\n        this.infoURL = \"https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Compression Mode\",\n                type: \"option\",\n                value: [\n                    \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"\n                ],\n                \"defaultIndex\": 6\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    async run(input, args) {\n        const mode = Number(args[0]);\n        return new Promise((resolve, reject) => {\n            compress(new Uint8Array(input), mode, (result, error) => {\n                if (error) {\n                    reject(new OperationError(`Failed to compress input: ${error.message}`));\n                }\n                // The compression returns as an Int8Array, but we can just get the unsigned data from the buffer\n                resolve(new Int8Array(result).buffer);\n            }, (percent) => {\n                if (isWorkerEnvironment()) self.sendStatusMessage(`Compressing input: ${(percent*100).toFixed(2)}%`);\n            });\n        });\n    }\n\n}\n\nexport default LZMACompress;\n"
  },
  {
    "path": "src/core/operations/LZMADecompress.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {decompress} from \"@blu3r4y/lzma\";\nimport Utils, {isWorkerEnvironment} from \"../Utils.mjs\";\n\n/**\n * LZMA Decompress operation\n */\nclass LZMADecompress extends Operation {\n\n    /**\n     * LZMADecompress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LZMA Decompress\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data using the Lempel-Ziv-Markov chain Algorithm.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    async run(input, args) {\n        return new Promise((resolve, reject) => {\n            decompress(new Uint8Array(input), (result, error) => {\n                if (error) {\n                    reject(new OperationError(`Failed to decompress input: ${error.message}`));\n                }\n                // The decompression returns either a String or an untyped unsigned int8 array, but we can just get the unsigned data from the buffer\n\n                if (typeof result == \"string\") {\n                    resolve(Utils.strToArrayBuffer(result));\n                } else {\n                    resolve(new Int8Array(result).buffer);\n                }\n            }, (percent) => {\n                if (isWorkerEnvironment()) self.sendStatusMessage(`Decompressing input: ${(percent*100).toFixed(2)}%`);\n            });\n        });\n    }\n\n}\n\nexport default LZMADecompress;\n"
  },
  {
    "path": "src/core/operations/LZNT1Decompress.mjs",
    "content": "/**\n * @author 0xThiebaut [thiebaut.dev]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {decompress} from \"../lib/LZNT1.mjs\";\n\n/**\n * LZNT1 Decompress operation\n */\nclass LZNT1Decompress extends Operation {\n\n    /**\n     * LZNT1 Decompress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LZNT1 Decompress\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data using the LZNT1 algorithm.<br><br>Similar to the Windows API <code>RtlDecompressBuffer</code>.\";\n        this.infoURL = \"https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/5655f4a3-6ba4-489b-959f-e1f407c52f15\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        return decompress(input);\n    }\n\n}\n\nexport default LZNT1Decompress;\n"
  },
  {
    "path": "src/core/operations/LZStringCompress.mjs",
    "content": "/**\n * @author crespyl [peter@crespyl.net]\n * @copyright Peter Jacobs 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\nimport {COMPRESSION_OUTPUT_FORMATS, COMPRESSION_FUNCTIONS} from \"../lib/LZString.mjs\";\n\n/**\n * LZString Compress operation\n */\nclass LZStringCompress extends Operation {\n\n    /**\n     * LZStringCompress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LZString Compress\";\n        this.module = \"Compression\";\n        this.description = \"Compress the input with lz-string.\";\n        this.infoURL = \"https://pieroxy.net/blog/pages/lz-string/index.html\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Compression Format\",\n                type: \"option\",\n                defaultIndex: 0,\n                value: COMPRESSION_OUTPUT_FORMATS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const compress = COMPRESSION_FUNCTIONS[args[0]];\n        if (compress) {\n            return compress(input);\n        } else {\n            throw new OperationError(\"Unable to find compression function\");\n        }\n    }\n\n}\n\nexport default LZStringCompress;\n"
  },
  {
    "path": "src/core/operations/LZStringDecompress.mjs",
    "content": "/**\n * @author crespyl [peter@crespyl.net]\n * @copyright Peter Jacobs 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\nimport {COMPRESSION_OUTPUT_FORMATS, DECOMPRESSION_FUNCTIONS} from \"../lib/LZString.mjs\";\n\n/**\n * LZString Decompress operation\n */\nclass LZStringDecompress extends Operation {\n\n    /**\n     * LZStringDecompress constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"LZString Decompress\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data that was compressed with lz-string.\";\n        this.infoURL = \"https://pieroxy.net/blog/pages/lz-string/index.html\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Compression Format\",\n                type: \"option\",\n                defaultIndex: 0,\n                value: COMPRESSION_OUTPUT_FORMATS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const decompress = DECOMPRESSION_FUNCTIONS[args[0]];\n        if (decompress) {\n            return decompress(input);\n        } else {\n            throw new OperationError(\"Unable to find decompression function\");\n        }\n    }\n\n\n}\n\nexport default LZStringDecompress;\n"
  },
  {
    "path": "src/core/operations/Label.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Label operation. For use with Jump and Conditional Jump.\n */\nclass Label extends Operation {\n\n    /**\n     * Label constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Label\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"Provides a location for conditional and fixed jumps to redirect execution to.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Name\",\n                \"type\": \"shortString\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @returns {Object} The updated state of the recipe.\n     */\n    run(state) {\n        return state;\n    }\n\n}\n\nexport default Label;\n"
  },
  {
    "path": "src/core/operations/LevenshteinDistance.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Levenshtein Distance operation\n */\nclass LevenshteinDistance extends Operation {\n\n    /**\n     * LevenshteinDistance constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Levenshtein Distance\";\n        this.module = \"Default\";\n        this.description = \"Levenshtein Distance (also known as Edit Distance) is a string metric to measure a difference between two strings that counts operations (insertions, deletions, and substitutions) on single character that are required to change one string to another.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Levenshtein_distance\";\n        this.inputType = \"string\";\n        this.outputType = \"number\";\n        this.args = [\n            {\n                name: \"Sample delimiter\",\n                type: \"binaryString\",\n                value: \"\\\\n\"\n            },\n            {\n                name: \"Insertion cost\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Deletion cost\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Substitution cost\",\n                type: \"number\",\n                value: 1\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        const [delim, insCost, delCost, subCost] = args;\n        const samples = input.split(delim);\n        if (samples.length !== 2) {\n            throw new OperationError(\"Incorrect number of samples. Check your input and/or delimiter.\");\n        }\n        if (insCost < 0 || delCost < 0 || subCost < 0) {\n            throw new OperationError(\"Negative costs are not allowed.\");\n        }\n        const src = samples[0], dest = samples[1];\n        let currentCost = new Array(src.length + 1);\n        let nextCost = new Array(src.length + 1);\n        for (let i = 0; i < currentCost.length; i++) {\n            currentCost[i] = delCost * i;\n        }\n        for (let i = 0; i < dest.length; i++) {\n            const destc = dest.charAt(i);\n            nextCost[0] = currentCost[0] + insCost;\n            for (let j = 0; j < src.length; j++) {\n                let candidate;\n                // insertion\n                let optCost = currentCost[j + 1] + insCost;\n                // deletion\n                candidate = nextCost[j] + delCost;\n                if (candidate < optCost) optCost = candidate;\n                // substitution or matched character\n                candidate = currentCost[j];\n                if (src.charAt(j) !== destc) candidate += subCost;\n                if (candidate < optCost) optCost = candidate;\n                // store calculated cost\n                nextCost[j + 1] = optCost;\n            }\n            const tempCost = nextCost;\n            nextCost = currentCost;\n            currentCost = tempCost;\n        }\n\n        return currentCost[currentCost.length - 1];\n    }\n\n}\n\nexport default LevenshteinDistance;\n"
  },
  {
    "path": "src/core/operations/Lorenz.mjs",
    "content": "/**\n * Emulation of the Lorenz SZ40/42a/42b cipher attachment.\n *\n * Tested against the Colossus Rebuild at Bletchley Park's TNMOC\n * using a variety of inputs and settings to confirm correctness.\n *\n * @author VirtualColossus [martin@virtualcolossus.co.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Lorenz operation\n */\nclass Lorenz extends Operation {\n\n    /**\n     * Lorenz constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Lorenz\";\n        this.module = \"Bletchley\";\n        this.description = \"The Lorenz SZ40/42 cipher attachment was a WW2 German rotor cipher machine with twelve rotors which attached in-line between remote teleprinters.<br><br>It used the Vernam cipher with two groups of five rotors (named the psi(ψ) wheels and chi(χ) wheels at Bletchley Park) to create two pseudorandom streams of five bits, encoded in ITA2, which were XOR added to the plaintext. Two other rotors, dubbed the mu(μ) or motor wheels, could hold up the stepping of the psi wheels meaning they stepped intermittently.<br><br>Each rotor has a different number of cams/lugs around their circumference which could be set active or inactive changing the key stream.<br><br>Three models of the Lorenz are emulated, SZ40, SZ42a and SZ42b and three example wheel patterns (the lug settings) are included (KH, ZMUG & BREAM) with the option to set a custom set using the letter 'x' for active or '.' for an inactive lug.<br><br>The input can either be plaintext or ITA2 when sending and ITA2 when receiving.<br><br>To learn more, Virtual Lorenz, an online, browser based simulation of the Lorenz SZ40/42 is available at <a href='https://lorenz.virtualcolossus.co.uk' target='_blank'>lorenz.virtualcolossus.co.uk</a>.<br><br>A more detailed description of this operation can be found <a href='https://github.com/gchq/CyberChef/wiki/Lorenz-SZ' target='_blank'>here</a>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Lorenz_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Model\",\n                type: \"option\",\n                value: [\"SZ40\", \"SZ42a\", \"SZ42b\"]\n            },\n            {\n                name: \"Wheel Pattern\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"KH Pattern\",\n                        off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n                    },\n                    {\n                        name: \"ZMUG Pattern\",\n                        off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n                    },\n                    {\n                        name: \"BREAM Pattern\",\n                        off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n                    },\n                    {\n                        name: \"No Pattern\",\n                        off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n                    },\n                    {\n                        name: \"Custom\",\n                        on: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n                    }\n                ]\n            },\n            {\n                name: \"KT-Schalter\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Mode\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"Send\",\n                        on: [4],\n                        off: [5]\n                    },\n                    {\n                        name: \"Receive\",\n                        off: [4],\n                        on: [5]\n                    }\n                ]\n            },\n            {\n                name: \"Input Type\",\n                type: \"option\",\n                value: [\"Plaintext\", \"ITA2\"]\n            },\n            {\n                name: \"Output Type\",\n                type: \"option\",\n                value: [\"Plaintext\", \"ITA2\"]\n            },\n            {\n                name: \"ITA2 Format\",\n                type: \"option\",\n                value: [\"5/8/9\", \"+/-/.\"]\n            },\n            {\n                name: \"Ψ1 start (1-43)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Ψ2 start (1-47)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Ψ3 start (1-51)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Ψ4 start (1-53)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Ψ5 start (1-59)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Μ37 start (1-37)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Μ61 start (1-61)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Χ1 start (1-41)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Χ2 start (1-31)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Χ3 start (1-29)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Χ4 start (1-26)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Χ5 start (1-23)\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Ψ1 lugs (43)\",\n                type: \"string\",\n                value: \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\"\n            },\n            {\n                name: \"Ψ2 lugs (47)\",\n                type: \"string\",\n                value: \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\"\n            },\n            {\n                name: \"Ψ3 lugs (51)\",\n                type: \"string\",\n                value: \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\"\n            },\n            {\n                name: \"Ψ4 lugs (53)\",\n                type: \"string\",\n                value: \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\"\n            },\n            {\n                name: \"Ψ5 lugs (59)\",\n                type: \"string\",\n                value: \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\"\n            },\n            {\n                name: \"Μ37 lugs (37)\",\n                type: \"string\",\n                value: \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\"\n            },\n            {\n                name: \"Μ61 lugs (61)\",\n                type: \"string\",\n                value: \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\"\n            },\n            {\n                name: \"Χ1 lugs (41)\",\n                type: \"string\",\n                value: \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\"\n            },\n            {\n                name: \"Χ2 lugs (31)\",\n                type: \"string\",\n                value: \"x..xxx...x.xxxx..xx..x..xx.xx..\"\n            },\n            {\n                name: \"Χ3 lugs (29)\",\n                type: \"string\",\n                value: \"..xx..x.xxx...xx...xx..xx.xx.\"\n            },\n            {\n                name: \"Χ4 lugs (26)\",\n                type: \"string\",\n                value: \"xx..x..xxxx..xx.xxx....x..\"\n            },\n            {\n                name: \"Χ5 lugs (23)\",\n                type: \"string\",\n                value: \"xx..xx....xxxx.x..x.x..\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n\n        const model = args[0],\n            pattern = args[1],\n            kt = args[2],\n            mode = args[3],\n            intype = args[4],\n            outtype = args[5],\n            format = args[6],\n            lugs1 = args[19],\n            lugs2 = args[20],\n            lugs3 = args[21],\n            lugs4 = args[22],\n            lugs5 = args[23],\n            lugm37 = args[24],\n            lugm61 = args[25],\n            lugx1 = args[26],\n            lugx2 = args[27],\n            lugx3 = args[28],\n            lugx4 = args[29],\n            lugx5 = args[30];\n\n        let s1 = args[7],\n            s2 = args[8],\n            s3 = args[9],\n            s4 = args[10],\n            s5 = args[11],\n            m37 = args[12],\n            m61 = args[13],\n            x1 = args[14],\n            x2 = args[15],\n            x3 = args[16],\n            x4 = args[17],\n            x5 = args[18];\n\n        this.reverseTable();\n\n        if (s1<1 || s1>43) throw new OperationError(\"Ψ1 start must be between 1 and 43\");\n        if (s2<1 || s2>47) throw new OperationError(\"Ψ2 start must be between 1 and 47\");\n        if (s3<1 || s3>51) throw new OperationError(\"Ψ3 start must be between 1 and 51\");\n        if (s4<1 || s4>53) throw new OperationError(\"Ψ4 start must be between 1 and 53\");\n        if (s5<1 || s5>59) throw new OperationError(\"Ψ5 start must be between 1 and 59\");\n        if (m37<1 || m37>37) throw new OperationError(\"Μ37 start must be between 1 and 37\");\n        if (m61<1 || m61>61) throw new OperationError(\"Μ61 start must be between 1 and 61\");\n        if (x1<1 || x1>41) throw new OperationError(\"Χ1 start must be between 1 and 41\");\n        if (x2<1 || x2>31) throw new OperationError(\"Χ2 start must be between 1 and 31\");\n        if (x3<1 || x3>29) throw new OperationError(\"Χ3 start must be between 1 and 29\");\n        if (x4<1 || x4>26) throw new OperationError(\"Χ4 start must be between 1 and 26\");\n        if (x5<1 || x5>23) throw new OperationError(\"Χ5 start must be between 1 and 23\");\n\n        // Initialise chosen wheel pattern\n        let chosenSetting = \"\";\n        if (pattern === \"Custom\") {\n            const re = new RegExp(\"^[.xX]*$\");\n            if (lugs1.length !== 43 || !re.test(lugs1)) throw new OperationError(\"Ψ1 custom lugs must be 43 long and can only include . or x \");\n            if (lugs2.length !== 47 || !re.test(lugs2)) throw new OperationError(\"Ψ2 custom lugs must be 47 long and can only include . or x\");\n            if (lugs3.length !== 51 || !re.test(lugs3)) throw new OperationError(\"Ψ3 custom lugs must be 51 long and can only include . or x\");\n            if (lugs4.length !== 53 || !re.test(lugs4)) throw new OperationError(\"Ψ4 custom lugs must be 53 long and can only include . or x\");\n            if (lugs5.length !== 59 || !re.test(lugs5)) throw new OperationError(\"Ψ5 custom lugs must be 59 long and can only include . or x\");\n            if (lugm37.length !== 37 || !re.test(lugm37)) throw new OperationError(\"M37 custom lugs must be 37 long and can only include . or x\");\n            if (lugm61.length !== 61 || !re.test(lugm61)) throw new OperationError(\"M61 custom lugs must be 61 long and can only include . or x\");\n            if (lugx1.length !== 41 || !re.test(lugx1)) throw new OperationError(\"Χ1 custom lugs must be 41 long and can only include . or x\");\n            if (lugx2.length !== 31 || !re.test(lugx2)) throw new OperationError(\"Χ2 custom lugs must be 31 long and can only include . or x\");\n            if (lugx3.length !== 29 || !re.test(lugx3)) throw new OperationError(\"Χ3 custom lugs must be 29 long and can only include . or x\");\n            if (lugx4.length !== 26 || !re.test(lugx4)) throw new OperationError(\"Χ4 custom lugs must be 26 long and can only include . or x\");\n            if (lugx5.length !== 23 || !re.test(lugx5)) throw new OperationError(\"Χ5 custom lugs must be 23 long and can only include . or x\");\n            chosenSetting = INIT_PATTERNS[\"No Pattern\"];\n            chosenSetting.S[1] = this.readLugs(lugs1);\n            chosenSetting.S[2] = this.readLugs(lugs2);\n            chosenSetting.S[3] = this.readLugs(lugs3);\n            chosenSetting.S[4] = this.readLugs(lugs4);\n            chosenSetting.S[5] = this.readLugs(lugs5);\n            chosenSetting.M[1] = this.readLugs(lugm61);\n            chosenSetting.M[2] = this.readLugs(lugm37);\n            chosenSetting.X[1] = this.readLugs(lugx1);\n            chosenSetting.X[2] = this.readLugs(lugx2);\n            chosenSetting.X[3] = this.readLugs(lugx3);\n            chosenSetting.X[4] = this.readLugs(lugx4);\n            chosenSetting.X[5] = this.readLugs(lugx5);\n        } else {\n            chosenSetting = INIT_PATTERNS[pattern];\n        }\n        const chiSettings = chosenSetting.X; // Pin settings for Chi links (X)\n        const psiSettings = chosenSetting.S; // Pin settings for Psi links (S)\n        const muSettings = chosenSetting.M; // Pin settings for Motor links (M)\n\n        // Convert input text to ITA2 (including figure/letter shifts)\n        const ita2Input = this.convertToITA2(input, intype, mode);\n\n        let thisPsi = [];\n        let thisChi = [];\n        let m61lug = muSettings[1][m61-1];\n        let m37lug = muSettings[2][m37-1];\n        const p5 = [0, 0, 0];\n\n        const self = this;\n        const letters = Array.prototype.map.call(ita2Input, function(character) {\n            const letter = character.toUpperCase();\n\n            // Store lugs used in limitations, need these later\n            let x2bptr = x2+1;\n            if (x2bptr===32) x2bptr=1;\n            let s1bptr = s1+1;\n            if (s1bptr===44) s1bptr=1;\n\n            thisChi = [\n                chiSettings[1][x1-1],\n                chiSettings[2][x2-1],\n                chiSettings[3][x3-1],\n                chiSettings[4][x4-1],\n                chiSettings[5][x5-1]\n            ];\n\n            thisPsi = [\n                psiSettings[1][s1-1],\n                psiSettings[2][s2-1],\n                psiSettings[3][s3-1],\n                psiSettings[4][s4-1],\n                psiSettings[5][s5-1]\n            ];\n\n            if (typeof ITA2_TABLE[letter] == \"undefined\") {\n                return \"\";\n            }\n\n            // The encipher calculation\n\n            // We calculate Bitwise XOR for each of the 5 bits across our input ( K XOR Psi XOR Chi )\n            const xorSum = [];\n            for (let i=0;i<=4;i++) {\n                xorSum[i] = ITA2_TABLE[letter][i] ^ thisPsi[i] ^ thisChi[i];\n            }\n            const resultStr = xorSum.join(\"\");\n\n            // Wheel movement\n\n            // Chi wheels always move one back after each letter\n            if (--x1 < 1) x1 = 41;\n            if (--x2 < 1) x2 = 31;\n            if (--x3 < 1) x3 = 29;\n            if (--x4 < 1) x4 = 26;\n            if (--x5 < 1) x5 = 23;\n\n            // Motor wheel (61 pin) also moves one each letter\n            if (--m61 < 1) m61 = 61;\n\n            // If M61 is set, we also move M37\n            if (m61lug === 1) {\n                if (--m37 < 1) m37 = 37;\n            }\n\n            // Psi wheels only move sometimes, dependent on M37 current setting and limitations\n\n            const basicmotor = m37lug;\n            let totalmotor;\n            let lim = 0;\n\n            p5[2] = p5[1];\n            p5[1] = p5[0];\n            if (mode===\"Send\") {\n                p5[0] = parseInt(ITA2_TABLE[letter][4], 10);\n            } else {\n                p5[0] = parseInt(xorSum[4], 10);\n            }\n\n            // Limitations here\n            if (model===\"SZ42a\") {\n                // Chi 2 one back lim - The active character of Chi 2 (2nd Chi wheel) in the previous position\n                lim = parseInt(chiSettings[2][x2bptr-1], 10);\n                if (kt) {\n                    // p5 back 2\n                    if (lim===p5[2]) {\n                        lim = 0;\n                    } else {\n                        lim=1;\n                    }\n                }\n\n                // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move]\n                if (basicmotor===0 && lim===1) {\n                    totalmotor = 0;\n                } else {\n                    totalmotor = 1;\n                }\n\n            } else if (model===\"SZ42b\") {\n                // Chi 2 one back + Psi 1 one back.\n                const x2b1lug = parseInt(chiSettings[2][x2bptr-1], 10);\n                const s1b1lug = parseInt(psiSettings[1][s1bptr-1], 10);\n                lim = 1;\n                if (x2b1lug===s1b1lug) lim=0;\n                if (kt) {\n                     // p5 back 2\n                    if (lim===p5[2]) {\n                        lim=0;\n                    } else {\n                        lim=1;\n                    }\n                }\n                // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move]\n                if (basicmotor===0 && lim===1) {\n                    totalmotor = 0;\n                } else {\n                    totalmotor = 1;\n                }\n\n            } else if (model===\"SZ40\") {\n                // SZ40 - just move based on the M37 motor wheel\n                totalmotor = basicmotor;\n            } else {\n                throw new OperationError(\"Lorenz model type not recognised\");\n            }\n\n            // Move the Psi wheels when current totalmotor active\n            if (totalmotor === 1) {\n                if (--s1 < 1) s1 = 43;\n                if (--s2 < 1) s2 = 47;\n                if (--s3 < 1) s3 = 51;\n                if (--s4 < 1) s4 = 53;\n                if (--s5 < 1) s5 = 59;\n            }\n\n            m61lug = muSettings[1][m61-1];\n            m37lug = muSettings[2][m37-1];\n\n            let rtnstr = self.REVERSE_ITA2_TABLE[resultStr];\n            if (format===\"5/8/9\") {\n                if (rtnstr===\"+\") rtnstr=\"5\"; // + or 5 used to represent figure shift\n                if (rtnstr===\"-\") rtnstr=\"8\"; // - or 8 used to represent letter shift\n                if (rtnstr===\".\") rtnstr=\"9\"; // . or 9 used to represent space\n            }\n            return rtnstr;\n        });\n\n        const ita2output = letters.join(\"\");\n\n        return this.convertFromITA2(ita2output, outtype, mode);\n\n    }\n\n    /**\n     * Reverses the ITA2 Code lookup table\n     */\n    reverseTable() {\n        this.REVERSE_ITA2_TABLE = {};\n        this.REVERSE_FIGSHIFT_TABLE = {};\n\n        for (const letter in ITA2_TABLE) {\n            const code = ITA2_TABLE[letter];\n            this.REVERSE_ITA2_TABLE[code] = letter;\n        }\n        for (const letter in figShiftArr) {\n            const ltr = figShiftArr[letter];\n            this.REVERSE_FIGSHIFT_TABLE[ltr] = letter;\n        }\n    }\n\n    /**\n     * Read lugs settings - convert to 0|1\n     */\n    readLugs(lugstr) {\n        const arr = Array.prototype.map.call(lugstr, function(lug) {\n            if (lug===\".\") {\n                return 0;\n            } else {\n                return 1;\n            }\n        });\n        return arr;\n    }\n\n    /**\n     * Convert input plaintext to ITA2\n     */\n    convertToITA2(input, intype, mode) {\n        let result = \"\";\n        let figShifted = false;\n\n        for (const character of input) {\n            const letter = character.toUpperCase();\n\n            // Convert input text to ITA2 (including figure/letter shifts)\n            if (intype === \"ITA2\" || mode === \"Receive\") {\n                if (validITA2.indexOf(letter) === -1) {\n                    let errltr = letter;\n                    if (errltr===\"\\n\") errltr = \"Carriage Return\";\n                    if (errltr===\" \") errltr = \"Space\";\n                    throw new OperationError(\"Invalid ITA2 character : \"+errltr);\n                }\n                result += letter;\n            } else {\n                if (validChars.indexOf(letter) === -1) throw new OperationError(\"Invalid Plaintext character : \"+letter);\n\n                if (!figShifted && figShiftedChars.indexOf(letter) !== -1) {\n                    // in letters mode and next char needs to be figure shifted\n                    figShifted = true;\n                    result += \"55\" + figShiftArr[letter];\n                } else if (figShifted) {\n                    // in figures mode and next char needs to be letter shifted\n                    if (letter===\"\\n\") {\n                        result += \"34\";\n                    } else if (letter===\"\\r\") {\n                        result += \"4\";\n                    } else if (figShiftedChars.indexOf(letter) === -1) {\n                        figShifted = false;\n                        result += \"88\" + letter;\n                    } else {\n                        result += figShiftArr[letter];\n                    }\n\n                } else {\n                    if (letter===\"\\n\") {\n                        result += \"34\";\n                    } else if (letter===\"\\r\") {\n                        result += \"4\";\n                    } else {\n                        result += letter;\n                    }\n                }\n\n            }\n\n        }\n\n        return result;\n    }\n\n    /**\n     * Convert final result ITA2 to plaintext\n     */\n    convertFromITA2(input, outtype, mode) {\n        let result = \"\";\n        let figShifted = false;\n        for (const letter of input) {\n            if (mode === \"Receive\") {\n\n                // Convert output ITA2 to plaintext (including figure/letter shifts)\n                if (outtype === \"Plaintext\") {\n\n                    if (letter === \"5\" || letter === \"+\") {\n                        figShifted = true;\n                    } else if (letter === \"8\" || letter === \"-\") {\n                        figShifted = false;\n                    } else if (letter === \"9\") {\n                        result += \" \";\n                    } else if (letter === \"3\") {\n                        result += \"\\n\";\n                    } else if (letter === \"4\") {\n                        result += \"\";\n                    } else if (letter === \"/\") {\n                        result += \"/\";\n                    } else {\n\n                        if (figShifted) {\n                            result += this.REVERSE_FIGSHIFT_TABLE[letter];\n                        } else {\n                            result += letter;\n                        }\n\n                    }\n\n                } else {\n                    result += letter;\n                }\n\n            } else {\n                result += letter;\n            }\n        }\n\n        return result;\n\n    }\n\n}\n\nconst ITA2_TABLE = {\n    \"A\": \"11000\",\n    \"B\": \"10011\",\n    \"C\": \"01110\",\n    \"D\": \"10010\",\n    \"E\": \"10000\",\n    \"F\": \"10110\",\n    \"G\": \"01011\",\n    \"H\": \"00101\",\n    \"I\": \"01100\",\n    \"J\": \"11010\",\n    \"K\": \"11110\",\n    \"L\": \"01001\",\n    \"M\": \"00111\",\n    \"N\": \"00110\",\n    \"O\": \"00011\",\n    \"P\": \"01101\",\n    \"Q\": \"11101\",\n    \"R\": \"01010\",\n    \"S\": \"10100\",\n    \"T\": \"00001\",\n    \"U\": \"11100\",\n    \"V\": \"01111\",\n    \"W\": \"11001\",\n    \"X\": \"10111\",\n    \"Y\": \"10101\",\n    \"Z\": \"10001\",\n    \"3\": \"00010\",\n    \"4\": \"01000\",\n    \"9\": \"00100\",\n    \"/\": \"00000\",\n    \" \": \"00100\",\n    \".\": \"00100\",\n    \"8\": \"11111\",\n    \"5\": \"11011\",\n    \"-\": \"11111\",\n    \"+\": \"11011\"\n};\n\nconst validChars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-'()/:=?,. \\n\\r\";\nconst validITA2 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ34589+-./\";\nconst figShiftedChars = \"1234567890+-'()/:=?,.\";\n\nconst figShiftArr = {\n    \"1\": \"Q\",\n    \"2\": \"W\",\n    \"3\": \"E\",\n    \"4\": \"R\",\n    \"5\": \"T\",\n    \"6\": \"Y\",\n    \"7\": \"U\",\n    \"8\": \"I\",\n    \"9\": \"O\",\n    \"0\": \"P\",\n    \" \": \"9\",\n    \"-\": \"A\",\n    \"?\": \"B\",\n    \":\": \"C\",\n    \"#\": \"D\",\n    \"%\": \"F\",\n    \"@\": \"G\",\n    \"£\": \"H\",\n    \"\": \"J\",\n    \"(\": \"K\",\n    \")\": \"L\",\n    \".\": \"M\",\n    \",\": \"N\",\n    \"'\": \"S\",\n    \"=\": \"V\",\n    \"/\": \"X\",\n    \"+\": \"Z\",\n    \"\\n\": \"3\",\n    \"\\r\": \"4\"\n};\n\nconst INIT_PATTERNS = {\n    \"No Pattern\": {\n        \"X\": {\n            1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n        },\n        \"S\": {\n            1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n        },\n        \"M\": {\n            1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n        }\n\n    },\n    \"KH Pattern\": {\n        \"X\": {\n            1: [0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0],\n            2: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0],\n            3: [0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0],\n            4: [1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0],\n            5: [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0]\n        },\n        \"S\": {\n            1: [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1],\n            2: [0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],\n            3: [0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1],\n            4: [0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],\n            5: [1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0]\n        },\n        \"M\":  {\n            1: [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0],\n            2: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0]\n        }\n    },\n    \"ZMUG Pattern\":  {\n        \"X\": {\n            1: [0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0],\n            2: [1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0],\n            3: [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0],\n            4: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1],\n            5: [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1]\n        },\n        \"S\": {\n            1: [1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0],\n            2: [0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1],\n            3: [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1],\n            4: [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1],\n            5: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0]\n        },\n        \"M\": {\n            1: [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1],\n            2: [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1]\n        }\n    },\n    \"BREAM Pattern\": {\n        \"X\": {\n            1: [0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0],\n            2: [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1],\n            3: [1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0],\n            4: [1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0],\n            5: [0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0]\n        },\n        \"S\": {\n            1: [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0],\n            2: [1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0],\n            3: [1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],\n            4: [0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1],\n            5: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]\n        },\n        \"M\": {\n            1: [1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1],\n            2: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1]\n        }\n    }\n};\n\nexport default Lorenz;\n"
  },
  {
    "path": "src/core/operations/LuhnChecksum.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @author k3ach [k3ach@proton.me]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Luhn Checksum operation\n */\nclass LuhnChecksum extends Operation {\n\n    /**\n     * LuhnChecksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Luhn Checksum\";\n        this.module = \"Default\";\n        this.description = \"The Luhn mod N algorithm using the english alphabet. The Luhn mod N algorithm is an extension to the Luhn algorithm (also known as mod 10 algorithm) that allows it to work with sequences of values in any even-numbered base. This can be useful when a check digit is required to validate an identification string composed of letters, a combination of letters and digits or any arbitrary set of N characters where N is divisible by 2.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Radix\",\n                \"type\": \"number\",\n                \"value\": 10\n            }\n        ];\n    }\n\n    /**\n     * Generates the Luhn checksum from the input.\n     *\n     * @param {string} inputStr\n     * @returns {number}\n     */\n    checksum(inputStr, radix = 10) {\n        let even = false;\n        return inputStr.split(\"\").reverse().reduce((acc, elem) => {\n            // Convert element to an integer based on the provided radix.\n            let temp = parseInt(elem, radix);\n\n            // If element is not a valid number in the given radix.\n            if (isNaN(temp)) {\n                throw new Error(\"Character: \" + elem + \" is not valid in radix \" + radix + \".\");\n            }\n\n            // If element is in an even position\n            if (even) {\n                // Double the element and sum the quotient and remainder.\n                temp = 2 * temp;\n                temp = Math.floor(temp / radix) + (temp % radix);\n            }\n\n            even = !even;\n            return acc + temp;\n        }, 0) % radix; // Use radix as the modulus base\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input) return \"\";\n\n        const radix = args[0];\n\n        if (radix < 2 || radix > 36) {\n            throw new OperationError(\"Error: Radix argument must be between 2 and 36\");\n        }\n\n        if (radix % 2 !== 0) {\n            throw new OperationError(\"Error: Radix argument must be divisible by 2\");\n        }\n\n        const checkSum = this.checksum(input, radix).toString(radix);\n        let checkDigit = this.checksum(input + \"0\", radix);\n        checkDigit = checkDigit === 0 ? 0 : (radix - checkDigit);\n        checkDigit = checkDigit.toString(radix);\n\n        return `Checksum: ${checkSum}\nCheckdigit: ${checkDigit}\nLuhn Validated String: ${input + \"\" + checkDigit}`;\n    }\n\n}\n\nexport default LuhnChecksum;\n"
  },
  {
    "path": "src/core/operations/MD2.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * MD2 operation\n */\nclass MD2 extends Operation {\n\n    /**\n     * MD2 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"MD2\";\n        this.module = \"Crypto\";\n        this.description = \"The MD2 (Message-Digest 2) algorithm is a cryptographic hash function developed by Ronald Rivest in 1989. The algorithm is optimized for 8-bit computers.<br><br>Although MD2 is no longer considered secure, even as of 2014, it remains in use in public key infrastructures as part of certificates generated with MD2 and RSA. The message digest algorithm consists, by default, of 18 rounds.\";\n        this.infoURL = \"https://wikipedia.org/wiki/MD2_(cryptography)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 18,\n                min: 0\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return runHash(\"md2\", input, {rounds: args[0]});\n    }\n\n}\n\nexport default MD2;\n"
  },
  {
    "path": "src/core/operations/MD4.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * MD4 operation\n */\nclass MD4 extends Operation {\n\n    /**\n     * MD4 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"MD4\";\n        this.module = \"Crypto\";\n        this.description = \"The MD4 (Message-Digest 4) algorithm is a cryptographic hash function developed by Ronald Rivest in 1990. The digest length is 128 bits. The algorithm has influenced later designs, such as the MD5, SHA-1 and RIPEMD algorithms.<br><br>The security of MD4 has been severely compromised.\";\n        this.infoURL = \"https://wikipedia.org/wiki/MD4\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return runHash(\"md4\", input);\n    }\n\n}\n\nexport default MD4;\n"
  },
  {
    "path": "src/core/operations/MD5.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * MD5 operation\n */\nclass MD5 extends Operation {\n\n    /**\n     * MD5 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"MD5\";\n        this.module = \"Crypto\";\n        this.description = \"MD5 (Message-Digest 5) is a widely used hash function. It has been used in a variety of security applications and is also commonly used to check the integrity of files.<br><br>However, MD5 is not collision resistant and it isn't suitable for applications like SSL/TLS certificates or digital signatures that rely on this property.\";\n        this.infoURL = \"https://wikipedia.org/wiki/MD5\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return runHash(\"md5\", input);\n    }\n\n}\n\nexport default MD5;\n"
  },
  {
    "path": "src/core/operations/MD6.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport NodeMD6 from \"node-md6\";\n\n/**\n * MD6 operation\n */\nclass MD6 extends Operation {\n\n    /**\n     * MD6 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"MD6\";\n        this.module = \"Crypto\";\n        this.description = \"The MD6 (Message-Digest 6) algorithm is a cryptographic hash function. It uses a Merkle tree-like structure to allow for immense parallel computation of hashes for very long inputs.\";\n        this.infoURL = \"https://wikipedia.org/wiki/MD6\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Size\",\n                \"type\": \"number\",\n                \"value\": 256\n            },\n            {\n                \"name\": \"Levels\",\n                \"type\": \"number\",\n                \"value\": 64\n            },\n            {\n                \"name\": \"Key\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [size, levels, key] = args;\n\n        if (size < 0 || size > 512)\n            throw new OperationError(\"Size must be between 0 and 512\");\n        if (levels < 0)\n            throw new OperationError(\"Levels must be greater than 0\");\n\n        return NodeMD6.getHashOfText(input, size, key, levels);\n    }\n\n}\n\nexport default MD6;\n"
  },
  {
    "path": "src/core/operations/MIMEDecoding.mjs",
    "content": "/**\n * @author mshwed [m@ttshwed.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { fromHex } from \"../lib/Hex.mjs\";\nimport { fromBase64 } from \"../lib/Base64.mjs\";\nimport cptable from \"codepage\";\n\n/**\n * MIME Decoding operation\n */\nclass MIMEDecoding extends Operation {\n\n    /**\n     * MIMEDecoding constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"MIME Decoding\";\n        this.module = \"Default\";\n        this.description = \"Enables the decoding of MIME message header extensions for non-ASCII text\";\n        this.infoURL = \"https://tools.ietf.org/html/rfc2047\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const mimeEncodedText = Utils.byteArrayToUtf8(input);\n        const encodedHeaders = mimeEncodedText.replace(/\\r\\n/g, \"\\n\");\n\n        const decodedHeader = this.decodeHeaders(encodedHeaders);\n\n        return decodedHeader;\n    }\n\n    /**\n     * Decode MIME header strings\n     *\n     * @param headerString\n     */\n    decodeHeaders(headerString) {\n        // No encoded words detected\n        let i = headerString.indexOf(\"=?\");\n        if (i === -1) return headerString;\n\n        let decodedHeaders = headerString.slice(0, i);\n        let header = headerString.slice(i);\n\n        let isBetweenWords = false;\n        let start, cur, charset, encoding, j, end, text;\n        while (header.length > -1) {\n            start = header.indexOf(\"=?\");\n            if (start === -1) break;\n            cur = start + \"=?\".length;\n\n            i = header.slice(cur).indexOf(\"?\");\n            if (i === -1) break;\n\n            charset = header.slice(cur, cur + i);\n            cur += i + \"?\".length;\n\n            if (header.length < cur + \"Q??=\".length) break;\n\n            encoding = header[cur];\n            cur += 1;\n\n            if (header[cur] !== \"?\") break;\n\n            cur += 1;\n\n            j = header.slice(cur).indexOf(\"?=\");\n            if (j === -1) break;\n\n            text = header.slice(cur, cur + j);\n            end = cur + j + \"?=\".length;\n\n            if (encoding.toLowerCase() === \"b\") {\n                text = fromBase64(text);\n            } else if (encoding.toLowerCase() === \"q\") {\n                text = this.parseQEncodedWord(text);\n            } else {\n                isBetweenWords = false;\n                decodedHeaders += header.slice(0, start + 2);\n                header = header.slice(start + 2);\n            }\n\n            if (start > 0 && (!isBetweenWords || header.slice(0, start).search(/\\S/g) > -1)) {\n                decodedHeaders += header.slice(0, start);\n            }\n\n            decodedHeaders += this.convertFromCharset(charset, text);\n\n            header = header.slice(end);\n            isBetweenWords = true;\n        }\n\n        if (header.length > 0) {\n            decodedHeaders += header;\n        }\n\n        return decodedHeaders;\n    }\n\n    /**\n     * Converts decoded text for supported charsets.\n     * Supports UTF-8, US-ASCII, ISO-8859-*\n     *\n     * @param encodedWord\n     */\n    convertFromCharset(charset, encodedText) {\n        charset = charset.toLowerCase();\n        const parsedCharset = charset.split(\"-\");\n\n        if (parsedCharset.length === 2 && parsedCharset[0] === \"utf\" && charset === \"utf-8\") {\n            return cptable.utils.decode(65001, encodedText);\n        } else if (parsedCharset.length === 2 && charset === \"us-ascii\") {\n            return cptable.utils.decode(20127, encodedText);\n        } else if (parsedCharset.length === 3 && parsedCharset[0] === \"iso\" && parsedCharset[1] === \"8859\") {\n            const isoCharset = parseInt(parsedCharset[2], 10);\n            if (isoCharset >= 1 && isoCharset <= 16) {\n                return cptable.utils.decode(28590 + isoCharset, encodedText);\n            }\n        }\n\n        throw new OperationError(\"Unhandled Charset\");\n    }\n\n    /**\n     * Parses a Q encoded word\n     *\n     * @param encodedWord\n     */\n    parseQEncodedWord(encodedWord) {\n        let decodedWord = \"\";\n        for (let i = 0; i < encodedWord.length; i++) {\n            if (encodedWord[i] === \"_\") {\n                decodedWord += \" \";\n            // Parse hex encoding\n            } else if (encodedWord[i] === \"=\") {\n                if ((i + 2) >= encodedWord.length) throw new OperationError(\"Incorrectly Encoded Word\");\n                const decodedHex = Utils.byteArrayToChars(fromHex(encodedWord.substring(i + 1, i + 3)));\n                decodedWord += decodedHex;\n                i += 2;\n            } else if (\n                (encodedWord[i].charCodeAt(0) >= \" \".charCodeAt(0) && encodedWord[i].charCodeAt(0) <= \"~\".charCodeAt(0)) ||\n                encodedWord[i] === \"\\n\" ||\n                encodedWord[i] === \"\\r\" ||\n                encodedWord[i] === \"\\t\") {\n                decodedWord += encodedWord[i];\n            } else {\n                throw new OperationError(\"Incorrectly Encoded Word\");\n            }\n        }\n\n        return decodedWord;\n    }\n}\n\nexport default MIMEDecoding;\n"
  },
  {
    "path": "src/core/operations/Magic.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Dish from \"../Dish.mjs\";\nimport MagicLib from \"../lib/Magic.mjs\";\n\n/**\n * Magic operation\n */\nclass Magic extends Operation {\n\n    /**\n     * Magic constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Magic\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.<br><br><b>Options</b><br><u>Depth:</u> If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.<br><br><u>Intensive mode:</u> When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.<br><br><u>Extensive language support:</u> At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar.<br><br>Optionally enter a regular expression to match a string you expect to find to filter results (crib).\";\n        this.infoURL = \"https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Depth\",\n                \"type\": \"number\",\n                \"value\": 3\n            },\n            {\n                \"name\": \"Intensive mode\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Extensive language support\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Crib (known plaintext string or regex)\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @returns {Object} The updated state of the recipe.\n     */\n    async run(state) {\n        const ings = state.opList[state.progress].ingValues,\n            [depth, intensive, extLang, crib] = ings,\n            dish = state.dish,\n            magic = new MagicLib(await dish.get(Dish.ARRAY_BUFFER)),\n            cribRegex = (crib && crib.length) ? new RegExp(crib, \"i\") : null;\n        let options = await magic.speculativeExecution(depth, extLang, intensive, [], false, cribRegex);\n\n        // Filter down to results which matched the crib\n        if (cribRegex) {\n            options = options.filter(option => option.matchesCrib);\n        }\n\n        // Record the current state for use when presenting\n        this.state = state;\n\n        dish.set(options, Dish.JSON);\n        return state;\n    }\n\n    /**\n     * Displays Magic results in HTML for web apps.\n     *\n     * @param {JSON} options\n     * @returns {html}\n     */\n    present(options) {\n        const currentRecipeConfig = this.state.opList.map(op => op.config);\n\n        let output = `<table\n                class='table table-hover table-sm table-bordered'\n                style='table-layout: fixed;'>\n            <tr>\n                <th>Recipe (click to load)</th>\n                <th>Result snippet</th>\n                <th>Properties</th>\n            </tr>`;\n\n        /**\n         * Returns a CSS colour value based on an integer input.\n         *\n         * @param {number} val\n         * @returns {string}\n         */\n        function chooseColour(val) {\n            if (val < 3) return \"green\";\n            if (val < 5) return \"goldenrod\";\n            return \"red\";\n        }\n\n        options.forEach(option => {\n            // Construct recipe URL\n            // Replace this Magic op with the generated recipe\n            const recipeConfig = currentRecipeConfig.slice(0, this.state.progress)\n                    .concat(option.recipe)\n                    .concat(currentRecipeConfig.slice(this.state.progress + 1)),\n                recipeURL = \"recipe=\" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(recipeConfig));\n\n            let language = \"\",\n                fileType = \"\",\n                matchingOps = \"\",\n                useful = \"\";\n            const entropy = `<span data-toggle=\"tooltip\" data-container=\"body\" title=\"Shannon Entropy is measured from 0 to 8. High entropy suggests encrypted or compressed data. Normal text is usually around 3.5 to 5.\">Entropy: <span style=\"color: ${chooseColour(option.entropy)}\">${option.entropy.toFixed(2)}</span></span>`,\n                validUTF8 = option.isUTF8 ? \"<span data-toggle='tooltip' data-container='body' title='The data could be a valid UTF8 string based on its encoding.'>Valid UTF8</span>\\n\" : \"\";\n\n            if (option.languageScores[0].probability > 0) {\n                let likelyLangs = option.languageScores.filter(l => l.probability > 0);\n                if (likelyLangs.length < 1) likelyLangs = [option.languageScores[0]];\n                language = \"<span data-toggle='tooltip' data-container='body' title='Based on a statistical comparison of the frequency of bytes in various languages. Ordered by likelihood.'>\" +\n                    \"Possible languages:\\n    \" +\n                    likelyLangs.map(lang => {\n                        return MagicLib.codeToLanguage(lang.lang);\n                    }).join(\"\\n    \") +\n                    \"</span>\\n\";\n            }\n\n            if (option.fileType) {\n                fileType = `<span data-toggle=\"tooltip\" data-container=\"body\" title=\"Based on the presence of magic bytes.\">File type: ${option.fileType.mime} (${option.fileType.ext})</span>\\n`;\n            }\n\n            if (option.matchingOps.length) {\n                matchingOps = `Matching ops: ${[...new Set(option.matchingOps.map(op => op.op))].join(\", \")}\\n`;\n            }\n\n            if (option.useful) {\n                useful = \"<span data-toggle='tooltip' data-container='body' title='This could be an operation that displays data in a useful way, such as rendering an image.'>Useful op detected</span>\\n\";\n            }\n\n            output += `<tr>\n                <td><a href=\"#${recipeURL}\">${Utils.generatePrettyRecipe(option.recipe, true)}</a></td>\n                <td>${Utils.escapeHtml(Utils.escapeWhitespace(Utils.truncate(option.data, 99)))}</td>\n                <td>${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy}</td>\n            </tr>`;\n        });\n\n        output += \"</table><script type='application/javascript'>$('[data-toggle=\\\"tooltip\\\"]').tooltip()</script>\";\n\n        if (!options.length) {\n            output = \"Nothing of interest could be detected about the input data.\\nHave you tried modifying the operation arguments?\";\n        }\n\n        return output;\n    }\n\n}\n\nexport default Magic;\n"
  },
  {
    "path": "src/core/operations/Mean.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { mean, createNumArray } from \"../lib/Arithmetic.mjs\";\nimport { ARITHMETIC_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\nimport BigNumber from \"bignumber.js\";\n\n/**\n * Mean operation\n */\nclass Mean extends Operation {\n\n    /**\n     * Mean constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Mean\";\n        this.module = \"Default\";\n        this.description = \"Computes the mean (average) of a number list. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5 .5</code> becomes <code>4.75</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Arithmetic_mean\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": ARITHMETIC_DELIM_OPTIONS,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const val = mean(createNumArray(input, args[0]));\n        return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);\n    }\n\n}\n\nexport default Mean;\n"
  },
  {
    "path": "src/core/operations/Median.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport BigNumber from \"bignumber.js\";\nimport Operation from \"../Operation.mjs\";\nimport { median, createNumArray } from \"../lib/Arithmetic.mjs\";\nimport { ARITHMETIC_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n/**\n * Median operation\n */\nclass Median extends Operation {\n\n    /**\n     * Median constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Median\";\n        this.module = \"Default\";\n        this.description = \"Computes the median of a number list. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 1 .5</code> becomes <code>4.5</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Median\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": ARITHMETIC_DELIM_OPTIONS,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const val = median(createNumArray(input, args[0]));\n        return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);\n    }\n\n}\n\nexport default Median;\n"
  },
  {
    "path": "src/core/operations/Merge.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Merge operation\n */\nclass Merge extends Operation {\n\n    /**\n     * Merge constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Merge\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"Consolidate all branches back into a single trunk. The opposite of Fork. Unticking the Merge All checkbox will only consolidate all branches up to the nearest Fork/Subsection.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Merge All\",\n                type: \"boolean\",\n                value: true,\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @returns {Object} The updated state of the recipe.\n     */\n    run(state) {\n        // No need to actually do anything here. The fork operation will\n        // merge when it sees this operation.\n        return state;\n    }\n\n}\n\nexport default Merge;\n"
  },
  {
    "path": "src/core/operations/MicrosoftScriptDecoder.mjs",
    "content": "/**\n * @author bmwhitn [brian.m.whitney@outlook.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Microsoft Script Decoder operation\n */\nclass MicrosoftScriptDecoder extends Operation {\n\n    /**\n     * MicrosoftScriptDecoder constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Microsoft Script Decoder\";\n        this.module = \"Default\";\n        this.description = \"Decodes Microsoft Encoded Script files that have been encoded with Microsoft's custom encoding. These are often VBS (Visual Basic Script) files that are encoded and renamed with a '.vbe' extention or JS (JScript) files renamed with a '.jse' extention.<br><br><b>Sample</b><br><br>Encoded:<br><code>#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&amp;@*eEI@#@&amp;@#@&amp;.jm.raY 214Wv:zms/obI0xEAAA==^#~@</code><br><br>Decoded:<br><code>var my_msg = &#34;Testing <1><2><3>!&#34;;\\n\\nVScript.Echo(my_msg);</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/JScript.Encode\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern: \"#@~\\\\^.{6}==(.+).{6}==\\\\^#~@\",\n                flags: \"i\",\n                args: []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const matcher = /#@~\\^.{6}==(.+).{6}==\\^#~@/;\n        const encodedData = matcher.exec(input);\n        if (encodedData) {\n            return MicrosoftScriptDecoder._decode(encodedData[1]);\n        } else {\n            return \"\";\n        }\n    }\n\n    /**\n     * Decodes Microsoft Encoded Script files that can be read and executed by cscript.exe/wscript.exe.\n     * This is a conversion of a Python script that was originally created by Didier Stevens\n     * (https://DidierStevens.com).\n     *\n     * @private\n     * @param {string} data\n     * @returns {string}\n     */\n    static _decode(data) {\n        const result = [];\n        let index = -1;\n        data = data.replace(/@&/g, String.fromCharCode(10))\n            .replace(/@#/g, String.fromCharCode(13))\n            .replace(/@\\*/g, \">\")\n            .replace(/@!/g, \"<\")\n            .replace(/@\\$/g, \"@\");\n\n        for (let i = 0; i < data.length; i++) {\n            const byte = data.charCodeAt(i);\n            let char = data.charAt(i);\n            if (byte < 128) {\n                index++;\n            }\n\n            if ((byte === 9 || byte > 31 && byte < 128) &&\n                byte !== 60 &&\n                byte !== 62 &&\n                byte !== 64) {\n                char = D_DECODE[byte].charAt(D_COMBINATION[index % 64]);\n            }\n            result.push(char);\n        }\n        return result.join(\"\");\n    }\n\n}\n\nconst D_DECODE = [\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    \"\",\n    \"\\x57\\x6E\\x7B\",\n    \"\\x4A\\x4C\\x41\",\n    \"\\x0B\\x0B\\x0B\",\n    \"\\x0C\\x0C\\x0C\",\n    \"\\x4A\\x4C\\x41\",\n    \"\\x0E\\x0E\\x0E\",\n    \"\\x0F\\x0F\\x0F\",\n    \"\\x10\\x10\\x10\",\n    \"\\x11\\x11\\x11\",\n    \"\\x12\\x12\\x12\",\n    \"\\x13\\x13\\x13\",\n    \"\\x14\\x14\\x14\",\n    \"\\x15\\x15\\x15\",\n    \"\\x16\\x16\\x16\",\n    \"\\x17\\x17\\x17\",\n    \"\\x18\\x18\\x18\",\n    \"\\x19\\x19\\x19\",\n    \"\\x1A\\x1A\\x1A\",\n    \"\\x1B\\x1B\\x1B\",\n    \"\\x1C\\x1C\\x1C\",\n    \"\\x1D\\x1D\\x1D\",\n    \"\\x1E\\x1E\\x1E\",\n    \"\\x1F\\x1F\\x1F\",\n    \"\\x2E\\x2D\\x32\",\n    \"\\x47\\x75\\x30\",\n    \"\\x7A\\x52\\x21\",\n    \"\\x56\\x60\\x29\",\n    \"\\x42\\x71\\x5B\",\n    \"\\x6A\\x5E\\x38\",\n    \"\\x2F\\x49\\x33\",\n    \"\\x26\\x5C\\x3D\",\n    \"\\x49\\x62\\x58\",\n    \"\\x41\\x7D\\x3A\",\n    \"\\x34\\x29\\x35\",\n    \"\\x32\\x36\\x65\",\n    \"\\x5B\\x20\\x39\",\n    \"\\x76\\x7C\\x5C\",\n    \"\\x72\\x7A\\x56\",\n    \"\\x43\\x7F\\x73\",\n    \"\\x38\\x6B\\x66\",\n    \"\\x39\\x63\\x4E\",\n    \"\\x70\\x33\\x45\",\n    \"\\x45\\x2B\\x6B\",\n    \"\\x68\\x68\\x62\",\n    \"\\x71\\x51\\x59\",\n    \"\\x4F\\x66\\x78\",\n    \"\\x09\\x76\\x5E\",\n    \"\\x62\\x31\\x7D\",\n    \"\\x44\\x64\\x4A\",\n    \"\\x23\\x54\\x6D\",\n    \"\\x75\\x43\\x71\",\n    \"\\x4A\\x4C\\x41\",\n    \"\\x7E\\x3A\\x60\",\n    \"\\x4A\\x4C\\x41\",\n    \"\\x5E\\x7E\\x53\",\n    \"\\x40\\x4C\\x40\",\n    \"\\x77\\x45\\x42\",\n    \"\\x4A\\x2C\\x27\",\n    \"\\x61\\x2A\\x48\",\n    \"\\x5D\\x74\\x72\",\n    \"\\x22\\x27\\x75\",\n    \"\\x4B\\x37\\x31\",\n    \"\\x6F\\x44\\x37\",\n    \"\\x4E\\x79\\x4D\",\n    \"\\x3B\\x59\\x52\",\n    \"\\x4C\\x2F\\x22\",\n    \"\\x50\\x6F\\x54\",\n    \"\\x67\\x26\\x6A\",\n    \"\\x2A\\x72\\x47\",\n    \"\\x7D\\x6A\\x64\",\n    \"\\x74\\x39\\x2D\",\n    \"\\x54\\x7B\\x20\",\n    \"\\x2B\\x3F\\x7F\",\n    \"\\x2D\\x38\\x2E\",\n    \"\\x2C\\x77\\x4C\",\n    \"\\x30\\x67\\x5D\",\n    \"\\x6E\\x53\\x7E\",\n    \"\\x6B\\x47\\x6C\",\n    \"\\x66\\x34\\x6F\",\n    \"\\x35\\x78\\x79\",\n    \"\\x25\\x5D\\x74\",\n    \"\\x21\\x30\\x43\",\n    \"\\x64\\x23\\x26\",\n    \"\\x4D\\x5A\\x76\",\n    \"\\x52\\x5B\\x25\",\n    \"\\x63\\x6C\\x24\",\n    \"\\x3F\\x48\\x2B\",\n    \"\\x7B\\x55\\x28\",\n    \"\\x78\\x70\\x23\",\n    \"\\x29\\x69\\x41\",\n    \"\\x28\\x2E\\x34\",\n    \"\\x73\\x4C\\x09\",\n    \"\\x59\\x21\\x2A\",\n    \"\\x33\\x24\\x44\",\n    \"\\x7F\\x4E\\x3F\",\n    \"\\x6D\\x50\\x77\",\n    \"\\x55\\x09\\x3B\",\n    \"\\x53\\x56\\x55\",\n    \"\\x7C\\x73\\x69\",\n    \"\\x3A\\x35\\x61\",\n    \"\\x5F\\x61\\x63\",\n    \"\\x65\\x4B\\x50\",\n    \"\\x46\\x58\\x67\",\n    \"\\x58\\x3B\\x51\",\n    \"\\x31\\x57\\x49\",\n    \"\\x69\\x22\\x4F\",\n    \"\\x6C\\x6D\\x46\",\n    \"\\x5A\\x4D\\x68\",\n    \"\\x48\\x25\\x7C\",\n    \"\\x27\\x28\\x36\",\n    \"\\x5C\\x46\\x70\",\n    \"\\x3D\\x4A\\x6E\",\n    \"\\x24\\x32\\x7A\",\n    \"\\x79\\x41\\x2F\",\n    \"\\x37\\x3D\\x5F\",\n    \"\\x60\\x5F\\x4B\",\n    \"\\x51\\x4F\\x5A\",\n    \"\\x20\\x42\\x2C\",\n    \"\\x36\\x65\\x57\"\n];\n\nconst D_COMBINATION = [\n    0, 1, 2, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 2, 1, 2, 0, 2, 1, 2, 0, 0, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1,\n    0, 0, 2, 1, 2, 1, 2, 0, 2, 0, 0, 1, 2, 0, 2, 1, 0, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 2, 1\n];\n\nexport default MicrosoftScriptDecoder;\n"
  },
  {
    "path": "src/core/operations/MultipleBombe.mjs",
    "content": "/**\n * Emulation of the Bombe machine.\n * This version carries out multiple Bombe runs to handle unknown rotor configurations.\n *\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { BombeMachine } from \"../lib/Bombe.mjs\";\nimport { ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector } from \"../lib/Enigma.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n\n/**\n * Convenience method for flattening the preset ROTORS object into a newline-separated string.\n * @param {Object[]} - Preset rotors object\n * @param {number} s - Start index\n * @param {number} n - End index\n * @returns {string}\n */\nfunction rotorsFormat(rotors, s, n) {\n    const res = [];\n    for (const i of rotors.slice(s, n)) {\n        res.push(i.value);\n    }\n    return res.join(\"\\n\");\n}\n\n/**\n * Combinatorics choose function\n * @param {number} n\n * @param {number} k\n * @returns number\n */\nfunction choose(n, k) {\n    let res = 1;\n    for (let i=1; i<=k; i++) {\n        res *= (n + 1 - i) / i;\n    }\n    return res;\n}\n\n/**\n * Bombe operation\n */\nclass MultipleBombe extends Operation {\n    /**\n     * Bombe constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Multiple Bombe\";\n        this.module = \"Bletchley\";\n        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.<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bombe\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Standard Enigmas\",\n                \"type\": \"populateMultiOption\",\n                \"value\": [\n                    {\n                        name: \"German Service Enigma (First - 3 rotor)\",\n                        value: [\n                            rotorsFormat(ROTORS, 0, 5),\n                            \"\",\n                            rotorsFormat(REFLECTORS, 0, 1)\n                        ]\n                    },\n                    {\n                        name: \"German Service Enigma (Second - 3 rotor)\",\n                        value: [\n                            rotorsFormat(ROTORS, 0, 8),\n                            \"\",\n                            rotorsFormat(REFLECTORS, 0, 2)\n                        ]\n                    },\n                    {\n                        name: \"German Service Enigma (Third - 4 rotor)\",\n                        value: [\n                            rotorsFormat(ROTORS, 0, 8),\n                            rotorsFormat(ROTORS_FOURTH, 1, 2),\n                            rotorsFormat(REFLECTORS, 2, 3)\n                        ]\n                    },\n                    {\n                        name: \"German Service Enigma (Fourth - 4 rotor)\",\n                        value: [\n                            rotorsFormat(ROTORS, 0, 8),\n                            rotorsFormat(ROTORS_FOURTH, 1, 3),\n                            rotorsFormat(REFLECTORS, 2, 4)\n                        ]\n                    },\n                    {\n                        name: \"User defined\",\n                        value: [\"\", \"\", \"\"]\n                    },\n                ],\n                \"target\": [1, 2, 3]\n            },\n            {\n                name: \"Main rotors\",\n                type: \"text\",\n                value: \"\"\n            },\n            {\n                name: \"4th rotor\",\n                type: \"text\",\n                value: \"\"\n            },\n            {\n                name: \"Reflectors\",\n                type: \"text\",\n                value: \"\"\n            },\n            {\n                name: \"Crib\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Crib offset\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Use checking machine\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n    }\n\n    /**\n     * Format and send a status update message.\n     * @param {number} nLoops - Number of loops in the menu\n     * @param {number} nStops - How many stops so far\n     * @param {number} progress - Progress (as a float in the range 0..1)\n     */\n    updateStatus(nLoops, nStops, progress, start) {\n        const elapsed = Date.now() - start;\n        const remaining = (elapsed / progress) * (1 - progress) / 1000;\n        const hours = Math.floor(remaining / 3600);\n        const minutes = `0${Math.floor((remaining % 3600) / 60)}`.slice(-2);\n        const seconds = `0${Math.floor(remaining % 60)}`.slice(-2);\n        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`;\n        self.sendStatusMessage(msg);\n    }\n\n    /**\n     * Early rotor description string validation.\n     * Drops stepping information.\n     * @param {string} rstr - The rotor description string\n     * @returns {string} - Rotor description with stepping stripped, if any\n     */\n    validateRotor(rstr) {\n        // The Bombe doesn't take stepping into account so we'll just ignore it here\n        if (rstr.includes(\"<\")) {\n            rstr = rstr.split(\"<\", 2)[0];\n        }\n        // Duplicate the validation of the rotor strings here, otherwise you might get an error\n        // thrown halfway into a big Bombe run\n        if (!/^[A-Z]{26}$/.test(rstr)) {\n            throw new OperationError(\"Rotor wiring must be 26 unique uppercase letters\");\n        }\n        if (new Set(rstr).size !== 26) {\n            throw new OperationError(\"Rotor wiring must be 26 unique uppercase letters\");\n        }\n        return rstr;\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const mainRotorsStr = args[1];\n        const fourthRotorsStr = args[2];\n        const reflectorsStr = args[3];\n        let crib = args[4];\n        const offset = args[5];\n        const check = args[6];\n        const rotors = [];\n        const fourthRotors = [];\n        const reflectors = [];\n        for (let rstr of mainRotorsStr.split(\"\\n\")) {\n            rstr = this.validateRotor(rstr);\n            rotors.push(rstr);\n        }\n        if (rotors.length < 3) {\n            throw new OperationError(\"A minimum of three rotors must be supplied\");\n        }\n        if (fourthRotorsStr !== \"\") {\n            for (let rstr of fourthRotorsStr.split(\"\\n\")) {\n                rstr = this.validateRotor(rstr);\n                fourthRotors.push(rstr);\n            }\n        }\n        if (fourthRotors.length === 0) {\n            fourthRotors.push(\"\");\n        }\n        for (const rstr of reflectorsStr.split(\"\\n\")) {\n            const reflector = new Reflector(rstr);\n            reflectors.push(reflector);\n        }\n        if (reflectors.length === 0) {\n            throw new OperationError(\"A minimum of one reflector must be supplied\");\n        }\n        if (crib.length === 0) {\n            throw new OperationError(\"Crib cannot be empty\");\n        }\n        if (offset < 0) {\n            throw new OperationError(\"Offset cannot be negative\");\n        }\n        // For symmetry with the Enigma op, for the input we'll just remove all invalid characters\n        input = input.replace(/[^A-Za-z]/g, \"\").toUpperCase();\n        crib = crib.replace(/[^A-Za-z]/g, \"\").toUpperCase();\n        const ciphertext = input.slice(offset);\n        let update;\n        if (isWorkerEnvironment()) {\n            update = this.updateStatus;\n        } else {\n            update = undefined;\n        }\n        let bombe = undefined;\n        const output = {bombeRuns: []};\n        // I could use a proper combinatorics algorithm here... but it would be more code to\n        // write one, and we don't seem to have one in our existing libraries, so massively nested\n        // for loop it is\n        const totalRuns = choose(rotors.length, 3) * 6 * fourthRotors.length * reflectors.length;\n        let nRuns = 0;\n        let nStops = 0;\n        const start = Date.now();\n        for (const rotor1 of rotors) {\n            for (const rotor2 of rotors) {\n                if (rotor2 === rotor1) {\n                    continue;\n                }\n                for (const rotor3 of rotors) {\n                    if (rotor3 === rotor2 || rotor3 === rotor1) {\n                        continue;\n                    }\n                    for (const rotor4 of fourthRotors) {\n                        for (const reflector of reflectors) {\n                            nRuns++;\n                            const runRotors = [rotor1, rotor2, rotor3];\n                            if (rotor4 !== \"\") {\n                                runRotors.push(rotor4);\n                            }\n                            if (bombe === undefined) {\n                                bombe = new BombeMachine(runRotors, reflector, ciphertext, crib, check);\n                                output.nLoops = bombe.nLoops;\n                            } else {\n                                bombe.changeRotors(runRotors, reflector);\n                            }\n                            const result = bombe.run();\n                            nStops += result.length;\n                            if (update !== undefined) {\n                                update(bombe.nLoops, nStops, nRuns / totalRuns, start);\n                            }\n                            if (result.length > 0) {\n                                output.bombeRuns.push({\n                                    rotors: runRotors,\n                                    reflector: reflector.pairs,\n                                    result: result\n                                });\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        return output;\n    }\n\n\n    /**\n     * Displays the MultiBombe results in an HTML table\n     *\n     * @param {Object} output\n     * @param {number} output.nLoops\n     * @param {Array[]} output.result\n     * @returns {html}\n     */\n    present(output) {\n        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`;\n\n        for (const run of output.bombeRuns) {\n            html += `\\nRotors: ${run.rotors.slice().reverse().join(\", \")}\\nReflector: ${run.reflector}\\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>\\n\";\n            for (const [setting, stecker, decrypt] of run.result) {\n                html += `<tr><td>${setting}</td>  <td>${stecker}</td>  <td>${decrypt}</td></tr>\\n`;\n            }\n            html += \"</table>\\n\";\n        }\n        return html;\n    }\n}\n\nexport default MultipleBombe;\n"
  },
  {
    "path": "src/core/operations/Multiply.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport BigNumber from \"bignumber.js\";\nimport Operation from \"../Operation.mjs\";\nimport { multi, createNumArray } from \"../lib/Arithmetic.mjs\";\nimport { ARITHMETIC_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n\n/**\n * Multiply operation\n */\nclass Multiply extends Operation {\n\n    /**\n     * Multiply constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Multiply\";\n        this.module = \"Default\";\n        this.description = \"Multiplies a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>40</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Multiplication\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": ARITHMETIC_DELIM_OPTIONS,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const val = multi(createNumArray(input, args[0]));\n        return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);\n    }\n\n}\n\nexport default Multiply;\n"
  },
  {
    "path": "src/core/operations/MurmurHash3.mjs",
    "content": "/**\n * Based on murmurhash-js (https://github.com/garycourt/murmurhash-js)\n * @author Gary Court\n * @license MIT\n *\n * @author AliceGrey [alice@grey.systems]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * MurmurHash3 operation\n */\nclass MurmurHash3 extends Operation {\n\n    /**\n     * MurmurHash3 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"MurmurHash3\";\n        this.module = \"Hashing\";\n        this.description = \"Generates a MurmurHash v3 for a string input and an optional seed input\";\n        this.infoURL = \"https://wikipedia.org/wiki/MurmurHash\";\n        this.inputType = \"string\";\n        this.outputType = \"number\";\n        this.args = [\n            {\n                name: \"Seed\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Convert to Signed\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n /**\n * Calculates the MurmurHash3 hash of the input.\n * Based on Gary Court's JS MurmurHash implementation\n * @see http://github.com/garycourt/murmurhash-js\n * @author AliceGrey [alice@grey.systems]\n * @param {string} input ASCII only\n * @param {number} seed Positive integer only\n * @return {number} 32-bit positive integer hash\n */\n    mmh3(input, seed) {\n        let h1b;\n        let k1;\n        const remainder = input.length & 3; // input.length % 4\n        const bytes = input.length - remainder;\n        let h1 = seed;\n        const c1 = 0xcc9e2d51;\n        const c2 = 0x1b873593;\n        let i = 0;\n\n        while (i < bytes) {\n            k1 =\n                ((input.charCodeAt(i) & 0xff)) |\n                ((input.charCodeAt(++i) & 0xff) << 8) |\n                ((input.charCodeAt(++i) & 0xff) << 16) |\n                ((input.charCodeAt(++i) & 0xff) << 24);\n            ++i;\n\n            k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;\n            k1 = (k1 << 15) | (k1 >>> 17);\n            k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;\n\n            h1 ^= k1;\n            h1 = (h1 << 13) | (h1 >>> 19);\n            h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;\n            h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));\n        }\n\n        k1 = 0;\n\n        if (remainder === 3) {\n            k1 ^= (input.charCodeAt(i + 2) & 0xff) << 16;\n        }\n\n        if (remainder === 3 || remainder === 2) {\n            k1 ^= (input.charCodeAt(i + 1) & 0xff) << 8;\n        }\n\n        if (remainder === 3 || remainder === 2 || remainder === 1) {\n            k1 ^= (input.charCodeAt(i) & 0xff);\n\n            k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;\n            k1 = (k1 << 15) | (k1 >>> 17);\n            k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;\n            h1 ^= k1;\n        }\n\n        h1 ^= input.length;\n\n        h1 ^= h1 >>> 16;\n        h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;\n        h1 ^= h1 >>> 13;\n        h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;\n        h1 ^= h1 >>> 16;\n\n        return h1 >>> 0;\n    }\n\n    /**\n    * Converts an unsigned 32-bit integer to a signed 32-bit integer\n    * @author AliceGrey [alice@grey.systems]\n    * @param {value} 32-bit unsigned integer\n    * @return {number} 32-bit signed integer\n    */\n    unsignedToSigned(value) {\n        return value & 0x80000000 ? -0x100000000 + value : value;\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        if (args && args.length >= 1) {\n            const seed = args[0];\n            const hash = this.mmh3(input, seed);\n            if (args.length > 1 && args[1]) {\n                return this.unsignedToSigned(hash);\n            }\n            return hash;\n        }\n        return this.mmh3(input);\n    }\n}\n\nexport default MurmurHash3;\n"
  },
  {
    "path": "src/core/operations/NOT.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { bitOp, not } from \"../lib/BitwiseOp.mjs\";\n\n/**\n * NOT operation\n */\nclass NOT extends Operation {\n\n    /**\n     * NOT constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"NOT\";\n        this.module = \"Default\";\n        this.description = \"Returns the inverse of each byte.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#NOT\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        return bitOp(input, null, not);\n    }\n\n    /**\n     * Highlight NOT\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight NOT in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default NOT;\n"
  },
  {
    "path": "src/core/operations/NTHash.mjs",
    "content": "/**\n * @author brun0ne [brunonblok@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * NT Hash operation\n */\nclass NTHash extends Operation {\n\n    /**\n     * NTHash constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"NT Hash\";\n        this.module = \"Crypto\";\n        this.description = \"An NT Hash, sometimes referred to as an NTLM hash, is a method of storing passwords on Windows systems. It works by running MD4 on UTF-16LE encoded input. NTLM hashes are considered weak because they can be brute-forced very easily with modern hardware.\";\n        this.infoURL = \"https://wikipedia.org/wiki/NT_LAN_Manager\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        // Convert to UTF-16LE\n        const buf = new ArrayBuffer(input.length * 2);\n        const bufView = new Uint16Array(buf);\n        for (let i = 0; i < input.length; i++) {\n            bufView[i] = input.charCodeAt(i);\n        }\n\n        const hashed = runHash(\"md4\", buf);\n        return hashed.toUpperCase();\n    }\n}\n\nexport default NTHash;\n"
  },
  {
    "path": "src/core/operations/NormaliseImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Normalise Image operation\n */\nclass NormaliseImage extends Operation {\n    /**\n     * NormaliseImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Normalise Image\";\n        this.module = \"Image\";\n        this.description = \"Normalise the image colours.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error opening image file. (${err})`);\n        }\n\n        try {\n            image.normalize();\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error normalising image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the normalised image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default NormaliseImage;\n"
  },
  {
    "path": "src/core/operations/NormaliseUnicode.mjs",
    "content": "/**\n * @author Matthieu [m@tthieu.xyz]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {UNICODE_NORMALISATION_FORMS} from \"../lib/ChrEnc.mjs\";\nimport unorm from \"unorm\";\n\n/**\n * Normalise Unicode operation\n */\nclass NormaliseUnicode extends Operation {\n\n    /**\n     * NormaliseUnicode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Normalise Unicode\";\n        this.module = \"Encodings\";\n        this.description = \"Transform Unicode characters to one of the Normalisation Forms\";\n        this.infoURL = \"https://wikipedia.org/wiki/Unicode_equivalence#Normal_forms\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Normal Form\",\n                type: \"option\",\n                value: UNICODE_NORMALISATION_FORMS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [normalForm] = args;\n\n        switch (normalForm) {\n            case \"NFD\":\n                return unorm.nfd(input);\n            case \"NFC\":\n                return unorm.nfc(input);\n            case \"NFKD\":\n                return unorm.nfkd(input);\n            case \"NFKC\":\n                return unorm.nfkc(input);\n            default:\n                throw new OperationError(\"Unknown Normalisation Form\");\n        }\n    }\n\n}\n\nexport default NormaliseUnicode;\n"
  },
  {
    "path": "src/core/operations/Numberwang.mjs",
    "content": "/**\n * @author Unknown Male 282\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Numberwang operation. Remain indoors.\n */\nclass Numberwang extends Operation {\n\n    /**\n     * Numberwang constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Numberwang\";\n        this.module = \"Default\";\n        this.description = \"Based on the popular gameshow by Mitchell and Webb.\";\n        this.infoURL = \"https://wikipedia.org/wiki/That_Mitchell_and_Webb_Look#Recurring_sketches\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let output;\n        if (!input) {\n            output = \"Let's play Wangernumb!\";\n        } else {\n            const match = input.match(/(f0rty-s1x|shinty-six|filth-hundred and neeb|-?√?\\d+(\\.\\d+)?i?([a-z]?)%?)/i);\n            if (match) {\n                if (match[3]) output = match[0] + \"! That's AlphaNumericWang!\";\n                else output = match[0] + \"! That's Numberwang!\";\n            } else {\n                // That's a bad miss!\n                output = \"Sorry, that's not Numberwang. Let's rotate the board!\";\n            }\n        }\n\n        const rand = Math.floor(Math.random() * didYouKnow.length);\n        return output + \"\\n\\nDid you know: \" + didYouKnow[rand];\n    }\n\n}\n\n/**\n * Taken from http://numberwang.wikia.com/wiki/Numberwang_Wikia\n *\n * @constant\n */\nconst didYouKnow = [\n    \"Numberwang, contrary to popular belief, is a fruit and not a vegetable.\",\n    \"Robert Webb once got WordWang while presenting an episode of Numberwang.\",\n    \"The 6705th digit of pi is Numberwang.\",\n    \"Numberwang was invented on a Sevenday.\",\n    \"Contrary to popular belief, Albert Einstein always got good grades in Numberwang at school. He once scored ^4$ on a test.\",\n    \"680 asteroids have been named after Numberwang.\",\n    \"Archimedes is most famous for proclaiming \\\"That's Numberwang!\\\" during an epiphany about water displacement he had while taking a bath.\",\n    \"Numberwang Day is celebrated in Japan on every day of the year apart from June 6.\",\n    \"Biologists recently discovered Numberwang within a strand of human DNA.\",\n    \"Numbernot is a special type of non-Numberwang number. It is divisible by 3 and the letter \\\"y\\\".\",\n    \"Julie once got 612.04 Numberwangs in a single episode of Emmerdale.\",\n    \"In India, it is traditional to shout out \\\"Numberwang!\\\" instead of checkmate during games of chess.\",\n    \"There is a rule on Countdown which states that if you get Numberwang in the numbers round, you automatically win. It has only ever been invoked twice.\",\n    \"\\\"Numberwang\\\" was the third-most common baby name for a brief period in 1722.\",\n    \"\\\"The Lion King\\\" was loosely based on Numberwang.\",\n    \"\\\"A Numberwang a day keeps the doctor away\\\" is how Donny Cosy, the oldest man in the world, explained how he was in such good health at the age of 136.\",\n    \"The \\\"number lock\\\" button on a keyboard is based on the popular round of the same name in \\\"Numberwang\\\".\",\n    \"Cambridge became the first university to offer a course in Numberwang in 1567.\",\n    \"Schrödinger's Numberwang is a number that has been confusing dentists for centuries.\",\n    \"\\\"Harry Potter and the Numberwang of Numberwang\\\" was rejected by publishers -41 times before it became a bestseller.\",\n    \"\\\"Numberwang\\\" is the longest-running British game show in history; it has aired 226 seasons, each containing 19 episodes, which makes a grand total of 132 episodes.\",\n    \"The triple Numberwang bonus was discovered by archaeologist Thomas Jefferson in Somerset.\",\n    \"Numberwang is illegal in parts of Czechoslovakia.\",\n    \"Numberwang was discovered in India in the 12th century.\",\n    \"Numberwang has the chemical formula Zn4SO2(HgEs)3.\",\n    \"The first pack of cards ever created featured two \\\"Numberwang\\\" cards instead of jokers.\",\n    \"Julius Caesar was killed by an overdose of Numberwang.\",\n    \"The most Numberwang musical note is G#.\",\n    \"In 1934, the forty-third Google Doodle promoted the upcoming television show \\\"Numberwang on Ice\\\".\",\n    \"A recent psychology study found that toddlers were 17% faster at identifying numbers which were Numberwang.\",\n    \"There are 700 ways to commit a foul in the television show \\\"Numberwang\\\". All 700 of these fouls were committed by Julie in one single episode in 1473.\",\n    \"Astronomers suspect God is Numberwang.\",\n    \"Numberwang is the official beverage of Canada.\",\n    \"In the pilot episode of \\\"The Price is Right\\\", if a contestant got the value of an item exactly right they were told \\\"That's Numberwang!\\\" and immediately won ₹5.7032.\",\n    \"The first person to get three Numberwangs in a row was Madonna.\",\n    \"\\\"Numberwang\\\" has the code U+46402 in Unicode.\",\n    \"The musical note \\\"Numberwang\\\" is between D# and E♮.\",\n    \"Numberwang was first played on the moon in 1834.\",\n];\n\nexport default Numberwang;\n"
  },
  {
    "path": "src/core/operations/OR.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { bitOp, or, BITWISE_OP_DELIMS } from \"../lib/BitwiseOp.mjs\";\n\n/**\n * OR operation\n */\nclass OR extends Operation {\n\n    /**\n     * OR constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"OR\";\n        this.module = \"Default\";\n        this.description = \"OR the input with the given key.<br>e.g. <code>fe023da5</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#OR\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": BITWISE_OP_DELIMS\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string || \"\", args[0].option);\n        input = new Uint8Array(input);\n\n        return bitOp(input, key, or);\n    }\n\n    /**\n     * Highlight OR\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight OR in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default OR;\n"
  },
  {
    "path": "src/core/operations/ObjectIdentifierToHex.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Object Identifier to Hex operation\n */\nclass ObjectIdentifierToHex extends Operation {\n\n    /**\n     * ObjectIdentifierToHex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Object Identifier to Hex\";\n        this.module = \"PublicKey\";\n        this.description = \"Converts an object identifier (OID) into a hexadecimal string.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Object_identifier\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return r.KJUR.asn1.ASN1Util.oidIntToHex(input);\n    }\n\n}\n\nexport default ObjectIdentifierToHex;\n"
  },
  {
    "path": "src/core/operations/OffsetChecker.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Offset checker operation\n */\nclass OffsetChecker extends Operation {\n\n    /**\n     * OffsetChecker constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Offset checker\";\n        this.module = \"Default\";\n        this.description = \"Compares multiple inputs (separated by the specified delimiter) and highlights matching characters which appear at the same position in all samples.\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Sample delimiter\",\n                \"type\": \"binaryString\",\n                \"value\": \"\\\\n\\\\n\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const sampleDelim = args[0],\n            samples = input.split(sampleDelim),\n            outputs = new Array(samples.length);\n        let i = 0,\n            s = 0,\n            match = false,\n            inMatch = false,\n            chr;\n\n        if (!samples || samples.length < 2) {\n            throw new OperationError(\"Not enough samples, perhaps you need to modify the sample delimiter or add more data?\");\n        }\n\n        // Initialise output strings\n        outputs.fill(\"\", 0, samples.length);\n\n        // Loop through each character in the first sample\n        for (i = 0; i < samples[0].length; i++) {\n            chr = samples[0][i];\n            match = false;\n\n            // Loop through each sample to see if the chars are the same\n            for (s = 1; s < samples.length; s++) {\n                if (samples[s][i] !== chr) {\n                    match = false;\n                    break;\n                }\n                match = true;\n            }\n\n            // Write output for each sample\n            for (s = 0; s < samples.length; s++) {\n                if (samples[s].length <= i) {\n                    if (inMatch) outputs[s] += \"</span>\";\n                    if (s === samples.length - 1) inMatch = false;\n                    continue;\n                }\n\n                if (match && !inMatch) {\n                    outputs[s] += \"<span class='hl5'>\" + Utils.escapeHtml(samples[s][i]);\n                    if (samples[s].length === i + 1) outputs[s] += \"</span>\";\n                    if (s === samples.length - 1) inMatch = true;\n                } else if (!match && inMatch) {\n                    outputs[s] += \"</span>\" + Utils.escapeHtml(samples[s][i]);\n                    if (s === samples.length - 1) inMatch = false;\n                } else {\n                    outputs[s] += Utils.escapeHtml(samples[s][i]);\n                    if (inMatch && samples[s].length === i + 1) {\n                        outputs[s] += \"</span>\";\n                        if (samples[s].length - 1 !== i) inMatch = false;\n                    }\n                }\n\n                if (samples[0].length - 1 === i) {\n                    if (inMatch) outputs[s] += \"</span>\";\n                    outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1));\n                }\n            }\n        }\n\n        return outputs.join(sampleDelim);\n    }\n\n}\n\nexport default OffsetChecker;\n"
  },
  {
    "path": "src/core/operations/OpticalCharacterRecognition.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author mshwed [m@ttshwed.com]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\nimport { createWorker } from \"tesseract.js\";\n\nconst OEM_MODES = [\"Tesseract only\", \"LSTM only\", \"Tesseract/LSTM Combined\"];\n\n/**\n * Optical Character Recognition operation\n */\nclass OpticalCharacterRecognition extends Operation {\n\n    /**\n     * OpticalCharacterRecognition constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Optical Character Recognition\";\n        this.module = \"OCR\";\n        this.description = \"Optical character recognition or optical character reader (OCR) is the mechanical or electronic conversion of images of typed, handwritten or printed text into machine-encoded text.<br><br>Supported image formats: png, jpg, bmp, pbm.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Optical_character_recognition\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Show confidence\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"OCR Engine Mode\",\n                type: \"option\",\n                value: OEM_MODES,\n                defaultIndex: 1\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [showConfidence, oemChoice] = args;\n\n        if (!isWorkerEnvironment()) throw new OperationError(\"This operation only works in a browser\");\n\n        const type = isImage(input);\n        if (!type) {\n            throw new OperationError(\"Unsupported file type (supported: jpg,png,pbm,bmp) or no file provided\");\n        }\n\n        const assetDir = `${self.docURL}/assets/`;\n        const oem = OEM_MODES.indexOf(oemChoice);\n\n        try {\n            self.sendStatusMessage(\"Spinning up Tesseract worker...\");\n            const image = `data:${type};base64,${toBase64(input)}`;\n            const worker = await createWorker(\"eng\", oem, {\n                workerPath: `${assetDir}tesseract/worker.min.js`,\n                langPath: `${assetDir}tesseract/lang-data`,\n                corePath: `${assetDir}tesseract/tesseract-core.wasm.js`,\n                logger: progress => {\n                    if (isWorkerEnvironment()) {\n                        self.sendStatusMessage(`Status: ${progress.status}${progress.status === \"recognizing text\" ? ` - ${(parseFloat(progress.progress)*100).toFixed(2)}%`: \"\" }`);\n                    }\n                }\n            });\n            self.sendStatusMessage(\"Finding text...\");\n            const result = await worker.recognize(image);\n\n            if (showConfidence) {\n                return `Confidence: ${result.data.confidence}%\\n\\n${result.data.text}`;\n            } else {\n                return result.data.text;\n            }\n        } catch (err) {\n            throw new OperationError(`Error performing OCR on image. (${err})`);\n        }\n    }\n}\n\nexport default OpticalCharacterRecognition;\n"
  },
  {
    "path": "src/core/operations/PEMToHex.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author cplussharp\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport { fromBase64 } from \"../lib/Base64.mjs\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * PEM to Hex operation\n */\nclass PEMToHex extends Operation {\n\n    /**\n     * PEMToHex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PEM to Hex\";\n        this.module = \"Default\";\n        this.description = \"Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Privacy-Enhanced_Mail#Format\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                \"pattern\": \"----BEGIN ([A-Z][A-Z ]+[A-Z])-----\",\n                \"args\": []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const output = [];\n        let match;\n        const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g;\n        while ((match = regex.exec(input)) !== null) {\n            // find corresponding end tag\n            const indexBase64 = match.index + match[0].length;\n            const footer = `-----END ${match[1]}-----`;\n            const indexFooter = input.indexOf(footer, indexBase64);\n            if (indexFooter === -1) {\n                throw new OperationError(`PEM footer '${footer}' not found`);\n            }\n\n            // decode base64 content\n            const base64 = input.substring(indexBase64, indexFooter);\n            const bytes = fromBase64(base64, \"A-Za-z0-9+/=\", \"byteArray\", true);\n            const hex = toHexFast(bytes);\n            output.push(hex);\n        }\n        return output.join(\"\\n\");\n    }\n\n}\n\nexport default PEMToHex;\n"
  },
  {
    "path": "src/core/operations/PEMToJWK.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * PEM to JWK operation\n */\nclass PEMToJWK extends Operation {\n\n    /**\n     * PEMToJWK constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PEM to JWK\";\n        this.module = \"PublicKey\";\n        this.description = \"Converts Keys in PEM format to a JSON Web Key format.\";\n        this.infoURL = \"https://datatracker.ietf.org/doc/html/rfc7517\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                \"pattern\": \"-----BEGIN ((RSA |EC )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-----\",\n                \"args\": []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let output = \"\";\n        let match;\n        const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g;\n        while ((match = regex.exec(input)) !== null) {\n            // find corresponding end tag\n            const indexBase64 = match.index + match[0].length;\n            const header = input.substring(match.index, indexBase64);\n            const footer = `-----END ${match[1]}-----`;\n            const indexFooter = input.indexOf(footer, indexBase64);\n            if (indexFooter === -1) {\n                throw new OperationError(`PEM footer '${footer}' not found`);\n            }\n\n            const pem = input.substring(match.index, indexFooter + footer.length);\n            if (match[1].indexOf(\"KEY\") !== -1) {\n                if (header === \"-----BEGIN RSA PUBLIC KEY-----\") {\n                    throw new OperationError(\"Unsupported RSA public key format. Only PKCS#8 is supported.\");\n                }\n\n                const key = r.KEYUTIL.getKey(pem);\n                if (key.type === \"DSA\") {\n                    throw new OperationError(\"DSA keys are not supported for JWK\");\n                }\n                const jwk = r.KEYUTIL.getJWKFromKey(key);\n                if (output.length > 0) {\n                    output += \"\\n\";\n                }\n                output += JSON.stringify(jwk);\n            } else if (match[1] === \"CERTIFICATE\") {\n                const cert = new r.X509();\n                cert.readCertPEM(pem);\n                const key = cert.getPublicKey();\n                const jwk = r.KEYUTIL.getJWKFromKey(key);\n                if (output.length > 0) {\n                    output += \"\\n\";\n                }\n                output += JSON.stringify(jwk);\n            } else {\n                throw new OperationError(`Unsupported PEM type '${match[1]}'`);\n            }\n        }\n        return output;\n    }\n}\n\nexport default PEMToJWK;\n"
  },
  {
    "path": "src/core/operations/PGPDecrypt.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport kbpgp from \"kbpgp\";\nimport { ASP, importPrivateKey } from \"../lib/PGP.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport * as es6promisify from \"es6-promisify\";\nconst promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;\n\n/**\n * PGP Decrypt operation\n */\nclass PGPDecrypt extends Operation {\n\n    /**\n     * PGPDecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PGP Decrypt\";\n        this.module = \"PGP\";\n        this.description = [\n            \"Input: the ASCII-armoured PGP message you want to decrypt.\",\n            \"<br><br>\",\n            \"Arguments: the ASCII-armoured PGP private key of the recipient, \",\n            \"(and the private key password if necessary).\",\n            \"<br><br>\",\n            \"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\",\n            \"<br><br>\",\n            \"This function uses the Keybase implementation of PGP.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Pretty_Good_Privacy\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Private key of recipient\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Private key passphrase\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if invalid private key\n     */\n    async run(input, args) {\n        const encryptedMessage = input,\n            [privateKey, passphrase] = args,\n            keyring = new kbpgp.keyring.KeyRing();\n        let plaintextMessage;\n\n        if (!privateKey) throw new OperationError(\"Enter the private key of the recipient.\");\n\n        const key = await importPrivateKey(privateKey, passphrase);\n        keyring.add_key_manager(key);\n\n        try {\n            plaintextMessage = await promisify(kbpgp.unbox)({\n                armored: encryptedMessage,\n                keyfetch: keyring,\n                asp: ASP\n            });\n        } catch (err) {\n            throw new OperationError(`Couldn't decrypt message with provided private key: ${err}`);\n        }\n\n        return plaintextMessage.toString();\n    }\n\n}\n\nexport default PGPDecrypt;\n"
  },
  {
    "path": "src/core/operations/PGPDecryptAndVerify.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport kbpgp from \"kbpgp\";\nimport { ASP, importPrivateKey, importPublicKey } from \"../lib/PGP.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport * as es6promisify from \"es6-promisify\";\nconst promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;\n\n/**\n * PGP Decrypt and Verify operation\n */\nclass PGPDecryptAndVerify extends Operation {\n\n    /**\n     * PGPDecryptAndVerify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PGP Decrypt and Verify\";\n        this.module = \"PGP\";\n        this.description = [\n            \"Input: the ASCII-armoured encrypted PGP message you want to verify.\",\n            \"<br><br>\",\n            \"Arguments: the ASCII-armoured PGP public key of the signer, \",\n            \"the ASCII-armoured private key of the recipient (and the private key password if necessary).\",\n            \"<br><br>\",\n            \"This operation uses PGP to decrypt and verify an encrypted digital signature.\",\n            \"<br><br>\",\n            \"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\",\n            \"<br><br>\",\n            \"This function uses the Keybase implementation of PGP.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Pretty_Good_Privacy\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Public key of signer\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Private key of recipient\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Private key password\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const signedMessage = input,\n            [publicKey, privateKey, passphrase] = args,\n            keyring = new kbpgp.keyring.KeyRing();\n        let unboxedLiterals;\n\n        if (!publicKey) throw new OperationError(\"Enter the public key of the signer.\");\n        if (!privateKey) throw new OperationError(\"Enter the private key of the recipient.\");\n        const privKey = await importPrivateKey(privateKey, passphrase);\n        const pubKey = await importPublicKey(publicKey);\n        keyring.add_key_manager(privKey);\n        keyring.add_key_manager(pubKey);\n\n        try {\n            unboxedLiterals = await promisify(kbpgp.unbox)({\n                armored: signedMessage,\n                keyfetch: keyring,\n                asp: ASP\n            });\n            const ds = unboxedLiterals[0].get_data_signer();\n            if (ds) {\n                const km = ds.get_key_manager();\n                if (km) {\n                    const signer = km.get_userids_mark_primary()[0].components;\n                    let text = \"Signed by \";\n                    if (signer.email || signer.username || signer.comment) {\n                        if (signer.username) {\n                            text += `${signer.username} `;\n                        }\n                        if (signer.comment) {\n                            text += `(${signer.comment}) `;\n                        }\n                        if (signer.email) {\n                            text += `<${signer.email}>`;\n                        }\n                        text += \"\\n\";\n                    }\n                    text += [\n                        `PGP key ID: ${km.get_pgp_short_key_id()}`,\n                        `PGP fingerprint: ${km.get_pgp_fingerprint().toString(\"hex\")}`,\n                        `Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`,\n                        \"----------------------------------\\n\"\n                    ].join(\"\\n\");\n                    text += unboxedLiterals.toString();\n                    return text.trim();\n                } else {\n                    throw new OperationError(\"Could not identify a key manager.\");\n                }\n            } else {\n                throw new OperationError(\"The data does not appear to be signed.\");\n            }\n        } catch (err) {\n            throw new OperationError(`Couldn't verify message: ${err}`);\n        }\n    }\n\n}\n\nexport default PGPDecryptAndVerify;\n"
  },
  {
    "path": "src/core/operations/PGPEncrypt.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport kbpgp from \"kbpgp\";\nimport { ASP, importPublicKey } from \"../lib/PGP.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport * as es6promisify from \"es6-promisify\";\nconst promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;\n\n/**\n * PGP Encrypt operation\n */\nclass PGPEncrypt extends Operation {\n\n    /**\n     * PGPEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PGP Encrypt\";\n        this.module = \"PGP\";\n        this.description = [\n            \"Input: the message you want to encrypt.\",\n            \"<br><br>\",\n            \"Arguments: the ASCII-armoured PGP public key of the recipient.\",\n            \"<br><br>\",\n            \"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\",\n            \"<br><br>\",\n            \"This function uses the Keybase implementation of PGP.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Pretty_Good_Privacy\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Public key of recipient\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if failed private key import or failed encryption\n     */\n    async run(input, args) {\n        const plaintextMessage = input,\n            plainPubKey = args[0];\n        let encryptedMessage;\n\n        if (!plainPubKey) throw new OperationError(\"Enter the public key of the recipient.\");\n\n        const key = await importPublicKey(plainPubKey);\n\n        try {\n            encryptedMessage = await promisify(kbpgp.box)({\n                \"msg\": plaintextMessage,\n                \"encrypt_for\": key,\n                \"asp\": ASP\n            });\n        } catch (err) {\n            throw new OperationError(`Couldn't encrypt message with provided public key: ${err}`);\n        }\n\n        return encryptedMessage.toString();\n    }\n\n}\n\nexport default PGPEncrypt;\n"
  },
  {
    "path": "src/core/operations/PGPEncryptAndSign.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport kbpgp from \"kbpgp\";\nimport { ASP, importPrivateKey, importPublicKey } from \"../lib/PGP.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport * as es6promisify from \"es6-promisify\";\nconst promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;\n\n/**\n * PGP Encrypt and Sign operation\n */\nclass PGPEncryptAndSign extends Operation {\n\n    /**\n     * PGPEncryptAndSign constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PGP Encrypt and Sign\";\n        this.module = \"PGP\";\n        this.description = [\n            \"Input: the cleartext you want to sign.\",\n            \"<br><br>\",\n            \"Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)\",\n            \"and the ASCII-armoured PGP public key of the recipient.\",\n            \"<br><br>\",\n            \"This operation uses PGP to produce an encrypted digital signature.\",\n            \"<br><br>\",\n            \"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\",\n            \"<br><br>\",\n            \"This function uses the Keybase implementation of PGP.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Pretty_Good_Privacy\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Private key of signer\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Private key passphrase\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Public key of recipient\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if failure to sign message\n     */\n    async run(input, args) {\n        const message = input,\n            [privateKey, passphrase, publicKey] = args;\n        let signedMessage;\n\n        if (!privateKey) throw new OperationError(\"Enter the private key of the signer.\");\n        if (!publicKey) throw new OperationError(\"Enter the public key of the recipient.\");\n        const privKey = await importPrivateKey(privateKey, passphrase);\n        const pubKey = await importPublicKey(publicKey);\n\n        try {\n            signedMessage = await promisify(kbpgp.box)({\n                \"msg\": message,\n                \"encrypt_for\": pubKey,\n                \"sign_with\": privKey,\n                \"asp\": ASP\n            });\n        } catch (err) {\n            throw new OperationError(`Couldn't sign message: ${err}`);\n        }\n\n        return signedMessage;\n    }\n\n}\n\nexport default PGPEncryptAndSign;\n"
  },
  {
    "path": "src/core/operations/PGPVerify.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\nimport kbpgp from \"kbpgp\";\nimport { ASP, importPublicKey } from \"../lib/PGP.mjs\";\nimport * as es6promisify from \"es6-promisify\";\nconst promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;\n\n/**\n * PGP Verify operation\n */\nclass PGPVerify extends Operation {\n\n    /**\n     * PGPVerify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PGP Verify\";\n        this.module = \"PGP\";\n        this.description = [\n            \"Input: the ASCII-armoured encrypted PGP message you want to verify.\",\n            \"<br><br>\",\n            \"Argument: the ASCII-armoured PGP public key of the signer\",\n            \"<br><br>\",\n            \"This operation uses PGP to decrypt a clearsigned message.\",\n            \"<br><br>\",\n            \"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\",\n            \"<br><br>\",\n            \"This function uses the Keybase implementation of PGP.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Pretty_Good_Privacy\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Public key of signer\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const signedMessage = input,\n            [publicKey] = args,\n            keyring = new kbpgp.keyring.KeyRing();\n        let unboxedLiterals;\n\n        if (!publicKey) throw new OperationError(\"Enter the public key of the signer.\");\n        const pubKey = await importPublicKey(publicKey);\n        keyring.add_key_manager(pubKey);\n\n        try {\n            unboxedLiterals = await promisify(kbpgp.unbox)({\n                armored: signedMessage,\n                keyfetch: keyring,\n                asp: ASP\n            });\n            const ds = unboxedLiterals[0].get_data_signer();\n            if (ds) {\n                const km = ds.get_key_manager();\n                if (km) {\n                    const signer = km.get_userids_mark_primary()[0].components;\n                    let text = \"Signed by \";\n                    if (signer.email || signer.username || signer.comment) {\n                        if (signer.username) {\n                            text += `${signer.username} `;\n                        }\n                        if (signer.comment) {\n                            text += `(${signer.comment}) `;\n                        }\n                        if (signer.email) {\n                            text += `<${signer.email}>`;\n                        }\n                        text += \"\\n\";\n                    }\n                    text += [\n                        `PGP key ID: ${km.get_pgp_short_key_id()}`,\n                        `PGP fingerprint: ${km.get_pgp_fingerprint().toString(\"hex\")}`,\n                        `Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`,\n                        \"----------------------------------\\n\"\n                    ].join(\"\\n\");\n                    text += unboxedLiterals.toString();\n                    return text.trim();\n                } else {\n                    throw new OperationError(\"Could not identify a key manager.\");\n                }\n            } else {\n                throw new OperationError(\"The data does not appear to be signed.\");\n            }\n        } catch (err) {\n            throw new OperationError(`Couldn't verify message: ${err}`);\n        }\n    }\n\n}\n\nexport default PGPVerify;\n"
  },
  {
    "path": "src/core/operations/PHPDeserialize.mjs",
    "content": "/**\n * @author Jarmo van Lenthe [github.com/jarmovanlenthe]\n * @copyright Jarmo van Lenthe\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * PHP Deserialize operation\n */\nclass PHPDeserialize extends Operation {\n\n    /**\n     * PHPDeserialize constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PHP Deserialize\";\n        this.module = \"Default\";\n        this.description = \"Deserializes PHP serialized data, outputting keyed arrays as JSON.<br><br>This function does not support <code>object</code> tags.<br><br>Example:<br><code>a:2:{s:1:&quot;a&quot;;i:10;i:0;a:1:{s:2:&quot;ab&quot;;b:1;}}</code><br>becomes<br><code>{&quot;a&quot;: 10,0: {&quot;ab&quot;: true}}</code><br><br><u>Output valid JSON:</u> JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes.\";\n        this.infoURL = \"http://www.phpinternalsbook.com/classes_objects/serialization.html\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Output valid JSON\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        /**\n         * Recursive method for deserializing.\n         * @returns {*}\n         */\n        function handleInput() {\n            /**\n             * Read `length` characters from the input, shifting them out the input.\n             * @param length\n             * @returns {string}\n             */\n            function read(length) {\n                let result = \"\";\n                for (let idx = 0; idx < length; idx++) {\n                    const char = inputPart.shift();\n                    if (char === undefined) {\n                        throw new OperationError(\"End of input reached before end of script\");\n                    }\n                    result += char;\n                }\n                return result;\n            }\n\n            /**\n             * Read characters from the input until `until` is found.\n             * @param until\n             * @returns {string}\n             */\n            function readUntil(until) {\n                let result = \"\";\n                for (;;) {\n                    const char = read(1);\n                    if (char === until) {\n                        break;\n                    } else {\n                        result += char;\n                    }\n                }\n                return result;\n\n            }\n\n            /**\n             * Read characters from the input that must be equal to `expect`\n             * @param expect\n             * @returns {string}\n             */\n            function expect(expect) {\n                const result = read(expect.length);\n                if (result !== expect) {\n                    throw new OperationError(\"Unexpected input found\");\n                }\n                return result;\n            }\n\n            /**\n             * Helper function to handle deserialized arrays.\n             * @returns {Array}\n             */\n            function handleArray() {\n                const items = parseInt(readUntil(\":\"), 10) * 2;\n                expect(\"{\");\n                const result = [];\n                let isKey = true;\n                let lastItem = null;\n                for (let idx = 0; idx < items; idx++) {\n                    const item = handleInput();\n                    if (isKey) {\n                        lastItem = item;\n                        isKey = false;\n                    } else {\n                        const numberCheck = lastItem.match(/[0-9]+/);\n                        if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) {\n                            result.push('\"' + lastItem + '\": ' + item);\n                        } else {\n                            result.push(lastItem + \": \" + item);\n                        }\n                        isKey = true;\n                    }\n                }\n                expect(\"}\");\n                return result;\n            }\n\n\n            const kind = read(1).toLowerCase();\n\n            switch (kind) {\n                case \"n\":\n                    expect(\";\");\n                    return \"null\";\n                case \"i\":\n                case \"d\":\n                case \"b\": {\n                    expect(\":\");\n                    const data = readUntil(\";\");\n                    if (kind === \"b\") {\n                        return (parseInt(data, 10) !== 0);\n                    }\n                    return data;\n                }\n\n                case \"a\":\n                    expect(\":\");\n                    return \"{\" + handleArray() + \"}\";\n\n                case \"s\": {\n                    expect(\":\");\n                    const length = readUntil(\":\");\n                    expect(\"\\\"\");\n                    const value = read(length);\n                    expect('\";');\n                    if (args[0]) {\n                        return '\"' + value.replace(/\"/g, '\\\\\"') + '\"'; // lgtm [js/incomplete-sanitization]\n                    } else {\n                        return '\"' + value + '\"';\n                    }\n                }\n\n                default:\n                    throw new OperationError(\"Unknown type: \" + kind);\n            }\n        }\n\n        const inputPart = input.split(\"\");\n        return handleInput();\n    }\n\n}\n\nexport default PHPDeserialize;\n"
  },
  {
    "path": "src/core/operations/PHPSerialize.mjs",
    "content": "/**\n * @author brun0ne [brunonblok@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * PHP Serialize operation\n */\nclass PHPSerialize extends Operation {\n\n    /**\n     * PHPSerialize constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"PHP Serialize\";\n        this.module = \"Default\";\n        this.description = \"Performs PHP serialization on JSON data.<br><br>This function does not support <code>object</code> tags.<br><br>Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to <code>PHP Deserialize</code>.<br><br>Example:<br><code>[5,&quot;abc&quot;,true]</code><br>becomes<br><code>a:3:{i:0;i:5;i:1;s:3:&quot;abc&quot;;i:2;b:1;}<code>\";\n        this.infoURL = \"https://www.phpinternalsbook.com/php5/classes_objects/serialization.html\";\n        this.inputType = \"JSON\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        /**\n         * Determines if a number is an integer\n         * @param {number} value\n         * @returns {boolean}\n         */\n        function isInteger(value) {\n            return typeof value === \"number\" && parseInt(value.toString(), 10) === value;\n        }\n\n        /**\n         * Serialize basic types\n         * @param {string | number | boolean} content\n         * @returns {string}\n         */\n        function serializeBasicTypes(content) {\n            const basicTypes = {\n                \"string\": \"s\",\n                \"integer\": \"i\",\n                \"float\": \"d\",\n                \"boolean\": \"b\"\n            };\n            /**\n             * Booleans\n             * cast to 0 or 1\n             */\n            if (typeof content === \"boolean\") {\n                return `${basicTypes.boolean}:${content ? 1 : 0}`;\n            }\n            /* Numbers */\n            if (typeof content === \"number\") {\n                if (isInteger(content)) {\n                    return `${basicTypes.integer}:${content.toString()}`;\n                } else {\n                    return `${basicTypes.float}:${content.toString()}`;\n                }\n            }\n            /* Strings */\n            if (typeof content === \"string\")\n                return `${basicTypes.string}:${content.length}:\"${content}\"`;\n\n            /** This should be unreachable */\n            throw new OperationError(`Encountered a non-implemented type: ${typeof content}`);\n        }\n\n        /**\n         * Recursively serialize\n         * @param {*} object\n         * @returns {string}\n         */\n        function serialize(object) {\n            /* Null */\n            if (object == null) {\n                return `N;`;\n            }\n\n            if (typeof object !== \"object\") {\n                /* Basic types */\n                return `${serializeBasicTypes(object)};`;\n            } else if (object instanceof Array) {\n                /* Arrays */\n                const serializedElements = [];\n\n                for (let i = 0; i < object.length; i++) {\n                    serializedElements.push(`${serialize(i)}${serialize(object[i])}`);\n                }\n\n                return `a:${object.length}:{${serializedElements.join(\"\")}}`;\n            } else if (object instanceof Object) {\n                /**\n                 * Objects\n                 * Note: the output cannot be guaranteed to be in the same order as the input\n                 */\n                const serializedElements = [];\n                const keys = Object.keys(object);\n\n                for (const key of keys) {\n                    serializedElements.push(`${serialize(key)}${serialize(object[key])}`);\n                }\n\n                return `a:${keys.length}:{${serializedElements.join(\"\")}}`;\n            }\n\n            /** This should be unreachable */\n            throw new OperationError(`Encountered a non-implemented type: ${typeof object}`);\n        }\n\n        return serialize(input);\n    }\n}\n\nexport default PHPSerialize;\n"
  },
  {
    "path": "src/core/operations/PLISTViewer.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * P-list Viewer operation\n */\nclass PlistViewer extends Operation {\n\n    /**\n     * PlistViewer constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"P-list Viewer\";\n        this.module = \"Default\";\n        this.description = \"In the macOS, iOS, NeXTSTEP, and GNUstep programming frameworks, property list files are files that store serialized objects. Property list files use the filename extension .plist, and thus are often referred to as p-list files.<br><br>This operation displays plist files in a human readable format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Property_list\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n\n        // Regexes are designed to transform the xml format into a more readable string format.\n        input = input.slice(input.indexOf(\"<plist\"))\n            .replace(/<plist.+>/g, \"plist => \")\n            .replace(/<dict>/g, \"{\")\n            .replace(/<\\/dict>/g, \"}\")\n            .replace(/<array>/g, \"[\")\n            .replace(/<\\/array>/g, \"]\")\n            .replace(/<key>.+<\\/key>/g, m => `${m.slice(5, m.indexOf(/<\\/key>/g)-5)}\\t=> `)\n            .replace(/<real>.+<\\/real>/g, m => `${m.slice(6, m.indexOf(/<\\/real>/g)-6)}\\n`)\n            .replace(/<string>.+<\\/string>/g, m => `\"${m.slice(8, m.indexOf(/<\\/string>/g)-8)}\"\\n`)\n            .replace(/<integer>.+<\\/integer>/g, m => `${m.slice(9, m.indexOf(/<\\/integer>/g)-9)}\\n`)\n            .replace(/<false\\/>/g, m => \"false\")\n            .replace(/<true\\/>/g, m => \"true\")\n            .replace(/<\\/plist>/g, \"/plist\")\n            .replace(/<date>.+<\\/date>/g, m => `${m.slice(6, m.indexOf(/<\\/integer>/g)-6)}`)\n            .replace(/<data>[\\s\\S]+?<\\/data>/g, m => `${m.slice(6, m.indexOf(/<\\/data>/g)-6)}`)\n            .replace(/[ \\t\\r\\f\\v]/g, \"\");\n\n        /**\n         * Depending on the type of brace, it will increment the depth and amount of arrays accordingly.\n         *\n         * @param {string} elem\n         * @param {array} vals\n         * @param {number} offset\n         */\n        function braces(elem, vals, offset) {\n            const temp = vals.indexOf(elem);\n            if (temp !== -1) {\n                depthCount += offset;\n                if (temp === 1)\n                    arrCount += offset;\n            }\n        }\n\n        let result = \"\";\n        let arrCount = 0;\n        let depthCount = 0;\n\n        /**\n         * Formats the input after the regex has replaced all of the relevant parts.\n         *\n         * @param {array} input\n         * @param {number} index\n         */\n        function printIt(input, index) {\n            if (!(input.length))\n                return;\n\n            let temp = \"\";\n            const origArr = arrCount;\n            let currElem = input[0];\n\n            // If the current position points at a larger dynamic structure.\n            if (currElem.indexOf(\"=>\") !== -1) {\n\n                // If the LHS also points at a larger structure (nested plists in a dictionary).\n                if (input[1].indexOf(\"=>\") !== -1)\n                    temp = currElem.slice(0, -2) + \" => \" + input[1].slice(0, -2) + \" =>\\n\";\n                else\n                    temp = currElem.slice(0, -2) + \" => \" + input[1] + \"\\n\";\n\n                input = input.slice(1);\n            } else {\n                // Controls the tab depth for how many closing braces there have been.\n\n                braces(currElem, [\"}\", \"]\"], -1);\n\n                // Has to be here since the formatting breaks otherwise.\n                temp = currElem + \"\\n\";\n            }\n\n            currElem = input[0];\n\n            // Tab out to the correct distance.\n            result += (\"\\t\".repeat(depthCount));\n\n            // If it is enclosed in an array show index.\n            if (arrCount > 0 && currElem !== \"]\")\n                result += index.toString() + \" => \";\n\n            result += temp;\n\n            // Controls the tab depth for how many opening braces there have been.\n            braces(currElem, [\"{\", \"[\"], 1);\n\n            // If there has been a new array then reset index.\n            if (arrCount > origArr)\n                return printIt(input.slice(1), 0);\n            return printIt(input.slice(1), ++index);\n        }\n\n        input = input.split(\"\\n\").filter(e => e !== \"\");\n        printIt(input, 0);\n        return result;\n    }\n}\n\nexport default PlistViewer;\n"
  },
  {
    "path": "src/core/operations/PadLines.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Pad lines operation\n */\nclass PadLines extends Operation {\n\n    /**\n     * PadLines constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Pad lines\";\n        this.module = \"Default\";\n        this.description = \"Add the specified number of the specified character to the beginning or end of each line\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Position\",\n                \"type\": \"option\",\n                \"value\": [\"Start\", \"End\"]\n            },\n            {\n                \"name\": \"Length\",\n                \"type\": \"number\",\n                \"value\": 5\n            },\n            {\n                \"name\": \"Character\",\n                \"type\": \"binaryShortString\",\n                \"value\": \" \"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [position, len, chr] = args,\n            lines = input.split(\"\\n\");\n        let output = \"\",\n            i = 0;\n\n        if (position === \"Start\") {\n            for (i = 0; i < lines.length; i++) {\n                output += lines[i].padStart(lines[i].length+len, chr) + \"\\n\";\n            }\n        } else if (position === \"End\") {\n            for (i = 0; i < lines.length; i++) {\n                output += lines[i].padEnd(lines[i].length+len, chr) + \"\\n\";\n            }\n        }\n\n        return output.slice(0, output.length-1);\n    }\n\n}\n\nexport default PadLines;\n"
  },
  {
    "path": "src/core/operations/ParseASN1HexString.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Parse ASN.1 hex string operation\n */\nclass ParseASN1HexString extends Operation {\n\n    /**\n     * ParseASN1HexString constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse ASN.1 hex string\";\n        this.module = \"PublicKey\";\n        this.description = \"Abstract Syntax Notation One (ASN.1) is a standard and notation that describes rules and structures for representing, encoding, transmitting, and decoding data in telecommunications and computer networking.<br><br>This operation parses arbitrary ASN.1 data (encoded as an hex string: use the 'To Hex' operation if necessary) and presents the resulting tree.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Abstract_Syntax_Notation_One\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Starting index\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Truncate octet strings longer than\",\n                \"type\": \"number\",\n                \"value\": 32\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [index, truncateLen] = args;\n        return r.ASN1HEX.dump(input.replace(/\\s/g, \"\").toLowerCase(), {\n            \"ommit_long_octet\": truncateLen\n        }, index);\n    }\n\n}\n\nexport default ParseASN1HexString;\n"
  },
  {
    "path": "src/core/operations/ParseCSR.mjs",
    "content": "/**\n * @author jkataja\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\nimport { formatDnObj } from \"../lib/PublicKey.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Parse CSR operation\n */\nclass ParseCSR extends Operation {\n\n    /**\n     * ParseCSR constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse CSR\";\n        this.module = \"PublicKey\";\n        this.description = \"Parse Certificate Signing Request (CSR) for an X.509 certificate\";\n        this.infoURL = \"https://wikipedia.org/wiki/Certificate_signing_request\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"PEM\"]\n            }\n        ];\n        this.checks = [\n            {\n                \"pattern\": \"^-+BEGIN CERTIFICATE REQUEST-+\\\\r?\\\\n[\\\\da-z+/\\\\n\\\\r]+-+END CERTIFICATE REQUEST-+\\\\r?\\\\n?$\",\n                \"flags\": \"i\",\n                \"args\": [\"PEM\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string} Human-readable description of a Certificate Signing Request (CSR).\n     */\n    run(input, args) {\n        if (!input.length) {\n            return \"No input\";\n        }\n\n        // Parse the CSR into JSON parameters\n        const csrParam = new r.KJUR.asn1.csr.CSRUtil.getParam(input);\n\n        return `Subject\\n${formatDnObj(csrParam.subject, 2)}\nPublic Key${formatSubjectPublicKey(csrParam.sbjpubkey)}\nSignature${formatSignature(csrParam.sigalg, csrParam.sighex)}\nRequested Extensions${formatRequestedExtensions(csrParam)}`;\n    }\n}\n\n/**\n * Format signature of a CSR\n * @param {*} sigAlg string\n * @param {*} sigHex string\n * @returns Multi-line string describing CSR Signature\n */\nfunction formatSignature(sigAlg, sigHex) {\n    let out = `\\n`;\n\n    out += `  Algorithm:      ${sigAlg}\\n`;\n\n    if (new RegExp(\"withdsa\", \"i\").test(sigAlg)) {\n        const d = new r.KJUR.crypto.DSA();\n        const sigParam = d.parseASN1Signature(sigHex);\n        out += `  Signature:\n      R:          ${formatHexOntoMultiLine(absBigIntToHex(sigParam[0]))}\n      S:          ${formatHexOntoMultiLine(absBigIntToHex(sigParam[1]))}\\n`;\n    } else if (new RegExp(\"withrsa\", \"i\").test(sigAlg)) {\n        out += `  Signature:      ${formatHexOntoMultiLine(sigHex)}\\n`;\n    } else {\n        out += `  Signature:      ${formatHexOntoMultiLine(ensureHexIsPositiveInTwosComplement(sigHex))}\\n`;\n    }\n\n    return chop(out);\n}\n\n/**\n * Format Subject Public Key from PEM encoded public key string\n * @param {*} publicKeyPEM string\n * @returns Multi-line string describing Subject Public Key Info\n */\nfunction formatSubjectPublicKey(publicKeyPEM) {\n    let out = \"\\n\";\n\n    const publicKey = r.KEYUTIL.getKey(publicKeyPEM);\n    if (publicKey instanceof r.RSAKey) {\n        out += `  Algorithm:      RSA\n  Length:         ${publicKey.n.bitLength()} bits\n  Modulus:        ${formatHexOntoMultiLine(absBigIntToHex(publicKey.n))}\n  Exponent:       ${publicKey.e} (0x${Utils.hex(publicKey.e)})\\n`;\n    } else if (publicKey instanceof r.KJUR.crypto.ECDSA) {\n        out += `  Algorithm:      ECDSA\n  Length:         ${publicKey.ecparams.keylen} bits\n  Pub:            ${formatHexOntoMultiLine(publicKey.pubKeyHex)}\n  ASN1 OID:       ${r.KJUR.crypto.ECDSA.getName(publicKey.getShortNISTPCurveName())}\n  NIST CURVE:     ${publicKey.getShortNISTPCurveName()}\\n`;\n    } else if (publicKey instanceof r.KJUR.crypto.DSA) {\n        out += `  Algorithm:      DSA\n  Length:         ${publicKey.p.toString(16).length * 4} bits\n  Pub:            ${formatHexOntoMultiLine(absBigIntToHex(publicKey.y))}\n  P:              ${formatHexOntoMultiLine(absBigIntToHex(publicKey.p))}\n  Q:              ${formatHexOntoMultiLine(absBigIntToHex(publicKey.q))}\n  G:              ${formatHexOntoMultiLine(absBigIntToHex(publicKey.g))}\\n`;\n    } else {\n        out += `unsupported public key algorithm\\n`;\n    }\n\n    return chop(out);\n}\n\n/**\n * Format known extensions of a CSR\n * @param {*} csrParam object\n * @returns Multi-line string describing CSR Requested Extensions\n */\nfunction formatRequestedExtensions(csrParam) {\n    const formattedExtensions = new Array(4).fill(\"\");\n\n    if (Object.hasOwn(csrParam, \"extreq\")) {\n        for (const extension of csrParam.extreq) {\n            let parts = [];\n            switch (extension.extname) {\n                case \"basicConstraints\" :\n                    parts = describeBasicConstraints(extension);\n                    formattedExtensions[0] = `  Basic Constraints:${formatExtensionCriticalTag(extension)}\\n${indent(4, parts)}`;\n                    break;\n                case \"keyUsage\" :\n                    parts = describeKeyUsage(extension);\n                    formattedExtensions[1] = `  Key Usage:${formatExtensionCriticalTag(extension)}\\n${indent(4, parts)}`;\n                    break;\n                case \"extKeyUsage\" :\n                    parts = describeExtendedKeyUsage(extension);\n                    formattedExtensions[2] = `  Extended Key Usage:${formatExtensionCriticalTag(extension)}\\n${indent(4, parts)}`;\n                    break;\n                case \"subjectAltName\" :\n                    parts = describeSubjectAlternativeName(extension);\n                    formattedExtensions[3] = `  Subject Alternative Name:${formatExtensionCriticalTag(extension)}\\n${indent(4, parts)}`;\n                    break;\n                default :\n                    parts = [\"(unsuported extension)\"];\n                    formattedExtensions.push(`  ${extension.extname}:${formatExtensionCriticalTag(extension)}\\n${indent(4, parts)}`);\n            }\n        }\n    }\n\n    let out = \"\\n\";\n\n    formattedExtensions.forEach((formattedExtension) => {\n        if (formattedExtension !== undefined && formattedExtension !== null && formattedExtension.length !== 0) {\n            out += formattedExtension;\n        }\n    });\n\n    return chop(out);\n}\n\n/**\n * Format extension critical tag\n * @param {*} extension Object\n * @returns String describing whether the extension is critical or not\n */\nfunction formatExtensionCriticalTag(extension) {\n    return Object.hasOwn(extension, \"critical\") && extension.critical ? \" critical\" : \"\";\n}\n\n/**\n * Format string input as a comma separated hex string on multiple lines\n * @param {*} hex String\n * @returns Multi-line string describing the Hex input\n */\nfunction formatHexOntoMultiLine(hex) {\n    if (hex.length % 2 !== 0) {\n        hex = \"0\" + hex;\n    }\n\n    return formatMultiLine(chop(hex.replace(/(..)/g, \"$&:\")));\n}\n\n/**\n * Convert BigInt to abs value in Hex\n * @param {*} int BigInt\n * @returns String representing absolute value in Hex\n */\nfunction absBigIntToHex(int) {\n    int = int < 0n ? -int : int;\n\n    return ensureHexIsPositiveInTwosComplement(int.toString(16));\n}\n\n/**\n * Ensure Hex String remains positive in 2's complement\n * @param {*} hex String\n * @returns Hex String ensuring value remains positive in 2's complement\n */\nfunction ensureHexIsPositiveInTwosComplement(hex) {\n    if (hex.length % 2 !== 0) {\n        return \"0\" + hex;\n    }\n\n    // prepend 00 if most significant bit is 1 (sign bit)\n    if (hex.length >=2 && (parseInt(hex.substring(0, 2), 16) & 128)) {\n        hex = \"00\" + hex;\n    }\n\n    return hex;\n}\n\n/**\n * Format string onto multiple lines\n * @param {*} longStr\n * @returns String as a multi-line string\n */\nfunction formatMultiLine(longStr) {\n    const lines = [];\n\n    for (let remain = longStr ; remain !== \"\" ; remain = remain.substring(48)) {\n        lines.push(remain.substring(0, 48));\n    }\n\n    return lines.join(\"\\n                  \");\n}\n\n/**\n * Describe Basic Constraints\n * @see RFC 5280 4.2.1.9. Basic Constraints https://www.ietf.org/rfc/rfc5280.txt\n * @param {*} extension CSR extension with the name `basicConstraints`\n * @returns Array of strings describing Basic Constraints\n */\nfunction describeBasicConstraints(extension) {\n    const constraints = [];\n\n    constraints.push(`CA = ${Object.hasOwn(extension, \"cA\") && extension.cA ? \"true\" : \"false\"}`);\n    if (Object.hasOwn(extension, \"pathLen\")) constraints.push(`PathLenConstraint = ${extension.pathLen}`);\n\n    return constraints;\n}\n\n/**\n * Describe Key Usage extension permitted use cases\n * @see RFC 5280 4.2.1.3. Key Usage https://www.ietf.org/rfc/rfc5280.txt\n * @param {*} extension CSR extension with the name `keyUsage`\n * @returns Array of strings describing Key Usage extension permitted use cases\n */\nfunction describeKeyUsage(extension) {\n    const usage = [];\n\n    const kuIdentifierToName = {\n        digitalSignature: \"Digital Signature\",\n        nonRepudiation:   \"Non-repudiation\",\n        keyEncipherment:  \"Key encipherment\",\n        dataEncipherment: \"Data encipherment\",\n        keyAgreement:     \"Key agreement\",\n        keyCertSign:      \"Key certificate signing\",\n        cRLSign:          \"CRL signing\",\n        encipherOnly:     \"Encipher Only\",\n        decipherOnly:     \"Decipher Only\",\n    };\n\n    if (Object.hasOwn(extension, \"names\")) {\n        extension.names.forEach((ku) => {\n            if (Object.hasOwn(kuIdentifierToName, ku)) {\n                usage.push(kuIdentifierToName[ku]);\n            } else {\n                usage.push(`unknown key usage (${ku})`);\n            }\n        });\n    }\n\n    if (usage.length === 0) usage.push(\"(none)\");\n\n    return usage;\n}\n\n/**\n * Describe Extended Key Usage extension permitted use cases\n * @see RFC 5280 4.2.1.12. Extended Key Usage https://www.ietf.org/rfc/rfc5280.txt\n * @param {*} extension CSR extension with the name `extendedKeyUsage`\n * @returns Array of strings describing Extended Key Usage extension permitted use cases\n */\nfunction describeExtendedKeyUsage(extension) {\n    const usage = [];\n\n    const ekuIdentifierToName = {\n        \"serverAuth\":             \"TLS Web Server Authentication\",\n        \"clientAuth\":             \"TLS Web Client Authentication\",\n        \"codeSigning\":            \"Code signing\",\n        \"emailProtection\":        \"E-mail Protection (S/MIME)\",\n        \"timeStamping\":           \"Trusted Timestamping\",\n        \"1.3.6.1.4.1.311.2.1.21\": \"Microsoft Individual Code Signing\",  // msCodeInd\n        \"1.3.6.1.4.1.311.2.1.22\": \"Microsoft Commercial Code Signing\",  // msCodeCom\n        \"1.3.6.1.4.1.311.10.3.1\": \"Microsoft Trust List Signing\",  // msCTLSign\n        \"1.3.6.1.4.1.311.10.3.3\": \"Microsoft Server Gated Crypto\",  // msSGC\n        \"1.3.6.1.4.1.311.10.3.4\": \"Microsoft Encrypted File System\",  // msEFS\n        \"1.3.6.1.4.1.311.20.2.2\": \"Microsoft Smartcard Login\",  // msSmartcardLogin\n        \"2.16.840.1.113730.4.1\":  \"Netscape Server Gated Crypto\",  // nsSGC\n    };\n\n    if (Object.hasOwn(extension, \"array\")) {\n        extension.array.forEach((eku) => {\n            if (Object.hasOwn(ekuIdentifierToName, eku)) {\n                usage.push(ekuIdentifierToName[eku]);\n            } else {\n                usage.push(eku);\n            }\n        });\n    }\n\n    if (usage.length === 0) usage.push(\"(none)\");\n\n    return usage;\n}\n\n/**\n * Format Subject Alternative Names from the name `subjectAltName` extension\n * @see RFC 5280 4.2.1.6. Subject Alternative Name https://www.ietf.org/rfc/rfc5280.txt\n * @param {*} extension object\n * @returns Array of strings describing Subject Alternative Name extension\n */\nfunction describeSubjectAlternativeName(extension) {\n    const names = [];\n\n    if (Object.hasOwn(extension, \"extname\") && extension.extname === \"subjectAltName\") {\n        if (Object.hasOwn(extension, \"array\")) {\n            for (const altName of extension.array) {\n                Object.keys(altName).forEach((key) => {\n                    switch (key) {\n                        case \"rfc822\":\n                            names.push(`EMAIL: ${altName[key]}`);\n                            break;\n                        case \"dns\":\n                            names.push(`DNS: ${altName[key]}`);\n                            break;\n                        case \"uri\":\n                            names.push(`URI: ${altName[key]}`);\n                            break;\n                        case \"ip\":\n                            names.push(`IP: ${altName[key]}`);\n                            break;\n                        case \"dn\":\n                            names.push(`DIR: ${altName[key].str}`);\n                            break;\n                        case \"other\" :\n                            names.push(`Other: ${altName[key].oid}::${altName[key].value.utf8str.str}`);\n                            break;\n                        default:\n                            names.push(`(unable to format SAN '${key}':${altName[key]})\\n`);\n                    }\n                });\n            }\n        }\n    }\n\n    return names;\n}\n\n/**\n * Join an array of strings and add leading spaces to each line.\n * @param {*} n How many leading spaces\n * @param {*} parts Array of strings\n * @returns Joined and indented string.\n */\nfunction indent(n, parts) {\n    const fluff = \" \".repeat(n);\n    return fluff + parts.join(\"\\n\" + fluff) + \"\\n\";\n}\n\n/**\n * Remove last character from a string.\n * @param {*} s String\n * @returns Chopped string.\n */\nfunction chop(s) {\n    return s.substring(0, s.length - 1);\n}\n\nexport default ParseCSR;\n"
  },
  {
    "path": "src/core/operations/ParseColourCode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Parse colour code operation\n */\nclass ParseColourCode extends Operation {\n\n    /**\n     * ParseColourCode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse colour code\";\n        this.module = \"Default\";\n        this.description = \"Converts a colour code in a standard format to other standard formats and displays the colour itself.<br><br><strong>Example inputs</strong><ul><li><code>#d9edf7</code></li><li><code>rgba(217,237,247,1)</code></li><li><code>hsla(200,65%,91%,1)</code></li><li><code>cmyk(0.12, 0.04, 0.00, 0.03)</code></li></ul>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Web_colors\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        let m = null,\n            r = 0, g = 0, b = 0, a = 1;\n\n        // Read in the input\n        if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) {\n            // Hex - #d9edf7\n            r = parseInt(m[1], 16);\n            g = parseInt(m[2], 16);\n            b = parseInt(m[3], 16);\n        } else if ((m = input.match(/rgba?\\((\\d{1,3}(?:\\.\\d+)?),\\s?(\\d{1,3}(?:\\.\\d+)?),\\s?(\\d{1,3}(?:\\.\\d+)?)(?:,\\s?(\\d(?:\\.\\d+)?))?\\)/i))) {\n            // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1)\n            r = parseFloat(m[1]);\n            g = parseFloat(m[2]);\n            b = parseFloat(m[3]);\n            a = m[4] ? parseFloat(m[4]) : 1;\n        } else if ((m = input.match(/hsla?\\((\\d{1,3}(?:\\.\\d+)?),\\s?(\\d{1,3}(?:\\.\\d+)?)%,\\s?(\\d{1,3}(?:\\.\\d+)?)%(?:,\\s?(\\d(?:\\.\\d+)?))?\\)/i))) {\n            // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1)\n            const h_ = parseFloat(m[1]) / 360,\n                s_ = parseFloat(m[2]) / 100,\n                l_ = parseFloat(m[3]) / 100,\n                rgb_ = ParseColourCode._hslToRgb(h_, s_, l_);\n\n            r = rgb_[0];\n            g = rgb_[1];\n            b = rgb_[2];\n            a = m[4] ? parseFloat(m[4]) : 1;\n        } else if ((m = input.match(/cmyk\\((\\d(?:\\.\\d+)?),\\s?(\\d(?:\\.\\d+)?),\\s?(\\d(?:\\.\\d+)?),\\s?(\\d(?:\\.\\d+)?)\\)/i))) {\n            // CMYK - cmyk(0.12, 0.04, 0.00, 0.03)\n            const c_ = parseFloat(m[1]),\n                m_ = parseFloat(m[2]),\n                y_ = parseFloat(m[3]),\n                k_ = parseFloat(m[4]);\n\n            r = Math.round(255 * (1 - c_) * (1 - k_));\n            g = Math.round(255 * (1 - m_) * (1 - k_));\n            b = Math.round(255 * (1 - y_) * (1 - k_));\n        }\n\n        const hsl_ = ParseColourCode._rgbToHsl(r, g, b),\n            h = Math.round(hsl_[0] * 360),\n            s = Math.round(hsl_[1] * 100),\n            l = Math.round(hsl_[2] * 100);\n        let k = 1 - Math.max(r/255, g/255, b/255),\n            c = (1 - r/255 - k) / (1 - k),\n            y = (1 - b/255 - k) / (1 - k);\n\n        m = (1 - g/255 - k) / (1 - k);\n\n        c = isNaN(c) ? \"0\" : c.toFixed(2);\n        m = isNaN(m) ? \"0\" : m.toFixed(2);\n        y = isNaN(y) ? \"0\" : y.toFixed(2);\n        k = k.toFixed(2);\n\n        const hex = \"#\" +\n                Math.round(r).toString(16).padStart(2, \"0\") +\n                Math.round(g).toString(16).padStart(2, \"0\") +\n                Math.round(b).toString(16).padStart(2, \"0\"),\n            rgb  = \"rgb(\" + r + \", \" + g + \", \" + b + \")\",\n            rgba = \"rgba(\" + r + \", \" + g + \", \" + b + \", \" + a + \")\",\n            hsl  = \"hsl(\" + h + \", \" + s + \"%, \" + l + \"%)\",\n            hsla = \"hsla(\" + h + \", \" + s + \"%, \" + l + \"%, \" + a + \")\",\n            cmyk = \"cmyk(\" + c + \", \" + m + \", \" + y + \", \" + k + \")\";\n\n        // Generate output\n        return `<div id=\"colorpicker\" style=\"white-space: normal;\"></div>\nHex:  ${hex}\nRGB:  ${rgb}\nRGBA: ${rgba}\nHSL:  ${hsl}\nHSLA: ${hsla}\nCMYK: ${cmyk}\n<script>\n    $('#colorpicker').colorpicker({\n        format: 'rgba',\n        color: '${rgba}',\n        container: true,\n        inline: true,\n        useAlpha: true\n    }).on('colorpickerChange', function(e) {\n        var color = e.color.string('rgba');\n        window.app.manager.input.setInput(color);\n        window.app.manager.input.inputChange(new Event(\"keyup\"));\n    });\n</script>`;\n    }\n\n    /**\n     * Converts an HSL color value to RGB. Conversion formula\n     * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace.\n     * Assumes h, s, and l are contained in the set [0, 1] and\n     * returns r, g, and b in the set [0, 255].\n     *\n     * @author Mohsen (http://stackoverflow.com/a/9493060)\n     *\n     * @param {number} h - The hue\n     * @param {number} s - The saturation\n     * @param {number} l - The lightness\n     * @return {Array} The RGB representation\n     */\n    static _hslToRgb(h, s, l) {\n        let r, g, b;\n\n        if (s === 0) {\n            r = g = b = l; // achromatic\n        } else {\n            const hue2rgb = function hue2rgb(p, q, t) {\n                if (t < 0) t += 1;\n                if (t > 1) t -= 1;\n                if (t < 1/6) return p + (q - p) * 6 * t;\n                if (t < 1/2) return q;\n                if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;\n                return p;\n            };\n\n            const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n            const p = 2 * l - q;\n            r = hue2rgb(p, q, h + 1/3);\n            g = hue2rgb(p, q, h);\n            b = hue2rgb(p, q, h - 1/3);\n        }\n\n        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];\n    }\n\n    /**\n     * Converts an RGB color value to HSL. Conversion formula\n     * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace.\n     * Assumes r, g, and b are contained in the set [0, 255] and\n     * returns h, s, and l in the set [0, 1].\n     *\n     * @author Mohsen (http://stackoverflow.com/a/9493060)\n     *\n     * @param {number} r - The red color value\n     * @param {number} g - The green color value\n     * @param {number} b - The blue color value\n     * @return {Array} The HSL representation\n     */\n    static _rgbToHsl(r, g, b) {\n        r /= 255; g /= 255; b /= 255;\n        const max = Math.max(r, g, b),\n            min = Math.min(r, g, b),\n            l = (max + min) / 2;\n        let h, s;\n\n        if (max === min) {\n            h = s = 0; // achromatic\n        } else {\n            const d = max - min;\n            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n            switch (max) {\n                case r: h = (g - b) / d + (g < b ? 6 : 0); break;\n                case g: h = (b - r) / d + 2; break;\n                case b: h = (r - g) / d + 4; break;\n            }\n            h /= 6;\n        }\n\n        return [h, s, l];\n    }\n}\n\nexport default ParseColourCode;\n"
  },
  {
    "path": "src/core/operations/ParseDateTime.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport moment from \"moment-timezone\";\nimport {DATETIME_FORMATS, FORMAT_EXAMPLES} from \"../lib/DateTime.mjs\";\n\n/**\n * Parse DateTime operation\n */\nclass ParseDateTime extends Operation {\n\n    /**\n     * ParseDateTime constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse DateTime\";\n        this.module = \"Default\";\n        this.description = \"Parses a DateTime string in your specified format and displays it in whichever timezone you choose with the following information:<ul><li>Date</li><li>Time</li><li>Period (AM/PM)</li><li>Timezone</li><li>UTC offset</li><li>Daylight Saving Time</li><li>Leap year</li><li>Days in this month</li><li>Day of year</li><li>Week number</li><li>Quarter</li></ul>Run with no input to see format string examples if required.\";\n        this.infoURL = \"https://momentjs.com/docs/#/parsing/string-format/\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Built in formats\",\n                \"type\": \"populateOption\",\n                \"value\": DATETIME_FORMATS,\n                \"target\": 1\n            },\n            {\n                \"name\": \"Input format string\",\n                \"type\": \"binaryString\",\n                \"value\": \"DD/MM/YYYY HH:mm:ss\"\n            },\n            {\n                \"name\": \"Input timezone\",\n                \"type\": \"option\",\n                \"value\": [\"UTC\"].concat(moment.tz.names())\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const inputFormat = args[1],\n            inputTimezone = args[2];\n        let date,\n            output = \"\";\n\n        try {\n            date = moment.tz(input, inputFormat, inputTimezone);\n            if (!date || date.format() === \"Invalid date\") throw Error;\n        } catch (err) {\n            return `Invalid format.\\n\\n${FORMAT_EXAMPLES}`;\n        }\n\n        output += \"Date: \" + date.format(\"dddd Do MMMM YYYY\") +\n            \"\\nTime: \" + date.format(\"HH:mm:ss\") +\n            \"\\nPeriod: \" + date.format(\"A\") +\n            \"\\nTimezone: \" + date.format(\"z\") +\n            \"\\nUTC offset: \" + date.format(\"ZZ\") +\n            \"\\n\\nDaylight Saving Time: \" + date.isDST() +\n            \"\\nLeap year: \" + date.isLeapYear() +\n            \"\\nDays in this month: \" + date.daysInMonth() +\n            \"\\n\\nDay of year: \" + date.dayOfYear() +\n            \"\\nWeek number: \" + date.week() +\n            \"\\nQuarter: \" + date.quarter();\n\n        return output;\n    }\n\n}\n\nexport default ParseDateTime;\n"
  },
  {
    "path": "src/core/operations/ParseIPRange.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author Klaxon [klaxon@veyr.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {ipv4CidrRange, ipv4HyphenatedRange, ipv4ListedRange, ipv6CidrRange, ipv6HyphenatedRange, ipv6ListedRange} from \"../lib/IP.mjs\";\n\n/**\n * Parse IP range operation\n */\nclass ParseIPRange extends Operation {\n\n    /**\n     * ParseIPRange constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse IP range\";\n        this.module = \"Default\";\n        this.description = \"Given a CIDR range (e.g. <code>10.0.0.0/24</code>), hyphenated range (e.g. <code>10.0.0.0 - 10.0.1.0</code>), or a list of IPs and/or CIDR ranges (separated by a new line), this operation provides network information and enumerates all IP addresses in the range.<br><br>IPv6 is supported but will not be enumerated.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Subnetwork\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Include network info\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Enumerate IP addresses\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Allow large queries\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [\n            includeNetworkInfo,\n            enumerateAddresses,\n            allowLargeList\n        ] = args;\n\n        // Check what type of input we are looking at\n        const ipv4CidrRegex = /^\\s*((?:\\d{1,3}\\.){3}\\d{1,3})\\/(\\d\\d?)\\s*$/,\n            ipv4RangeRegex = /^\\s*((?:\\d{1,3}\\.){3}\\d{1,3})\\s*-\\s*((?:\\d{1,3}\\.){3}\\d{1,3})\\s*$/,\n            ipv4ListRegex = /^\\s*(((?:\\d{1,3}\\.){3}\\d{1,3})(\\/(\\d\\d?))?(\\n|$)(\\n*))+\\s*$/,\n            ipv6CidrRegex = /^\\s*(((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\4)::|:\\b|(?![\\dA-F])))|(?!\\3\\4)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4}))\\/(\\d\\d?\\d?)\\s*$/i,\n            ipv6RangeRegex = /^\\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\4)::|:\\b|(?![\\dA-F])))|(?!\\3\\4)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4}))\\s*-\\s*(((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\17)::|:\\b|(?![\\dA-F])))|(?!\\16\\17)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4}))\\s*$/i,\n            ipv6ListRegex = /^\\s*((((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\4)::|:\\b|(?![\\dA-F])))|(?!\\3\\4)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4}))(\\/(\\d\\d?\\d?))?(\\n|$)(\\n*))+\\s*$/i;\n        let match;\n\n        if ((match = ipv4CidrRegex.exec(input))) {\n            return ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList);\n        } else if ((match = ipv4RangeRegex.exec(input))) {\n            return ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList);\n        } else if ((match = ipv4ListRegex.exec(input))) {\n            return ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList);\n        } else if ((match = ipv6CidrRegex.exec(input))) {\n            return ipv6CidrRange(match, includeNetworkInfo);\n        } else if ((match = ipv6RangeRegex.exec(input))) {\n            return ipv6HyphenatedRange(match, includeNetworkInfo);\n        } else if ((match = ipv6ListRegex.exec(input))) {\n            return ipv6ListedRange(match, includeNetworkInfo);\n        } else {\n            throw new OperationError(\"Invalid input.\\n\\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported.\");\n        }\n    }\n\n}\n\n\nexport default ParseIPRange;\n"
  },
  {
    "path": "src/core/operations/ParseIPv4Header.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {fromHex, toHex} from \"../lib/Hex.mjs\";\nimport {ipv4ToStr, protocolLookup} from \"../lib/IP.mjs\";\nimport TCPIPChecksum from \"./TCPIPChecksum.mjs\";\n\n/**\n * Parse IPv4 header operation\n */\nclass ParseIPv4Header extends Operation {\n\n    /**\n     * ParseIPv4Header constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse IPv4 header\";\n        this.module = \"Default\";\n        this.description = \"Given an IPv4 header, this operations parses and displays each field in an easily readable format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/IPv4#Header\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const format = args[0];\n        let output;\n\n        if (format === \"Hex\") {\n            input = fromHex(input);\n        } else if (format === \"Raw\") {\n            input = new Uint8Array(Utils.strToArrayBuffer(input));\n        } else {\n            throw new OperationError(\"Unrecognised input format.\");\n        }\n\n        let ihl = input[0] & 0x0f;\n        const dscp = (input[1] >>> 2) & 0x3f,\n            ecn = input[1] & 0x03,\n            length = input[2] << 8 | input[3],\n            identification = input[4] << 8 | input[5],\n            flags = (input[6] >>> 5) & 0x07,\n            fragOffset = (input[6] & 0x1f) << 8 | input[7],\n            ttl = input[8],\n            protocol = input[9],\n            checksum = input[10] << 8 | input[11],\n            srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15],\n            dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19],\n            checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20));\n        let version = (input[0] >>> 4) & 0x0f,\n            options = [];\n\n\n        // Version\n        if (version !== 4) {\n            version = version + \" (Error: for IPv4 headers, this should always be set to 4)\";\n        }\n\n        // IHL\n        if (ihl < 5) {\n            ihl = ihl + \" (Error: this should always be at least 5)\";\n        } else if (ihl > 5) {\n            // sort out options...\n            const optionsLen = ihl * 4 - 20;\n            options = input.slice(20, optionsLen + 20);\n        }\n\n        // Protocol\n        const protocolInfo = protocolLookup[protocol] || {keyword: \"\", protocol: \"\"};\n\n        // Checksum\n        const correctChecksum = (new TCPIPChecksum).run(checksumHeader),\n            givenChecksum = Utils.hex(checksum);\n        let checksumResult;\n        if (correctChecksum === givenChecksum) {\n            checksumResult = givenChecksum + \" (correct)\";\n        } else {\n            checksumResult = givenChecksum + \" (incorrect, should be \" + correctChecksum + \")\";\n        }\n\n        output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>\n<tr><td>Version</td><td>${version}</td></tr>\n<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>\n<tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr>\n<tr><td>Explicit Congestion Notification (ECN)</td><td>${ecn}</td></tr>\n<tr><td>Total length</td><td>${length} bytes\n  IP header: ${ihl * 4} bytes\n  Data: ${length - ihl * 4} bytes</td></tr>\n<tr><td>Identification</td><td>0x${Utils.hex(identification)} (${identification})</td></tr>\n<tr><td>Flags</td><td>0x${Utils.hex(flags, 2)}\n  Reserved bit:${flags >> 2} (must be 0)\n  Don't fragment:${flags >> 1 & 1}\n  More fragments:${flags & 1}</td></tr>\n<tr><td>Fragment offset</td><td>${fragOffset}</td></tr>\n<tr><td>Time-To-Live</td><td>${ttl}</td></tr>\n<tr><td>Protocol</td><td>${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})</td></tr>\n<tr><td>Header checksum</td><td>${checksumResult}</td></tr>\n<tr><td>Source IP address</td><td>${ipv4ToStr(srcIP)}</td></tr>\n<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>`;\n\n        if (ihl > 5) {\n            output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;\n        }\n\n        return output + \"</table>\";\n    }\n\n}\n\nexport default ParseIPv4Header;\n"
  },
  {
    "path": "src/core/operations/ParseIPv6Address.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {strToIpv6, ipv6ToStr, ipv4ToStr, IPV6_REGEX} from \"../lib/IP.mjs\";\nimport BigNumber from \"bignumber.js\";\n\n/**\n * Parse IPv6 address operation\n */\nclass ParseIPv6Address extends Operation {\n\n    /**\n     * ParseIPv6Address constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse IPv6 address\";\n        this.module = \"Default\";\n        this.description = \"Displays the longhand and shorthand versions of a valid IPv6 address.<br><br>Recognises all reserved ranges and parses encapsulated or tunnelled addresses including Teredo and 6to4.\";\n        this.infoURL = \"https://wikipedia.org/wiki/IPv6_address\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let match,\n            output = \"\";\n\n        if ((match = IPV6_REGEX.exec(input))) {\n            const ipv6 = strToIpv6(match[1]),\n                longhand = ipv6ToStr(ipv6),\n                shorthand = ipv6ToStr(ipv6, true);\n\n            output += \"Longhand:  \" + longhand + \"\\nShorthand: \" + shorthand + \"\\n\";\n\n            // Detect reserved addresses\n            if (shorthand === \"::\") {\n                // Unspecified address\n                output += \"\\nUnspecified address corresponding to 0.0.0.0/32 in IPv4.\";\n                output += \"\\nUnspecified address range: ::/128\";\n            } else if (shorthand === \"::1\") {\n                // Loopback address\n                output += \"\\nLoopback address to the local host corresponding to 127.0.0.1/8 in IPv4.\";\n                output += \"\\nLoopback addresses range: ::1/128\";\n            } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 &&\n                ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) {\n                // IPv4-mapped IPv6 address\n                output += \"\\nIPv4-mapped IPv6 address detected. IPv6 clients will be handled natively by default, and IPv4 clients appear as IPv6 clients at their IPv4-mapped IPv6 address.\";\n                output += \"\\nMapped IPv4 address: \" + ipv4ToStr((ipv6[6] << 16) + ipv6[7]);\n                output += \"\\nIPv4-mapped IPv6 addresses range: ::ffff:0:0/96\";\n            } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 &&\n                ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) {\n                // IPv4-translated address\n                output += \"\\nIPv4-translated address detected. Used by Stateless IP/ICMP Translation (SIIT). See RFCs 6145 and 6052 for more details.\";\n                output += \"\\nTranslated IPv4 address: \" + ipv4ToStr((ipv6[6] << 16) + ipv6[7]);\n                output += \"\\nIPv4-translated addresses range: ::ffff:0:0:0/96\";\n            } else if (ipv6[0] === 0x100) {\n                // Discard prefix per RFC 6666\n                output += \"\\nDiscard prefix detected. This is used when forwarding traffic to a sinkhole router to mitigate the effects of a denial-of-service attack. See RFC 6666 for more details.\";\n                output += \"\\nDiscard range: 100::/64\";\n            } else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 &&\n                ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) {\n                // IPv4/IPv6 translation per RFC 6052\n                output += \"\\n'Well-Known' prefix for IPv4/IPv6 translation detected. See RFC 6052 for more details.\";\n                output += \"\\nTranslated IPv4 address: \" + ipv4ToStr((ipv6[6] << 16) + ipv6[7]);\n                output += \"\\n'Well-Known' prefix range: 64:ff9b::/96\";\n            } else if (ipv6[0] === 0x2001 && ipv6[1] === 0) {\n                // Teredo tunneling\n                output += \"\\nTeredo tunneling IPv6 address detected\\n\";\n                const serverIpv4  = (ipv6[2] << 16) + ipv6[3],\n                    udpPort     = (~ipv6[5]) & 0xffff,\n                    clientIpv4  = ~((ipv6[6] << 16) + ipv6[7]),\n                    flagCone    = (ipv6[4] >>> 15) & 1,\n                    flagR       = (ipv6[4] >>> 14) & 1,\n                    flagRandom1 = (ipv6[4] >>> 10) & 15,\n                    flagUg      = (ipv6[4] >>> 8) & 3,\n                    flagRandom2 = ipv6[4] & 255;\n\n                output += \"\\nServer IPv4 address: \" + ipv4ToStr(serverIpv4) +\n                    \"\\nClient IPv4 address: \" + ipv4ToStr(clientIpv4) +\n                    \"\\nClient UDP port:     \" + udpPort +\n                    \"\\nFlags:\" +\n                    \"\\n\\tCone:    \" + flagCone;\n\n                if (flagCone) {\n                    output += \" (Client is behind a cone NAT)\";\n                } else {\n                    output += \" (Client is not behind a cone NAT)\";\n                }\n\n                output += \"\\n\\tR:       \" + flagR;\n\n                if (flagR) {\n                    output += \" Error: This flag should be set to 0. See RFC 5991 and RFC 4380.\";\n                }\n\n                output += \"\\n\\tRandom1: \" + Utils.bin(flagRandom1, 4) +\n                    \"\\n\\tUG:      \" + Utils.bin(flagUg, 2);\n\n                if (flagUg) {\n                    output += \" Error: This flag should be set to 00. See RFC 4380.\";\n                }\n\n                output += \"\\n\\tRandom2: \" + Utils.bin(flagRandom2, 8);\n\n                if (!flagR && !flagUg && flagRandom1 && flagRandom2) {\n                    output += \"\\n\\nThis is a valid Teredo address which complies with RFC 4380 and RFC 5991.\";\n                } else if (!flagR && !flagUg) {\n                    output += \"\\n\\nThis is a valid Teredo address which complies with RFC 4380, however it does not comply with RFC 5991 (Teredo Security Updates) as there are no randomised bits in the flag field.\";\n                } else {\n                    output += \"\\n\\nThis is an invalid Teredo address.\";\n                }\n                output += \"\\n\\nTeredo prefix range: 2001::/32\";\n            } else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) {\n                // Benchmarking\n                output += \"\\nAssigned to the Benchmarking Methodology Working Group (BMWG) for benchmarking IPv6. Corresponds to 198.18.0.0/15 for benchmarking IPv4. See RFC 5180 for more details.\";\n                output += \"\\nBMWG range: 2001:2::/48\";\n            } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) {\n                // ORCHIDv1\n                output += \"\\nDeprecated, previously ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers).\\nORCHIDv1 range: 2001:10::/28\\nORCHIDv2 now uses 2001:20::/28.\";\n            } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) {\n                // ORCHIDv2\n                output += \"\\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers).\\nThese are non-routed IPv6 addresses used for Cryptographic Hash Identifiers.\";\n                output += \"\\nORCHIDv2 range: 2001:20::/28\";\n            } else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) {\n                // Documentation\n                output += \"\\nThis is a documentation IPv6 address. This range should be used whenever an example IPv6 address is given or to model networking scenarios. Corresponds to 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4.\";\n                output += \"\\nDocumentation range: 2001:db8::/32\";\n            } else if (ipv6[0] === 0x2002) {\n                // 6to4\n                output += \"\\n6to4 transition IPv6 address detected. See RFC 3056 for more details.\" +\n                    \"\\n6to4 prefix range: 2002::/16\";\n\n                const v4Addr = ipv4ToStr((ipv6[1] << 16) + ipv6[2]),\n                    slaId = ipv6[3],\n                    interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16),\n                    interfaceId = new BigNumber(interfaceIdStr, 16);\n\n                output += \"\\n\\nEncapsulated IPv4 address: \" + v4Addr +\n                    \"\\nSLA ID: \" + slaId +\n                    \"\\nInterface ID (base 16): \" + interfaceIdStr +\n                    \"\\nInterface ID (base 10): \" + interfaceId.toString();\n            } else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) {\n                // Unique local address\n                output += \"\\nThis is a unique local address comparable to the IPv4 private addresses 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. See RFC 4193 for more details.\";\n                output += \"\\nUnique local addresses range: fc00::/7\";\n            } else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) {\n                // Link-local address\n                output += \"\\nThis is a link-local address comparable to the auto-configuration addresses 169.254.0.0/16 in IPv4.\";\n                output += \"\\nLink-local addresses range: fe80::/10\";\n            } else if (ipv6[0] >= 0xff00) {\n                // Multicast\n                output += \"\\nThis is a reserved multicast address.\";\n                output += \"\\nMulticast addresses range: ff00::/8\";\n\n                switch (ipv6[0]) {\n                    case 0xff01:\n                        output += \"\\n\\nReserved Multicast Block for Interface Local Scope\";\n                        break;\n                    case 0xff02:\n                        output += \"\\n\\nReserved Multicast Block for Link Local Scope\";\n                        break;\n                    case 0xff03:\n                        output += \"\\n\\nReserved Multicast Block for Realm Local Scope\";\n                        break;\n                    case 0xff04:\n                        output += \"\\n\\nReserved Multicast Block for Admin Local Scope\";\n                        break;\n                    case 0xff05:\n                        output += \"\\n\\nReserved Multicast Block for Site Local Scope\";\n                        break;\n                    case 0xff08:\n                        output += \"\\n\\nReserved Multicast Block for Organisation Local Scope\";\n                        break;\n                    case 0xff0e:\n                        output += \"\\n\\nReserved Multicast Block for Global Scope\";\n                        break;\n                }\n\n                if (ipv6[6] === 1) {\n                    if (ipv6[7] === 2) {\n                        output += \"\\nReserved Multicast Address for 'All DHCP Servers and Relay Agents (defined in RFC3315)'\";\n                    } else if (ipv6[7] === 3) {\n                        output += \"\\nReserved Multicast Address for 'All LLMNR Hosts (defined in RFC4795)'\";\n                    }\n                } else {\n                    switch (ipv6[7]) {\n                        case 1:\n                            output += \"\\nReserved Multicast Address for 'All nodes'\";\n                            break;\n                        case 2:\n                            output += \"\\nReserved Multicast Address for 'All routers'\";\n                            break;\n                        case 5:\n                            output += \"\\nReserved Multicast Address for 'OSPFv3 - All OSPF routers'\";\n                            break;\n                        case 6:\n                            output += \"\\nReserved Multicast Address for 'OSPFv3 - All Designated Routers'\";\n                            break;\n                        case 8:\n                            output += \"\\nReserved Multicast Address for 'IS-IS for IPv6 Routers'\";\n                            break;\n                        case 9:\n                            output += \"\\nReserved Multicast Address for 'RIP Routers'\";\n                            break;\n                        case 0xa:\n                            output += \"\\nReserved Multicast Address for 'EIGRP Routers'\";\n                            break;\n                        case 0xc:\n                            output += \"\\nReserved Multicast Address for 'Simple Service Discovery Protocol'\";\n                            break;\n                        case 0xd:\n                            output += \"\\nReserved Multicast Address for 'PIM Routers'\";\n                            break;\n                        case 0x16:\n                            output += \"\\nReserved Multicast Address for 'MLDv2 Reports (defined in RFC3810)'\";\n                            break;\n                        case 0x6b:\n                            output += \"\\nReserved Multicast Address for 'Precision Time Protocol v2 Peer Delay Measurement Messages'\";\n                            break;\n                        case 0xfb:\n                            output += \"\\nReserved Multicast Address for 'Multicast DNS'\";\n                            break;\n                        case 0x101:\n                            output += \"\\nReserved Multicast Address for 'Network Time Protocol'\";\n                            break;\n                        case 0x108:\n                            output += \"\\nReserved Multicast Address for 'Network Information Service'\";\n                            break;\n                        case 0x114:\n                            output += \"\\nReserved Multicast Address for 'Experiments'\";\n                            break;\n                        case 0x181:\n                            output += \"\\nReserved Multicast Address for 'Precision Time Protocol v2 Messages (exc. Peer Delay)'\";\n                            break;\n                    }\n                }\n            }\n\n\n            // Detect possible EUI-64 addresses\n            if (((ipv6[5] & 0xff) === 0xff) && (ipv6[6] >>> 8 === 0xfe)) {\n                output += \"\\n\\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets.\";\n\n                const intIdent = Utils.hex(ipv6[4] >>> 8) + \":\" + Utils.hex(ipv6[4] & 0xff) + \":\" +\n                    Utils.hex(ipv6[5] >>> 8) + \":\" + Utils.hex(ipv6[5] & 0xff) + \":\" +\n                    Utils.hex(ipv6[6] >>> 8) + \":\" + Utils.hex(ipv6[6] & 0xff) + \":\" +\n                    Utils.hex(ipv6[7] >>> 8) + \":\" + Utils.hex(ipv6[7] & 0xff),\n                    mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + \":\" + Utils.hex(ipv6[4] & 0xff) + \":\" +\n                    Utils.hex(ipv6[5] >>> 8) + \":\" + Utils.hex(ipv6[6] & 0xff) + \":\" +\n                    Utils.hex(ipv6[7] >>> 8) + \":\" + Utils.hex(ipv6[7] & 0xff);\n                output += \"\\nInterface identifier: \" + intIdent +\n                    \"\\nMAC address:          \" + mac;\n            }\n        } else {\n            throw new OperationError(\"Invalid IPv6 address\");\n        }\n        return output;\n    }\n\n}\n\nexport default ParseIPv6Address;\n"
  },
  {
    "path": "src/core/operations/ParseObjectIDTimestamp.mjs",
    "content": "/**\n * @author dmfj [dominic@dmfj.io]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport BSON from \"bson\";\n\n/**\n * Parse ObjectID timestamp operation\n */\nclass ParseObjectIDTimestamp extends Operation {\n\n    /**\n     * ParseObjectIDTimestamp constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse ObjectID timestamp\";\n        this.module = \"Serialise\";\n        this.description = \"Parse timestamp from MongoDB/BSON ObjectID hex string.\";\n        this.infoURL = \"https://docs.mongodb.com/manual/reference/method/ObjectId.getTimestamp/\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        try {\n            const objectId = new BSON.ObjectID(input);\n            return objectId.getTimestamp().toISOString();\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default ParseObjectIDTimestamp;\n"
  },
  {
    "path": "src/core/operations/ParseQRCode.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { parseQrCode } from \"../lib/QRCode.mjs\";\n\n/**\n * Parse QR Code operation\n */\nclass ParseQRCode extends Operation {\n    /**\n     * ParseQRCode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse QR Code\";\n        this.module = \"Image\";\n        this.description =\n            \"Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.<br><br><u>Normalise Image</u><br>Attempts to normalise the image before parsing it to improve detection of a QR code.\";\n        this.infoURL = \"https://wikipedia.org/wiki/QR_code\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Normalise image\",\n                type: \"boolean\",\n                value: false,\n            },\n        ];\n        this.checks = [\n            {\n                pattern:\n                    \"^(?:\\\\xff\\\\xd8\\\\xff|\\\\x89\\\\x50\\\\x4e\\\\x47|\\\\x47\\\\x49\\\\x46|.{8}\\\\x57\\\\x45\\\\x42\\\\x50|\\\\x42\\\\x4d)\",\n                flags: \"\",\n                args: [false],\n                useful: true,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const [normalise] = args;\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n        return parseQrCode(input, normalise);\n    }\n}\n\nexport default ParseQRCode;\n"
  },
  {
    "path": "src/core/operations/ParseSSHHostKey.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { fromBase64 } from \"../lib/Base64.mjs\";\nimport { fromHex, toHexFast } from \"../lib/Hex.mjs\";\n\n/**\n * Parse SSH Host Key operation\n */\nclass ParseSSHHostKey extends Operation {\n\n    /**\n     * ParseSSHHostKey constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse SSH Host Key\";\n        this.module = \"Default\";\n        this.description = \"Parses a SSH host key and extracts fields from it.<br>The key type can be:<ul><li>ssh-rsa</li><li>ssh-dss</li><li>ecdsa-sha2</li><li>ssh-ed25519</li></ul>The key format can be either Hex or Base64.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Secure_Shell\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Input Format\",\n                type: \"option\",\n                value: [\n                    \"Auto\",\n                    \"Base64\",\n                    \"Hex\"\n                ]\n            }\n        ];\n        this.checks = [\n            {\n                pattern:  \"^\\\\s*([A-F\\\\d]{2}[,;:]){15,}[A-F\\\\d]{2}\\\\s*$\",\n                flags:  \"i\",\n                args:   [\"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat] = args,\n            inputKey = this.convertKeyToBinary(input.trim(), inputFormat),\n            fields = this.parseKey(inputKey),\n            keyType = Utils.byteArrayToChars(fromHex(fields[0]), \"\");\n\n        let output = `Key type: ${keyType}`;\n\n        if (keyType === \"ssh-rsa\") {\n            output += `\\nExponent: 0x${fields[1]}`;\n            output += `\\nModulus: 0x${fields[2]}`;\n        } else if (keyType === \"ssh-dss\") {\n            output += `\\np: 0x${fields[1]}`;\n            output += `\\nq: 0x${fields[2]}`;\n            output += `\\ng: 0x${fields[3]}`;\n            output += `\\ny: 0x${fields[4]}`;\n        } else if (keyType.startsWith(\"ecdsa-sha2\")) {\n            output += `\\nCurve: ${Utils.byteArrayToChars(fromHex(fields[1]))}`;\n            output += `\\nPoint: 0x${fields.slice(2)}`;\n        } else if (keyType === \"ssh-ed25519\") {\n            output += `\\nx: 0x${fields[1]}`;\n        } else {\n            output += \"\\nUnsupported key type.\";\n            output += `\\nParameters: ${fields.slice(1)}`;\n        }\n\n        return output;\n    }\n\n    /**\n     * Converts the key to binary format from either hex or base64\n     *\n     * @param {string} inputKey\n     * @param {string} inputFormat\n     * @returns {byteArray}\n     */\n    convertKeyToBinary(inputKey, inputFormat) {\n        const keyPattern = new RegExp(/^(?:ssh|ecdsa-sha2)\\S+\\s+(\\S*)/),\n            keyMatch = inputKey.match(keyPattern);\n\n        if (keyMatch) {\n            inputKey = keyMatch[1];\n        }\n\n        if (inputFormat === \"Auto\") {\n            inputFormat = this.detectKeyFormat(inputKey);\n        }\n        if (inputFormat === \"Hex\") {\n            return fromHex(inputKey);\n        } else if (inputFormat === \"Base64\") {\n            return fromBase64(inputKey, null, \"byteArray\");\n        } else {\n            throw new OperationError(\"Invalid input format.\");\n        }\n    }\n\n\n    /**\n     * Detects if the key is base64 or hex encoded\n     *\n     * @param {string} inputKey\n     * @returns {string}\n     */\n    detectKeyFormat(inputKey) {\n        const hexPattern = new RegExp(/^(?:[\\dA-Fa-f]{2}[ ,;:]?)+$/);\n        const b64Pattern = new RegExp(/^\\s*(?:[A-Za-z\\d+/]{4})+(?:[A-Za-z\\d+/]{2}==|[A-Za-z\\d+/]{3}=)?\\s*$/);\n\n        if (hexPattern.test(inputKey)) {\n            return \"Hex\";\n        } else if (b64Pattern.test(inputKey)) {\n            return \"Base64\";\n        } else {\n            throw new OperationError(\"Unable to detect input key format.\");\n        }\n    }\n\n\n    /**\n     * Parses fields from the key\n     *\n     * @param {byteArray} key\n     */\n    parseKey(key) {\n        const fields = [];\n        while (key.length > 0) {\n            const lengthField = key.slice(0, 4);\n            let decodedLength = 0;\n            for (let i = 0; i < lengthField.length; i++) {\n                decodedLength += lengthField[i];\n                decodedLength = decodedLength << 8;\n            }\n            decodedLength = decodedLength >> 8;\n            // Break if length wasn't decoded correctly\n            if (decodedLength <= 0) break;\n\n            fields.push(toHexFast(key.slice(4, 4 + decodedLength)));\n            key = key.slice(4 + decodedLength);\n        }\n\n        return fields;\n    }\n\n}\n\nexport default ParseSSHHostKey;\n"
  },
  {
    "path": "src/core/operations/ParseTCP.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport {toHexFast, fromHex} from \"../lib/Hex.mjs\";\nimport {toBinary} from \"../lib/Binary.mjs\";\nimport {objToTable, bytesToLargeNumber} from \"../lib/Protocol.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport BigNumber from \"bignumber.js\";\n\n/**\n * Parse TCP operation\n */\nclass ParseTCP extends Operation {\n\n    /**\n     * ParseTCP constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse TCP\";\n        this.module = \"Default\";\n        this.description = \"Parses a TCP header and payload (if present).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Transmission_Control_Protocol\";\n        this.inputType = \"string\";\n        this.outputType = \"json\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const format = args[0];\n\n        if (format === \"Hex\") {\n            input = fromHex(input);\n        } else if (format === \"Raw\") {\n            input = Utils.strToArrayBuffer(input);\n        } else {\n            throw new OperationError(\"Unrecognised input format.\");\n        }\n\n        const s = new Stream(new Uint8Array(input));\n        if (s.length < 20) {\n            throw new OperationError(\"Need at least 20 bytes for a TCP Header\");\n        }\n\n        // Parse Header\n        const TCPPacket = {\n            \"Source port\": s.readInt(2),\n            \"Destination port\": s.readInt(2),\n            \"Sequence number\": bytesToLargeNumber(s.getBytes(4)),\n            \"Acknowledgement number\": s.readInt(4),\n            \"Data offset\": s.readBits(4),\n            \"Flags\": {\n                \"Reserved\": toBinary(s.readBits(3), \"\", 3),\n                \"NS\": s.readBits(1),\n                \"CWR\": s.readBits(1),\n                \"ECE\": s.readBits(1),\n                \"URG\": s.readBits(1),\n                \"ACK\": s.readBits(1),\n                \"PSH\": s.readBits(1),\n                \"RST\": s.readBits(1),\n                \"SYN\": s.readBits(1),\n                \"FIN\": s.readBits(1),\n            },\n            \"Window size\": s.readInt(2),\n            \"Checksum\": \"0x\" + toHexFast(s.getBytes(2)),\n            \"Urgent pointer\": \"0x\" + toHexFast(s.getBytes(2))\n        };\n\n        // Parse options if present\n        let windowScaleShift = 0;\n        if (TCPPacket[\"Data offset\"] > 5) {\n            let remainingLength = TCPPacket[\"Data offset\"] * 4 - 20;\n\n            const options = {};\n            while (remainingLength > 0) {\n                const option = {\n                    \"Kind\": s.readInt(1)\n                };\n\n                let opt = { name: \"Reserved\", length: true };\n                if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) {\n                    opt = TCP_OPTION_KIND_LOOKUP[option.Kind];\n                }\n\n                // Add Length and Value fields\n                if (opt.length) {\n                    option.Length = s.readInt(1);\n\n                    if (option.Length > 2) {\n                        if (Object.prototype.hasOwnProperty.call(opt, \"parser\")) {\n                            option.Value = opt.parser(s.getBytes(option.Length - 2));\n                        } else {\n                            option.Value = option.Length <= 6 ?\n                                s.readInt(option.Length - 2):\n                                \"0x\" + toHexFast(s.getBytes(option.Length - 2));\n                        }\n\n                        // Store Window Scale shift for later\n                        if (option.Kind === 3 && option.Value) {\n                            windowScaleShift = option.Value[\"Shift count\"];\n                        }\n                    }\n                }\n                options[opt.name] = option;\n\n                const length = option.Length || 1;\n                remainingLength -= length;\n            }\n            TCPPacket.Options = options;\n        }\n\n        if (s.hasMore()) {\n            TCPPacket.Data = \"0x\" + toHexFast(s.getBytes());\n        }\n\n        // Improve values\n        TCPPacket[\"Data offset\"] = `${TCPPacket[\"Data offset\"]} (${TCPPacket[\"Data offset\"] * 4} bytes)`;\n        const trueWndSize = BigNumber(TCPPacket[\"Window size\"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift)));\n        TCPPacket[\"Window size\"] = `${TCPPacket[\"Window size\"]} (Scaled: ${trueWndSize})`;\n\n        return TCPPacket;\n    }\n\n    /**\n     * Displays the TCP Packet in a tabular style\n     * @param {Object} data\n     * @returns {html}\n     */\n    present(data) {\n        return objToTable(data);\n    }\n\n}\n\n// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml\n// on 2022-05-30\nconst TCP_OPTION_KIND_LOOKUP = {\n    0: { name: \"End of Option List\", length: false },\n    1: { name: \"No-Operation\", length: false },\n    2: { name: \"Maximum Segment Size\", length: true },\n    3: { name: \"Window Scale\", length: true, parser: windowScaleParser },\n    4: { name: \"SACK Permitted\", length: true },\n    5: { name: \"SACK\", length: true },\n    6: { name: \"Echo (obsoleted by option 8)\", length: true },\n    7: { name: \"Echo Reply (obsoleted by option 8)\", length: true },\n    8: { name: \"Timestamps\", length: true, parser: tcpTimestampParser },\n    9: { name: \"Partial Order Connection Permitted (obsolete)\", length: true },\n    10: { name: \"Partial Order Service Profile (obsolete)\", length: true },\n    11: { name: \"CC (obsolete)\", length: true },\n    12: { name: \"CC.NEW (obsolete)\", length: true },\n    13: { name: \"CC.ECHO (obsolete)\", length: true },\n    14: { name: \"TCP Alternate Checksum Request (obsolete)\", length: true, parser: tcpAlternateChecksumParser },\n    15: { name: \"TCP Alternate Checksum Data (obsolete)\", length: true },\n    16: { name: \"Skeeter\", length: true },\n    17: { name: \"Bubba\", length: true },\n    18: { name: \"Trailer Checksum Option\", length: true },\n    19: { name: \"MD5 Signature Option (obsoleted by option 29)\", length: true },\n    20: { name: \"SCPS Capabilities\", length: true },\n    21: { name: \"Selective Negative Acknowledgements\", length: true },\n    22: { name: \"Record Boundaries\", length: true },\n    23: { name: \"Corruption experienced\", length: true },\n    24: { name: \"SNAP\", length: true },\n    25: { name: \"Unassigned (released 2000-12-18)\", length: true },\n    26: { name: \"TCP Compression Filter\", length: true },\n    27: { name: \"Quick-Start Response\", length: true },\n    28: { name: \"User Timeout Option (also, other known unauthorized use)\", length: true },\n    29: { name: \"TCP Authentication Option (TCP-AO)\", length: true },\n    30: { name: \"Multipath TCP (MPTCP)\", length: true },\n    69: { name: \"Encryption Negotiation (TCP-ENO)\", length: true },\n    70: { name: \"Reserved (known unauthorized use without proper IANA assignment)\", length: true },\n    76: { name: \"Reserved (known unauthorized use without proper IANA assignment)\", length: true },\n    77: { name: \"Reserved (known unauthorized use without proper IANA assignment)\", length: true },\n    78: { name: \"Reserved (known unauthorized use without proper IANA assignment)\", length: true },\n    253: { name: \"RFC3692-style Experiment 1 (also improperly used for shipping products) \", length: true },\n    254: { name: \"RFC3692-style Experiment 2 (also improperly used for shipping products) \", length: true }\n};\n\n/**\n * Parses the TCP Alternate Checksum Request field\n * @param {Uint8Array} data\n */\nfunction tcpAlternateChecksumParser(data) {\n    const lookup = {\n        0: \"TCP Checksum\",\n        1: \"8-bit Fletchers's algorithm\",\n        2: \"16-bit Fletchers's algorithm\",\n        3: \"Redundant Checksum Avoidance\"\n    }[data[0]];\n\n    return `${lookup} (0x${toHexFast(data)})`;\n}\n\n/**\n * Parses the TCP Timestamp field\n * @param {Uint8Array} data\n */\nfunction tcpTimestampParser(data) {\n    const s = new Stream(data);\n\n    if (s.length !== 8)\n        return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`;\n\n    const tsval = bytesToLargeNumber(s.getBytes(4)),\n        tsecr = bytesToLargeNumber(s.getBytes(4));\n\n    return {\n        \"Current Timestamp\": tsval,\n        \"Echo Reply\": tsecr\n    };\n}\n\n/**\n * Parses the Window Scale field\n * @param {Uint8Array} data\n */\nfunction windowScaleParser(data) {\n    if (data.length !== 1)\n        return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`;\n\n    return {\n        \"Shift count\": data[0],\n        \"Multiplier\": 1 << data[0]\n    };\n}\n\nexport default ParseTCP;\n"
  },
  {
    "path": "src/core/operations/ParseTLSRecord.mjs",
    "content": "/**\n * @author c65722 []\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {toHexFast} from \"../lib/Hex.mjs\";\nimport {objToTable} from \"../lib/Protocol.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\n\n/**\n * Parse TLS record operation.\n */\nclass ParseTLSRecord extends Operation {\n\n    /**\n     * ParseTLSRecord constructor.\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse TLS record\";\n        this.module = \"Default\";\n        this.description = \"Parses one or more TLS records\";\n        this.infoURL = \"https://wikipedia.org/wiki/Transport_Layer_Security\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"json\";\n        this.presentType = \"html\";\n        this.args = [];\n        this._handshakeParser = new HandshakeParser();\n        this._contentTypes = new Map();\n\n        for (const key in ContentType) {\n            this._contentTypes[ContentType[key]] = key.toString().toLocaleLowerCase();\n        }\n    }\n\n    /**\n     * @param {ArrayBuffer} input - Stream, containing one or more raw TLS Records.\n     * @param {Object[]} args\n     * @returns {Object[]} Array of Object representations of TLS Records contained within input.\n     */\n    run(input, args) {\n        const s = new Stream(new Uint8Array(input));\n\n        const output = [];\n\n        while (s.hasMore()) {\n            const record = this._readRecord(s);\n            if (record) {\n                output.push(record);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads a TLS Record from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw TLS Record.\n     * @returns {Object} Object representation of TLS Record.\n     */\n    _readRecord(input) {\n        const RECORD_HEADER_LEN = 5;\n\n        if (input.position + RECORD_HEADER_LEN > input.length) {\n            input.moveTo(input.length);\n\n            return null;\n        }\n\n        const type = input.readInt(1);\n        const typeString = this._contentTypes[type] ?? type.toString();\n        const version = \"0x\" + toHexFast(input.getBytes(2));\n        const length = input.readInt(2);\n        const content = input.getBytes(length);\n        const truncated = content.length < length;\n\n        const recordHeader = new RecordHeader(typeString, version, length, truncated);\n\n        if (!content.length) {\n            return {...recordHeader};\n        }\n\n        if (type === ContentType.HANDSHAKE) {\n            return this._handshakeParser.parse(new Stream(content), recordHeader);\n        }\n\n        const record = {...recordHeader};\n        record.value = \"0x\" + toHexFast(content);\n\n        return record;\n    }\n\n    /**\n     * Displays the parsed TLS Records in a tabular style.\n     *\n     * @param {Object[]} data - Array of Object representations of the TLS Records.\n     * @returns {html} HTML representation of TLS Records contained within data.\n     */\n    present(data) {\n        return data.map(r => objToTable(r)).join(\"\\n\\n\");\n    }\n}\n\nexport default ParseTLSRecord;\n\n/**\n * Repesents the known values of type field of a TLS Record header.\n */\nconst ContentType = Object.freeze({\n    CHANGE_CIPHER_SPEC: 20,\n    ALERT: 21,\n    HANDSHAKE: 22,\n    APPLICATION_DATA: 23,\n});\n\n/**\n * Represents a TLS Record header\n */\nclass RecordHeader {\n    /**\n     * RecordHeader cosntructor.\n     *\n     * @param {string} type - String representation of TLS Record type field.\n     * @param {string} version - Hex representation of TLS Record version field.\n     * @param {int} length - Length of TLS Record.\n     * @param {bool} truncated - Is TLS Record truncated.\n     */\n    constructor(type, version, length, truncated) {\n        this.type = type;\n        this.version = version;\n        this.length = length;\n\n        if (truncated) {\n            this.truncated = true;\n        }\n    }\n}\n\n/**\n * Parses TLS Handshake messages.\n */\nclass HandshakeParser {\n\n    /**\n     * HandshakeParser constructor.\n     */\n    constructor() {\n        this._clientHelloParser = new ClientHelloParser();\n        this._serverHelloParser = new ServerHelloParser();\n        this._newSessionTicketParser = new NewSessionTicketParser();\n        this._certificateParser = new CertificateParser();\n        this._certificateRequestParser = new CertificateRequestParser();\n        this._certificateVerifyParser = new CertificateVerifyParser();\n        this._handshakeTypes = new Map();\n\n        for (const key in HandshakeType) {\n            this._handshakeTypes[HandshakeType[key]] = key.toString().toLowerCase();\n        }\n    }\n\n    /**\n     * Parses a single TLS handshake message.\n     *\n     * @param {Stream} input - Stream, containing a raw Handshake message.\n     * @param {RecordHeader} recordHeader - TLS Record header.\n     * @returns {Object} Object representation of Handshake.\n     */\n    parse(input, recordHeader) {\n        const output = {...recordHeader};\n\n        if (!input.hasMore()) {\n            return output;\n        }\n\n        const handshakeType = input.readInt(1);\n        output.handshakeType = this._handshakeTypes[handshakeType] ?? handshakeType.toString();\n\n        if (input.position + 3 > input.length) {\n            input.moveTo(input.length);\n\n            return output;\n        }\n\n        const handshakeLength = input.readInt(3);\n\n        if (handshakeLength + 4 !== recordHeader.length) {\n            input.moveTo(0);\n\n            output.handshakeType = this._handshakeTypes[HandshakeType.FINISHED];\n            output.handshakeValue = \"0x\" + toHexFast(input.bytes);\n\n            return output;\n        }\n\n        const content = input.getBytes(handshakeLength);\n        if (!content.length) {\n            return output;\n        }\n\n        switch (handshakeType) {\n            case HandshakeType.CLIENT_HELLO:\n                return {...output, ...this._clientHelloParser.parse(new Stream(content))};\n            case HandshakeType.SERVER_HELLO:\n                return {...output, ...this._serverHelloParser.parse(new Stream(content))};\n            case HandshakeType.NEW_SESSION_TICKET:\n                return {...output, ...this._newSessionTicketParser.parse(new Stream(content))};\n            case HandshakeType.CERTIFICATE:\n                return {...output, ...this._certificateParser.parse(new Stream(content))};\n            case HandshakeType.CERTIFICATE_REQUEST:\n                return {...output, ...this._certificateRequestParser.parse(new Stream(content))};\n            case HandshakeType.CERTIFICATE_VERIFY:\n                return {...output, ...this._certificateVerifyParser.parse(new Stream(content))};\n            default:\n                output.handshakeValue = \"0x\" + toHexFast(content);\n        }\n\n        return output;\n    }\n}\n\n/**\n * Represents the known values of the msg_type field of a TLS Handshake message.\n */\nconst HandshakeType = Object.freeze({\n    HELLO_REQUEST: 0,\n    CLIENT_HELLO: 1,\n    SERVER_HELLO: 2,\n    NEW_SESSION_TICKET: 4,\n    CERTIFICATE: 11,\n    SERVER_KEY_EXCHANGE: 12,\n    CERTIFICATE_REQUEST: 13,\n    SERVER_HELLO_DONE: 14,\n    CERTIFICATE_VERIFY: 15,\n    CLIENT_KEY_EXCHANGE: 16,\n    FINISHED: 20,\n});\n\n/**\n * Parses TLS Handshake ClientHello messages.\n */\nclass ClientHelloParser {\n\n    /**\n     * ClientHelloParser constructor.\n     */\n    constructor() {\n        this._extensionsParser = new ExtensionsParser();\n    }\n\n    /**\n     * Parses a single TLS Handshake ClientHello message.\n     *\n     * @param {Stream} input - Stream, containing a raw ClientHello message.\n     * @returns {Object} Object representation of ClientHello.\n     */\n    parse(input) {\n        const output = {};\n\n        output.clientVersion =  this._readClientVersion(input);\n        output.random = this._readRandom(input);\n\n        const sessionID = this._readSessionID(input);\n        if (sessionID) {\n            output.sessionID = sessionID;\n        }\n\n        output.cipherSuites = this._readCipherSuites(input);\n        output.compressionMethods = this._readCompressionMethods(input);\n        output.extensions = this._readExtensions(input);\n\n        return output;\n    }\n\n    /**\n     * Reads the client_version field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ClientHello message, with position before client_version field.\n     * @returns {string} Hex representation of client_version.\n     */\n    _readClientVersion(input) {\n        return readBytesAsHex(input, 2);\n    }\n\n    /**\n     * Reads the random field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ClientHello message, with position before random field.\n     * @returns {string} Hex representation of random.\n     */\n    _readRandom(input) {\n        return readBytesAsHex(input, 32);\n    }\n\n    /**\n     * Reads the session_id field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ClientHello message, with position before session_id length field.\n     * @returns {string} Hex representation of session_id, or empty string if session_id not present.\n     */\n    _readSessionID(input) {\n        return readSizePrefixedBytesAsHex(input, 1);\n    }\n\n    /**\n     * Reads the cipher_suites field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ClientHello message, with position before cipher_suites length field.\n     * @returns {Object} Object represention of cipher_suites field.\n     */\n    _readCipherSuites(input) {\n        const output = {};\n\n        output.length = input.readInt(2);\n        if (!output.length) {\n            return {};\n        }\n\n        const cipherSuites = new Stream(input.getBytes(output.length));\n        if (cipherSuites.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = [];\n\n        while (cipherSuites.hasMore()) {\n            const cipherSuite = readBytesAsHex(cipherSuites, 2);\n            if (cipherSuite) {\n                output.values.push(cipherSuite);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads the compression_methods field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ClientHello message, with position before compression_methods length field.\n     * @returns {Object} Object representation of compression_methods field.\n     */\n    _readCompressionMethods(input) {\n        const output = {};\n\n        output.length = input.readInt(1);\n        if (!output.length) {\n            return {};\n        }\n\n        const compressionMethods = new Stream(input.getBytes(output.length));\n        if (compressionMethods.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = [];\n\n        while (compressionMethods.hasMore()) {\n            const compressionMethod = readBytesAsHex(compressionMethods, 1);\n            if (compressionMethod) {\n                output.values.push(compressionMethod);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads the extensions field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ClientHello message, with position before extensions length field.\n     * @returns {Object} Object representations of extensions field.\n     */\n    _readExtensions(input) {\n        const output = {};\n\n        output.length = input.readInt(2);\n        if (!output.length) {\n            return {};\n        }\n\n        const extensions = new Stream(input.getBytes(output.length));\n        if (extensions.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = this._extensionsParser.parse(extensions);\n\n        return output;\n    }\n}\n\n/**\n * Parses TLS Handshake ServeHello messages.\n */\nclass ServerHelloParser {\n\n    /**\n     * ServerHelloParser constructor.\n     */\n    constructor() {\n        this._extensionsParser = new ExtensionsParser();\n    }\n\n    /**\n     *  Parses a single TLS Handshake ServerHello message.\n     *\n     * @param {Stream} input - Stream, containing a raw ServerHello message.\n     * @return {Object} Object representation of ServerHello.\n     */\n    parse(input) {\n        const output = {};\n\n        output.serverVersion = this._readServerVersion(input);\n        output.random = this._readRandom(input);\n\n        const sessionID = this._readSessionID(input);\n        if (sessionID) {\n            output.sessionID = sessionID;\n        }\n\n        output.cipherSuite =  this._readCipherSuite(input);\n        output.compressionMethod = this._readCompressionMethod(input);\n        output.extensions = this._readExtensions(input);\n\n        return output;\n    }\n\n    /**\n     * Reads the server_version field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ServerHello message, with position before server_version field.\n     * @returns {string} Hex representation of server_version.\n     */\n    _readServerVersion(input) {\n        return readBytesAsHex(input, 2);\n    }\n\n    /**\n     * Reads the random field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ServerHello message, with position before random field.\n     * @returns {string} Hex representation of random.\n     */\n    _readRandom(input) {\n        return readBytesAsHex(input, 32);\n    }\n\n    /**\n     * Reads the session_id field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ServertHello message, with position before session_id length field.\n     * @returns {string} Hex representation of session_id, or empty string if session_id not present.\n     */\n    _readSessionID(input) {\n        return readSizePrefixedBytesAsHex(input, 1);\n    }\n\n    /**\n     * Reads the cipher_suite field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ServerHello message, with position before cipher_suite field.\n     * @returns {string} Hex represention of cipher_suite.\n     */\n    _readCipherSuite(input) {\n        return readBytesAsHex(input, 2);\n    }\n\n    /**\n     * Reads the compression_method field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ServerHello message, with position before compression_method field.\n     * @returns {string} Hex represention of compression_method.\n     */\n    _readCompressionMethod(input) {\n        return readBytesAsHex(input, 1);\n    }\n\n    /**\n     * Reads the extensions field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw ServerHello message, with position before extensions length field.\n     * @returns {Object} Object representation of extensions field.\n     */\n    _readExtensions(input) {\n        const output = {};\n\n        output.length = input.readInt(2);\n        if (!output.length) {\n            return {};\n        }\n\n        const extensions = new Stream(input.getBytes(output.length));\n        if (extensions.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = this._extensionsParser.parse(extensions);\n\n        return output;\n    }\n}\n\n/**\n * Parses TLS Handshake Hello Extensions.\n */\nclass ExtensionsParser {\n\n    /**\n     * Parses a stream of TLS Handshake Hello Extensions.\n     *\n     * @param {Stream} input - Stream, containing multiple raw Extensions, with position before first extension length field.\n     * @returns {Object[]} Array of Object representations of Extensions contained within input.\n     */\n    parse(input) {\n        const output = [];\n\n        while (input.hasMore()) {\n            const extension = this._readExtension(input);\n            if (extension) {\n                output.push(extension);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads a single Extension from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a list of Extensions, with position before the length field of the next Extension.\n     * @returns {Object} Object representation of Extension.\n     */\n    _readExtension(input) {\n        const output = {};\n\n        if (input.position + 4 > input.length) {\n            input.moveTo(input.length);\n            return null;\n        }\n\n        output.type = \"0x\" + toHexFast(input.getBytes(2));\n        output.length = input.readInt(2);\n        if (!output.length) {\n            return output;\n        }\n\n        const value = input.getBytes(output.length);\n        if (!value || value.length !== output.length) {\n            output.truncated = true;\n        }\n\n        if (value && value.length) {\n            output.value = \"0x\" + toHexFast(value);\n        }\n\n        return output;\n    }\n}\n\n/**\n * Parses TLS Handshake NewSessionTicket messages.\n */\nclass NewSessionTicketParser {\n\n    /**\n     * Parses a single TLS Handshake NewSessionTicket message.\n     *\n     * @param {Stream} input - Stream, containing a raw NewSessionTicket message.\n     * @returns {Object} Object representation of NewSessionTicket.\n     */\n    parse(input) {\n        return {\n            ticketLifetimeHint: this._readTicketLifetimeHint(input),\n            ticket: this._readTicket(input),\n        };\n    }\n\n    /**\n     * Reads the ticket_lifetime_hint field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket_lifetime_hint field.\n     * @returns {string} Lifetime hint, in seconds.\n     */\n    _readTicketLifetimeHint(input) {\n        if (input.position + 4 > input.length) {\n            input.moveTo(input.length);\n            return \"\";\n        }\n\n        return input.readInt(4) + \"s\";\n    }\n\n    /**\n     * Reads the ticket field fromt the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket length field.\n     * @returns {string} Hex representation of ticket.\n     */\n    _readTicket(input) {\n        return readSizePrefixedBytesAsHex(input, 2);\n    }\n}\n\n/**\n * Parses TLS Handshake Certificate messages.\n */\nclass CertificateParser {\n\n    /**\n     * Parses a single TLS Handshake Certificate message.\n     *\n     * @param {Stream} input - Stream, containing a raw Certificate message.\n     * @returns {Object} Object representation of Certificate.\n     */\n    parse(input) {\n        const output = {};\n\n        output.certificateList = this._readCertificateList(input);\n\n        return output;\n    }\n\n    /**\n     * Reads the certificate_list field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw Certificate message, with position before certificate_list length field.\n     * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_list field.\n     */\n    _readCertificateList(input) {\n        const output = {};\n\n        if (input.position + 3 > input.length) {\n            input.moveTo(input.length);\n            return output;\n        }\n\n        output.length = input.readInt(3);\n        if (!output.length) {\n            return output;\n        }\n\n        const certificates = new Stream(input.getBytes(output.length));\n        if (certificates.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = [];\n\n        while (certificates.hasMore()) {\n            const certificate = this._readCertificate(certificates);\n            if (certificate) {\n                output.values.push(certificate);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads a single certificate from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a list of certificicates, with position before the length field of the next certificate.\n     * @returns {string} Hex representation of certificate.\n     */\n    _readCertificate(input) {\n        return readSizePrefixedBytesAsHex(input, 3);\n    }\n}\n\n/**\n * Parses TLS Handshake CertificateRequest messages.\n */\nclass CertificateRequestParser {\n\n    /**\n     * Parses a single TLS Handshake CertificateRequest message.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateRequest message.\n     * @return {Object} Object representation of CertificateRequest.\n     */\n    parse(input) {\n        const output = {};\n\n        output.certificateTypes = this._readCertificateTypes(input);\n        output.supportedSignatureAlgorithms = this._readSupportedSignatureAlgorithms(input);\n\n        const certificateAuthorities = this._readCertificateAuthorities(input);\n        if (certificateAuthorities.length) {\n            output.certificateAuthorities = certificateAuthorities;\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads the certificate_types field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_types length field.\n     * @return {string[]} Array of strings, each containing a hex representation of a value within the certificate_types field.\n     */\n    _readCertificateTypes(input) {\n        const output = {};\n\n        output.length = input.readInt(1);\n        if (!output.length) {\n            return {};\n        }\n\n        const certificateTypes = new Stream(input.getBytes(output.length));\n        if (certificateTypes.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = [];\n\n        while (certificateTypes.hasMore()) {\n            const certificateType = readBytesAsHex(certificateTypes, 1);\n            if (certificateType) {\n                output.values.push(certificateType);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads the supported_signature_algorithms field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before supported_signature_algorithms length field.\n     * @returns {string[]} Array of strings, each containing a hex representation of a value within the supported_signature_algorithms field.\n     */\n    _readSupportedSignatureAlgorithms(input) {\n        const output = {};\n\n        output.length = input.readInt(2);\n        if (!output.length) {\n            return {};\n        }\n\n        const signatureAlgorithms = new Stream(input.getBytes(output.length));\n        if (signatureAlgorithms.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = [];\n\n        while (signatureAlgorithms.hasMore()) {\n            const signatureAlgorithm = readBytesAsHex(signatureAlgorithms, 2);\n            if (signatureAlgorithm) {\n                output.values.push(signatureAlgorithm);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads the certificate_authorities field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_authorities length field.\n     * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_authorities field.\n     */\n    _readCertificateAuthorities(input) {\n        const output = {};\n\n        output.length = input.readInt(2);\n        if (!output.length) {\n            return {};\n        }\n\n        const certificateAuthorities = new Stream(input.getBytes(output.length));\n        if (certificateAuthorities.length < output.length) {\n            output.truncated = true;\n        }\n\n        output.values = [];\n\n        while (certificateAuthorities.hasMore()) {\n            const certificateAuthority = this._readCertificateAuthority(certificateAuthorities);\n            if (certificateAuthority) {\n                output.values.push(certificateAuthority);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Reads a single certificate authority from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a list of raw certificate authorities, with position before the length field of the next certificate authority.\n     * @returns {string} Hex representation of certificate authority.\n     */\n    _readCertificateAuthority(input) {\n        return readSizePrefixedBytesAsHex(input, 2);\n    }\n}\n\n/**\n * Parses TLS Handshake CertificateVerify messages.\n */\nclass CertificateVerifyParser {\n\n    /**\n     * Parses a single CertificateVerify Message.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateVerify message.\n     * @returns {Object} Object representation of CertificateVerify.\n     */\n    parse(input) {\n        return {\n            algorithmHash: this._readAlgorithmHash(input),\n            algorithmSignature: this._readAlgorithmSignature(input),\n            signature: this._readSignature(input),\n        };\n    }\n\n    /**\n     * Reads the algorithm.hash field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.hash field.\n     * @return {string} Hex representation of hash algorithm.\n     */\n    _readAlgorithmHash(input) {\n        return readBytesAsHex(input, 1);\n    }\n\n    /**\n     * Reads the algorithm.signature field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.signature field.\n     * @return {string} Hex representation of signature algorithm.\n     */\n    _readAlgorithmSignature(input) {\n        return readBytesAsHex(input, 1);\n    }\n\n    /**\n     * Reads the signature field from the following bytes in the provided Stream.\n     *\n     * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before signature field.\n     * @return {string} Hex representation of signature.\n     */\n    _readSignature(input) {\n        return readSizePrefixedBytesAsHex(input, 2);\n    }\n}\n\n/**\n * Read the following size prefixed bytes from the provided Stream, and reuturn as a hex string.\n *\n * @param {Stream} input - Stream to read from.\n * @param {int} sizePrefixLength - Length of the size prefix field.\n * @returns {string} Hex representation of bytes read from Stream, empty string is returned if\n *                   field cannot be read in full.\n */\nfunction readSizePrefixedBytesAsHex(input, sizePrefixLength) {\n    const length = input.readInt(sizePrefixLength);\n    if (!length) {\n        return \"\";\n    }\n\n    return readBytesAsHex(input, length);\n}\n\n/**\n * Read n bytes from the provided Stream, and return as a hex string.\n *\n * @param {Stream} input - Stream to read from.\n * @param {int} n - Number of bytes to read.\n * @returns {string} Hex representation of bytes read from Stream, or empty string if field cannot\n *                   be read in full.\n */\nfunction readBytesAsHex(input, n) {\n    const bytes = input.getBytes(n);\n    if (!bytes || bytes.length !== n) {\n        return \"\";\n    }\n\n    return \"0x\" + toHexFast(bytes);\n}\n"
  },
  {
    "path": "src/core/operations/ParseTLV.mjs",
    "content": "/**\n * @author gchq77703 []\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport TLVParser from \"../lib/TLVParser.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Parse TLV operation\n */\nclass ParseTLV extends Operation {\n\n    /**\n     * ParseTLV constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse TLV\";\n        this.module = \"Default\";\n        this.description = \"Converts a Type-Length-Value (TLV) encoded string into a JSON object.  Can optionally include a <code>Key</code> / <code>Type</code> entry. <br><br>Tags: Key-Length-Value, KLV, Length-Value, LV\";\n        this.infoURL = \"https://wikipedia.org/wiki/Type-length-value\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.args = [\n            {\n                name: \"Type/Key size\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Length size\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Use BER\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [bytesInKey, bytesInLength, basicEncodingRules] = args;\n        input = new Uint8Array(input);\n\n        if (bytesInKey <= 0 && bytesInLength <= 0)\n            throw new OperationError(\"Type or Length size must be greater than 0\");\n\n        const tlv = new TLVParser(input, { bytesInLength, basicEncodingRules });\n\n        const data = [];\n\n        while (!tlv.atEnd()) {\n            const key = bytesInKey ? tlv.getValue(bytesInKey) : undefined;\n            const length = tlv.getLength();\n            const value = tlv.getValue(length);\n\n            data.push({ key, length, value });\n        }\n\n        return data;\n    }\n\n}\n\nexport default ParseTLV;\n"
  },
  {
    "path": "src/core/operations/ParseUDP.mjs",
    "content": "/**\n * @author h345983745 []\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport {toHexFast, fromHex} from \"../lib/Hex.mjs\";\nimport {objToTable} from \"../lib/Protocol.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Parse UDP operation\n */\nclass ParseUDP extends Operation {\n\n    /**\n     * ParseUDP constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse UDP\";\n        this.module = \"Default\";\n        this.description = \"Parses a UDP header and payload (if present).\";\n        this.infoURL = \"https://wikipedia.org/wiki/User_Datagram_Protocol\";\n        this.inputType = \"string\";\n        this.outputType = \"json\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {Object}\n     */\n    run(input, args) {\n        const format = args[0];\n\n        if (format === \"Hex\") {\n            input = fromHex(input);\n        } else if (format === \"Raw\") {\n            input = Utils.strToArrayBuffer(input);\n        } else {\n            throw new OperationError(\"Unrecognised input format.\");\n        }\n\n        const s = new Stream(new Uint8Array(input));\n        if (s.length < 8) {\n            throw new OperationError(\"Need 8 bytes for a UDP Header\");\n        }\n\n        // Parse Header\n        const UDPPacket = {\n            \"Source port\": s.readInt(2),\n            \"Destination port\": s.readInt(2),\n            \"Length\": s.readInt(2),\n            \"Checksum\": \"0x\" + toHexFast(s.getBytes(2))\n        };\n        // Parse data if present\n        if (s.hasMore()) {\n            UDPPacket.Data = \"0x\" + toHexFast(s.getBytes(UDPPacket.Length - 8));\n        }\n\n        return UDPPacket;\n    }\n\n    /**\n     * Displays the UDP Packet in a tabular style\n     * @param {Object} data\n     * @returns {html}\n     */\n    present(data) {\n        return objToTable(data);\n    }\n\n}\n\n\nexport default ParseUDP;\n"
  },
  {
    "path": "src/core/operations/ParseUNIXFilePermissions.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Parse UNIX file permissions operation\n */\nclass ParseUNIXFilePermissions extends Operation {\n\n    /**\n     * ParseUNIXFilePermissions constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse UNIX file permissions\";\n        this.module = \"Default\";\n        this.description = \"Given a UNIX/Linux file permission string in octal or textual format, this operation explains which permissions are granted to which user groups.<br><br>Input should be in either octal (e.g. <code>755</code>) or textual (e.g. <code>drwxr-xr-x</code>) format.\";\n        this.infoURL = \"https://wikipedia.org/wiki/File_system_permissions#Traditional_Unix_permissions\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern:  \"^\\\\s*d[rxw-]{9}\\\\s*$\",\n                flags:  \"\",\n                args:   []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const perms = {\n            d:  false, // directory\n            sl: false, // symbolic link\n            np: false, // named pipe\n            s:  false, // socket\n            cd: false, // character device\n            bd: false, // block device\n            dr: false, // door\n            sb: false, // sticky bit\n            su: false, // setuid\n            sg: false, // setgid\n            ru: false, // read user\n            wu: false, // write user\n            eu: false, // execute user\n            rg: false, // read group\n            wg: false, // write group\n            eg: false, // execute group\n            ro: false, // read other\n            wo: false, // write other\n            eo: false  // execute other\n        };\n        let d = 0,\n            u = 0,\n            g = 0,\n            o = 0,\n            output = \"\",\n            octal = null,\n            textual = null;\n\n        if (input.search(/\\s*[0-7]{1,4}\\s*/i) === 0) {\n            // Input is octal\n            octal = input.match(/\\s*([0-7]{1,4})\\s*/i)[1];\n\n            if (octal.length === 4) {\n                d = parseInt(octal[0], 8);\n                u = parseInt(octal[1], 8);\n                g = parseInt(octal[2], 8);\n                o = parseInt(octal[3], 8);\n            } else {\n                if (octal.length > 0) u = parseInt(octal[0], 8);\n                if (octal.length > 1) g = parseInt(octal[1], 8);\n                if (octal.length > 2) o = parseInt(octal[2], 8);\n            }\n\n            perms.su = d >> 2 & 0x1;\n            perms.sg = d >> 1 & 0x1;\n            perms.sb = d & 0x1;\n\n            perms.ru = u >> 2 & 0x1;\n            perms.wu = u >> 1 & 0x1;\n            perms.eu = u & 0x1;\n\n            perms.rg = g >> 2 & 0x1;\n            perms.wg = g >> 1 & 0x1;\n            perms.eg = g & 0x1;\n\n            perms.ro = o >> 2 & 0x1;\n            perms.wo = o >> 1 & 0x1;\n            perms.eo = o & 0x1;\n        } else if (input.search(/\\s*[dlpcbDrwxsStT-]{1,10}\\s*/) === 0) {\n            // Input is textual\n            textual = input.match(/\\s*([dlpcbDrwxsStT-]{1,10})\\s*/)[1];\n\n            switch (textual[0]) {\n                case \"d\":\n                    perms.d = true;\n                    break;\n                case \"l\":\n                    perms.sl = true;\n                    break;\n                case \"p\":\n                    perms.np = true;\n                    break;\n                case \"s\":\n                    perms.s = true;\n                    break;\n                case \"c\":\n                    perms.cd = true;\n                    break;\n                case \"b\":\n                    perms.bd = true;\n                    break;\n                case \"D\":\n                    perms.dr = true;\n                    break;\n            }\n\n            if (textual.length > 1) perms.ru = textual[1] === \"r\";\n            if (textual.length > 2) perms.wu = textual[2] === \"w\";\n            if (textual.length > 3) {\n                switch (textual[3]) {\n                    case \"x\":\n                        perms.eu = true;\n                        break;\n                    case \"s\":\n                        perms.eu = true;\n                        perms.su = true;\n                        break;\n                    case \"S\":\n                        perms.su = true;\n                        break;\n                }\n            }\n\n            if (textual.length > 4) perms.rg = textual[4] === \"r\";\n            if (textual.length > 5) perms.wg = textual[5] === \"w\";\n            if (textual.length > 6) {\n                switch (textual[6]) {\n                    case \"x\":\n                        perms.eg = true;\n                        break;\n                    case \"s\":\n                        perms.eg = true;\n                        perms.sg = true;\n                        break;\n                    case \"S\":\n                        perms.sg = true;\n                        break;\n                }\n            }\n\n            if (textual.length > 7) perms.ro = textual[7] === \"r\";\n            if (textual.length > 8) perms.wo = textual[8] === \"w\";\n            if (textual.length > 9) {\n                switch (textual[9]) {\n                    case \"x\":\n                        perms.eo = true;\n                        break;\n                    case \"t\":\n                        perms.eo = true;\n                        perms.sb = true;\n                        break;\n                    case \"T\":\n                        perms.sb = true;\n                        break;\n                }\n            }\n        } else {\n            throw new OperationError(\"Invalid input format.\\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format.\");\n        }\n\n        output += \"Textual representation: \" + permsToStr(perms);\n        output += \"\\nOctal representation:   \" + permsToOctal(perms);\n\n        // File type\n        if (textual) {\n            output += \"\\nFile type: \" + ftFromPerms(perms);\n        }\n\n        // setuid, setgid\n        if (perms.su) {\n            output += \"\\nThe setuid flag is set\";\n        }\n        if (perms.sg) {\n            output += \"\\nThe setgid flag is set\";\n        }\n\n        // sticky bit\n        if (perms.sb) {\n            output += \"\\nThe sticky bit is set\";\n        }\n\n        // Permission matrix\n        output += `\n\n +---------+-------+-------+-------+\n |         | User  | Group | Other |\n +---------+-------+-------+-------+\n |    Read |   ${perms.ru ? \"X\" : \" \"}   |   ${perms.rg ? \"X\" : \" \"}   |   ${perms.ro ? \"X\" : \" \"}   |\n +---------+-------+-------+-------+\n |   Write |   ${perms.wu ? \"X\" : \" \"}   |   ${perms.wg ? \"X\" : \" \"}   |   ${perms.wo ? \"X\" : \" \"}   |\n +---------+-------+-------+-------+\n | Execute |   ${perms.eu ? \"X\" : \" \"}   |   ${perms.eg ? \"X\" : \" \"}   |   ${perms.eo ? \"X\" : \" \"}   |\n +---------+-------+-------+-------+`;\n\n        return output;\n    }\n\n}\n\n\n/**\n * Given a permissions object dictionary, generates a textual permissions string.\n *\n * @param {Object} perms\n * @returns {string}\n */\nfunction permsToStr(perms) {\n    let str = \"\",\n        type = \"-\";\n\n    if (perms.d) type = \"d\";\n    if (perms.sl) type = \"l\";\n    if (perms.np) type = \"p\";\n    if (perms.s) type = \"s\";\n    if (perms.cd) type = \"c\";\n    if (perms.bd) type = \"b\";\n    if (perms.dr) type = \"D\";\n\n    str = type;\n\n    str += perms.ru ? \"r\" : \"-\";\n    str += perms.wu ? \"w\" : \"-\";\n    if (perms.eu && perms.su) {\n        str += \"s\";\n    } else if (perms.su) {\n        str += \"S\";\n    } else if (perms.eu) {\n        str += \"x\";\n    } else {\n        str += \"-\";\n    }\n\n    str += perms.rg ? \"r\" : \"-\";\n    str += perms.wg ? \"w\" : \"-\";\n    if (perms.eg && perms.sg) {\n        str += \"s\";\n    } else if (perms.sg) {\n        str += \"S\";\n    } else if (perms.eg) {\n        str += \"x\";\n    } else {\n        str += \"-\";\n    }\n\n    str += perms.ro ? \"r\" : \"-\";\n    str += perms.wo ? \"w\" : \"-\";\n    if (perms.eo && perms.sb) {\n        str += \"t\";\n    } else if (perms.sb) {\n        str += \"T\";\n    } else if (perms.eo) {\n        str += \"x\";\n    } else {\n        str += \"-\";\n    }\n\n    return str;\n}\n\n/**\n * Given a permissions object dictionary, generates an octal permissions string.\n *\n * @param {Object} perms\n * @returns {string}\n */\nfunction permsToOctal(perms) {\n    let d = 0,\n        u = 0,\n        g = 0,\n        o = 0;\n\n    if (perms.su) d += 4;\n    if (perms.sg) d += 2;\n    if (perms.sb) d += 1;\n\n    if (perms.ru) u += 4;\n    if (perms.wu) u += 2;\n    if (perms.eu) u += 1;\n\n    if (perms.rg) g += 4;\n    if (perms.wg) g += 2;\n    if (perms.eg) g += 1;\n\n    if (perms.ro) o += 4;\n    if (perms.wo) o += 2;\n    if (perms.eo) o += 1;\n\n    return d.toString() + u.toString() + g.toString() + o.toString();\n}\n\n\n/**\n * Given a permissions object dictionary, returns the file type.\n *\n * @param {Object} perms\n * @returns {string}\n */\nfunction ftFromPerms(perms) {\n    if (perms.d) return \"Directory\";\n    if (perms.sl) return \"Symbolic link\";\n    if (perms.np) return \"Named pipe\";\n    if (perms.s) return \"Socket\";\n    if (perms.cd) return \"Character device\";\n    if (perms.bd) return \"Block device\";\n    if (perms.dr) return \"Door\";\n    return \"Regular file\";\n}\n\nexport default ParseUNIXFilePermissions;\n"
  },
  {
    "path": "src/core/operations/ParseURI.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport url from \"url\";\n\n/**\n * Parse URI operation\n */\nclass ParseURI extends Operation {\n\n    /**\n     * ParseURI constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse URI\";\n        this.module = \"URL\";\n        this.description = \"Pretty prints complicated Uniform Resource Identifier (URI) strings for ease of reading. Particularly useful for Uniform Resource Locators (URLs) with a lot of arguments.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Uniform_Resource_Identifier\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const uri = url.parse(input, true);\n\n        let output = \"\";\n\n        if (uri.protocol) output += \"Protocol:\\t\" + uri.protocol + \"\\n\";\n        if (uri.auth) output += \"Auth:\\t\\t\" + uri.auth + \"\\n\";\n        if (uri.hostname) output += \"Hostname:\\t\" + uri.hostname + \"\\n\";\n        if (uri.port) output += \"Port:\\t\\t\" + uri.port + \"\\n\";\n        if (uri.pathname) output += \"Path name:\\t\" + uri.pathname + \"\\n\";\n        if (uri.query) {\n            const keys = Object.keys(uri.query);\n            let padding = 0;\n\n            keys.forEach(k => {\n                padding = (k.length > padding) ? k.length : padding;\n            });\n\n            output += \"Arguments:\\n\";\n            for (const key in uri.query) {\n                output += \"\\t\" + key.padEnd(padding, \" \");\n                if (uri.query[key].length) {\n                    output += \" = \" + uri.query[key] + \"\\n\";\n                } else {\n                    output += \"\\n\";\n                }\n            }\n        }\n        if (uri.hash) output += \"Hash:\\t\\t\" + uri.hash + \"\\n\";\n\n        return output;\n    }\n\n}\n\nexport default ParseURI;\n"
  },
  {
    "path": "src/core/operations/ParseUserAgent.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport UAParser from \"ua-parser-js\";\n\n/**\n * Parse User Agent operation\n */\nclass ParseUserAgent extends Operation {\n\n    /**\n     * ParseUserAgent constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse User Agent\";\n        this.module = \"UserAgent\";\n        this.description = \"Attempts to identify and categorise information contained in a user-agent string.\";\n        this.infoURL = \"https://wikipedia.org/wiki/User_agent\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern:  \"^(User-Agent:|Mozilla\\\\/)[^\\\\n\\\\r]+\\\\s*$\",\n                flags:  \"i\",\n                args:   []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const ua = UAParser(input);\n        return `Browser\n    Name: ${ua.browser.name || \"unknown\"}\n    Version: ${ua.browser.version || \"unknown\"}\nDevice\n    Model: ${ua.device.model || \"unknown\"}\n    Type: ${ua.device.type || \"unknown\"}\n    Vendor: ${ua.device.vendor || \"unknown\"}\nEngine\n    Name: ${ua.engine.name || \"unknown\"}\n    Version: ${ua.engine.version || \"unknown\"}\nOS\n    Name: ${ua.os.name || \"unknown\"}\n    Version: ${ua.os.version || \"unknown\"}\nCPU\n    Architecture: ${ua.cpu.architecture || \"unknown\"}`;\n    }\n\n}\n\nexport default ParseUserAgent;\n"
  },
  {
    "path": "src/core/operations/ParseX509CRL.mjs",
    "content": "/**\n * @author robinsandhu\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\nimport { fromBase64 } from \"../lib/Base64.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { formatDnObj } from \"../lib/PublicKey.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Parse X.509 CRL operation\n */\nclass ParseX509CRL extends Operation {\n\n    /**\n     * ParseX509CRL constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse X.509 CRL\";\n        this.module = \"PublicKey\";\n        this.description = \"Parse Certificate Revocation List (CRL)\";\n        this.infoURL = \"https://wikipedia.org/wiki/Certificate_revocation_list\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"PEM\", \"DER Hex\", \"Base64\", \"Raw\"]\n            }\n        ];\n        this.checks = [\n            {\n                \"pattern\": \"^-+BEGIN X509 CRL-+\\\\r?\\\\n[\\\\da-z+/\\\\n\\\\r]+-+END X509 CRL-+\\\\r?\\\\n?$\",\n                \"flags\": \"i\",\n                \"args\": [\"PEM\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string} Human-readable description of a Certificate Revocation List (CRL).\n     */\n    run(input, args) {\n        if (!input.length) {\n            return \"No input\";\n        }\n\n        const inputFormat = args[0];\n\n        let undefinedInputFormat = false;\n        try {\n            switch (inputFormat) {\n                case \"DER Hex\":\n                    input = input.replace(/\\s/g, \"\").toLowerCase();\n                    break;\n                case \"PEM\":\n                    break;\n                case \"Base64\":\n                    input = toHex(fromBase64(input, null, \"byteArray\"), \"\");\n                    break;\n                case \"Raw\":\n                    input = toHex(Utils.strToArrayBuffer(input), \"\");\n                    break;\n                default:\n                    undefinedInputFormat = true;\n            }\n        } catch (e) {\n            throw \"Certificate load error (non-certificate input?)\";\n        }\n        if (undefinedInputFormat) throw \"Undefined input format\";\n\n        const crl = new r.X509CRL(input);\n\n        let out = `Certificate Revocation List (CRL):\n    Version: ${crl.getVersion() === null ? \"1 (0x0)\" : \"2 (0x1)\"}\n    Signature Algorithm: ${crl.getSignatureAlgorithmField()}\n    Issuer:\\n${formatDnObj(crl.getIssuer(), 8)}\n    Last Update: ${generalizedDateTimeToUTC(crl.getThisUpdate())}\n    Next Update: ${generalizedDateTimeToUTC(crl.getNextUpdate())}\\n`;\n\n        if (crl.getParam().ext !== undefined) {\n            out += `\\tCRL extensions:\\n${formatCRLExtensions(crl.getParam().ext, 8)}\\n`;\n        }\n\n        out += `Revoked Certificates:\\n${formatRevokedCertificates(crl.getRevCertArray(), 4)}\nSignature Value:\\n${formatCRLSignature(crl.getSignatureValueHex(), 8)}`;\n\n        return out;\n    }\n}\n\n/**\n * Generalized date time string to UTC.\n * @param {string} datetime\n * @returns UTC datetime string.\n */\nfunction generalizedDateTimeToUTC(datetime) {\n    // Ensure the string is in the correct format\n    if (!/^\\d{12,14}Z$/.test(datetime)) {\n        throw new OperationError(`failed to format datetime string ${datetime}`);\n    }\n\n    // Extract components\n    let centuary = \"20\";\n    if (datetime.length === 15) {\n        centuary = datetime.substring(0, 2);\n        datetime = datetime.slice(2);\n    }\n    const year = centuary + datetime.substring(0, 2);\n    const month = datetime.substring(2, 4);\n    const day = datetime.substring(4, 6);\n    const hour = datetime.substring(6, 8);\n    const minute = datetime.substring(8, 10);\n    const second = datetime.substring(10, 12);\n\n    // Construct ISO 8601 format string\n    const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;\n\n    // Parse using standard Date object\n    const isoDateTime = new Date(isoString);\n\n    return isoDateTime.toUTCString();\n}\n\n/**\n * Format CRL extensions.\n * @param {r.ExtParam[] | undefined} extensions\n * @param {Number} indent\n * @returns Formatted string detailing CRL extensions.\n */\nfunction formatCRLExtensions(extensions, indent) {\n    if (Array.isArray(extensions) === false || extensions.length === 0) {\n        return indentString(`No CRL extensions.`, indent);\n    }\n\n    let out = ``;\n\n    extensions.sort((a, b) => {\n        if (!Object.hasOwn(a, \"extname\") || !Object.hasOwn(b, \"extname\")) {\n            return 0;\n        }\n        if (a.extname < b.extname) {\n            return -1;\n        } else if (a.extname === b.extname) {\n            return 0;\n        } else {\n            return 1;\n        }\n    });\n\n    extensions.forEach((ext) => {\n        if (!Object.hasOwn(ext, \"extname\")) {\n            throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);\n        }\n        switch (ext.extname) {\n            case \"authorityKeyIdentifier\":\n                out += `X509v3 Authority Key Identifier:\\n`;\n                if (Object.hasOwn(ext, \"kid\")) {\n                    out += `\\tkeyid:${colonDelimitedHexFormatString(ext.kid.hex.toUpperCase())}\\n`;\n                }\n                if (Object.hasOwn(ext, \"issuer\")) {\n                    out += `\\tDirName:${ext.issuer.str}\\n`;\n                }\n                if (Object.hasOwn(ext, \"sn\")) {\n                    out += `\\tserial:${colonDelimitedHexFormatString(ext.sn.hex.toUpperCase())}\\n`;\n                }\n                break;\n            case \"cRLDistributionPoints\":\n                out += `X509v3 CRL Distribution Points:\\n`;\n                ext.array.forEach((distPoint) => {\n                    const fullName = `Full Name:\\n${formatGeneralNames(distPoint.dpname.full, 4)}`;\n                    out += indentString(fullName, 4) + \"\\n\";\n                });\n                break;\n            case \"cRLNumber\":\n                if (!Object.hasOwn(ext, \"num\")) {\n                    throw new OperationError(`'cRLNumber' CRL entry extension missing 'num' key: ${ext}`);\n                }\n                out += `X509v3 CRL Number:\\n\\t${ext.num.hex.toUpperCase()}\\n`;\n                break;\n            case \"issuerAltName\":\n                out += `X509v3 Issuer Alternative Name:\\n${formatGeneralNames(ext.array, 4)}\\n`;\n                break;\n            default:\n                out += `${ext.extname}:\\n`;\n                out += `\\tUnsupported CRL extension. Try openssl CLI.\\n`;\n                break;\n        }\n    });\n\n    return indentString(chop(out), indent);\n}\n\n/**\n * Format general names array.\n * @param {Object[]} names\n * @returns Multi-line formatted string describing all supported general name types.\n */\nfunction formatGeneralNames(names, indent) {\n    let out = ``;\n\n    names.forEach((name) => {\n        const key = Object.keys(name)[0];\n\n        switch (key) {\n            case \"ip\":\n                out += `IP:${name.ip}\\n`;\n                break;\n            case \"dns\":\n                out += `DNS:${name.dns}\\n`;\n                break;\n            case \"uri\":\n                out += `URI:${name.uri}\\n`;\n                break;\n            case \"rfc822\":\n                out += `EMAIL:${name.rfc822}\\n`;\n                break;\n            case \"dn\":\n                out += `DIR:${name.dn.str}\\n`;\n                break;\n            case \"other\":\n                out += `OtherName:${name.other.oid}::${Object.values(name.other.value)[0].str}\\n`;\n                break;\n            default:\n                out += `${key}: unsupported general name type`;\n                break;\n        }\n    });\n\n    return indentString(chop(out), indent);\n}\n\n/**\n * Colon-delimited hex formatted output.\n * @param {string} hexString Hex String\n * @returns String representing input hex string with colon delimiter.\n */\nfunction colonDelimitedHexFormatString(hexString) {\n    if (hexString.length % 2 !== 0) {\n        hexString = \"0\" + hexString;\n    }\n\n    return chop(hexString.replace(/(..)/g, \"$&:\"));\n}\n\n/**\n * Format revoked certificates array\n * @param {r.RevokedCertificate[] | null} revokedCertificates\n * @param {Number} indent\n * @returns Multi-line formatted string output of revoked certificates array\n */\nfunction formatRevokedCertificates(revokedCertificates, indent) {\n    if (Array.isArray(revokedCertificates) === false || revokedCertificates.length === 0) {\n        return indentString(\"No Revoked Certificates.\", indent);\n    }\n\n    let out=``;\n\n    revokedCertificates.forEach((revCert) => {\n        if (!Object.hasOwn(revCert, \"sn\") || !Object.hasOwn(revCert, \"date\")) {\n            throw new OperationError(\"invalid revoked certificate object, missing either serial number or date\");\n        }\n\n        out += `Serial Number: ${revCert.sn.hex.toUpperCase()}\n    Revocation Date: ${generalizedDateTimeToUTC(revCert.date)}\\n`;\n        if (Object.hasOwn(revCert, \"ext\") && Array.isArray(revCert.ext) && revCert.ext.length !== 0) {\n            out += `\\tCRL entry extensions:\\n${indentString(formatCRLEntryExtensions(revCert.ext), 2*indent)}\\n`;\n        }\n    });\n\n    return indentString(chop(out), indent);\n}\n\n/**\n * Format CRL entry extensions.\n * @param {Object[]} exts\n * @returns Formatted multi-line string describing CRL entry extensions.\n */\nfunction formatCRLEntryExtensions(exts) {\n    let out = ``;\n\n    const crlReasonCodeToReasonMessage = {\n        0: \"Unspecified\",\n        1: \"Key Compromise\",\n        2: \"CA Compromise\",\n        3: \"Affiliation Changed\",\n        4: \"Superseded\",\n        5: \"Cessation Of Operation\",\n        6: \"Certificate Hold\",\n        8: \"Remove From CRL\",\n        9: \"Privilege Withdrawn\",\n        10: \"AA Compromise\",\n    };\n\n    const holdInstructionOIDToName = {\n        \"1.2.840.10040.2.1\": \"Hold Instruction None\",\n        \"1.2.840.10040.2.2\": \"Hold Instruction Call Issuer\",\n        \"1.2.840.10040.2.3\": \"Hold Instruction Reject\",\n    };\n\n    exts.forEach((ext) => {\n        if (!Object.hasOwn(ext, \"extname\")) {\n            throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);\n        }\n        switch (ext.extname) {\n            case \"cRLReason\":\n                if (!Object.hasOwn(ext, \"code\")) {\n                    throw new OperationError(`'cRLReason' CRL entry extension missing 'code' key: ${ext}`);\n                }\n                out += `X509v3 CRL Reason Code:\n    ${Object.hasOwn(crlReasonCodeToReasonMessage, ext.code) ? crlReasonCodeToReasonMessage[ext.code] : `invalid reason code: ${ext.code}`}\\n`;\n                break;\n            case \"2.5.29.23\": // Hold instruction\n                out += `Hold Instruction Code:\\n\\t${Object.hasOwn(holdInstructionOIDToName, ext.extn.oid) ? holdInstructionOIDToName[ext.extn.oid] : `${ext.extn.oid}: unknown hold instruction OID`}\\n`;\n                break;\n            case \"2.5.29.24\": // Invalidity Date\n                out += `Invalidity Date:\\n\\t${generalizedDateTimeToUTC(ext.extn.gentime.str)}\\n`;\n                break;\n            default:\n                out += `${ext.extname}:\\n`;\n                out += `\\tUnsupported CRL entry extension. Try openssl CLI.\\n`;\n                break;\n        }\n    });\n\n    return chop(out);\n}\n\n/**\n * Format CRL signature.\n * @param {String} sigHex\n * @param {Number} indent\n * @returns String representing hex signature value formatted on multiple lines.\n */\nfunction formatCRLSignature(sigHex, indent) {\n    if (sigHex.length % 2 !== 0) {\n        sigHex = \"0\" + sigHex;\n    }\n\n    return indentString(formatMultiLine(chop(sigHex.replace(/(..)/g, \"$&:\"))), indent);\n}\n\n/**\n * Format string onto multiple lines.\n * @param {string} longStr\n * @returns String as a multi-line string.\n */\nfunction formatMultiLine(longStr) {\n    const lines = [];\n\n    for (let remain = longStr ; remain !== \"\" ; remain = remain.substring(54)) {\n        lines.push(remain.substring(0, 54));\n    }\n\n    return lines.join(\"\\n\");\n}\n\n/**\n * Indent a multi-line string by n spaces.\n * @param {string} input String\n * @param {number} spaces How many leading spaces\n * @returns Indented string.\n */\nfunction indentString(input, spaces) {\n    const indent = \" \".repeat(spaces);\n    return input.replace(/^/gm, indent);\n}\n\n/**\n * Remove last character from a string.\n * @param {string} s String\n * @returns Chopped string.\n */\nfunction chop(s) {\n    if (s.length < 1) {\n        return s;\n    }\n    return s.substring(0, s.length - 1);\n}\n\nexport default ParseX509CRL;\n"
  },
  {
    "path": "src/core/operations/ParseX509Certificate.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport { fromBase64 } from \"../lib/Base64.mjs\";\nimport { runHash } from \"../lib/Hash.mjs\";\nimport { fromHex, toHex } from \"../lib/Hex.mjs\";\nimport { formatByteStr, formatDnObj } from \"../lib/PublicKey.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Parse X.509 certificate operation\n */\nclass ParseX509Certificate extends Operation {\n\n    /**\n     * ParseX509Certificate constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Parse X.509 certificate\";\n        this.module = \"PublicKey\";\n        this.description = \"X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.<br><br>This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.<br><br>Tags: X509, server hello, handshake\";\n        this.infoURL = \"https://wikipedia.org/wiki/X.509\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"PEM\", \"DER Hex\", \"Base64\", \"Raw\"]\n            }\n        ];\n        this.checks = [\n            {\n                \"pattern\": \"^-+BEGIN CERTIFICATE-+\\\\r?\\\\n[\\\\da-z+/\\\\n\\\\r]+-+END CERTIFICATE-+\\\\r?\\\\n?$\",\n                \"flags\": \"i\",\n                \"args\": [\"PEM\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input.length) {\n            return \"No input\";\n        }\n\n        const cert = new r.X509(),\n            inputFormat = args[0];\n\n        let undefinedInputFormat = false;\n        try {\n            switch (inputFormat) {\n                case \"DER Hex\":\n                    input = input.replace(/\\s/g, \"\").toLowerCase();\n                    cert.readCertHex(input);\n                    break;\n                case \"PEM\":\n                    cert.readCertPEM(input);\n                    break;\n                case \"Base64\":\n                    cert.readCertHex(toHex(fromBase64(input, null, \"byteArray\"), \"\"));\n                    break;\n                case \"Raw\":\n                    cert.readCertHex(toHex(Utils.strToArrayBuffer(input), \"\"));\n                    break;\n                default:\n                    undefinedInputFormat = true;\n            }\n        } catch (e) {\n            throw \"Certificate load error (non-certificate input?)\";\n        }\n        if (undefinedInputFormat) throw \"Undefined input format\";\n\n        const hex = Utils.strToArrayBuffer(Utils.byteArrayToChars(fromHex(cert.hex))),\n            sn = cert.getSerialNumberHex(),\n            issuer = cert.getIssuer(),\n            subject = cert.getSubject(),\n            pk = cert.getPublicKey(),\n            pkFields = [],\n            sig = cert.getSignatureValueHex();\n\n        let pkStr = \"\",\n            sigStr = \"\",\n            extensions = \"\";\n\n        // Public Key fields\n        pkFields.push({\n            key: \"Algorithm\",\n            value: pk.type\n        });\n\n        if (pk.type === \"EC\") { // ECDSA\n            pkFields.push({\n                key: \"Curve Name\",\n                value: pk.curveName\n            });\n            pkFields.push({\n                key: \"Length\",\n                value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + \" bits\"\n            });\n            pkFields.push({\n                key: \"pub\",\n                value: formatByteStr(pk.pubKeyHex, 16, 18)\n            });\n        } else if (pk.type === \"DSA\") { // DSA\n            pkFields.push({\n                key: \"pub\",\n                value: formatByteStr(pk.y.toString(16), 16, 18)\n            });\n            pkFields.push({\n                key: \"P\",\n                value: formatByteStr(pk.p.toString(16), 16, 18)\n            });\n            pkFields.push({\n                key: \"Q\",\n                value: formatByteStr(pk.q.toString(16), 16, 18)\n            });\n            pkFields.push({\n                key: \"G\",\n                value: formatByteStr(pk.g.toString(16), 16, 18)\n            });\n        } else if (pk.e) { // RSA\n            pkFields.push({\n                key: \"Length\",\n                value: pk.n.bitLength() + \" bits\"\n            });\n            pkFields.push({\n                key: \"Modulus\",\n                value: formatByteStr(pk.n.toString(16), 16, 18)\n            });\n            pkFields.push({\n                key: \"Exponent\",\n                value: pk.e + \" (0x\" + pk.e.toString(16) + \")\"\n            });\n        } else {\n            pkFields.push({\n                key: \"Error\",\n                value: \"Unknown Public Key type\"\n            });\n        }\n\n        // Format Public Key fields\n        for (let i = 0; i < pkFields.length; i++) {\n            pkStr += `  ${pkFields[i].key}:${(pkFields[i].value + \"\\n\").padStart(\n                18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1,\n                \" \"\n            )}`;\n        }\n\n        // Signature fields\n        let breakoutSig = false;\n        try {\n            breakoutSig = r.ASN1HEX.dump(sig).indexOf(\"SEQUENCE\") === 0;\n        } catch (err) {\n            // Error processing signature, output without further breakout\n        }\n\n        if (breakoutSig) { // DSA or ECDSA\n            sigStr = `  r:              ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)}\n  s:              ${formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18)}`;\n        } else { // RSA or unknown\n            sigStr = `  Signature:      ${formatByteStr(sig, 16, 18)}`;\n        }\n\n        // Extensions\n        try {\n            extensions = cert.getInfo().split(\"X509v3 Extensions:\\n\")[1].split(\"signature\")[0];\n        } catch (err) {}\n\n        const issuerStr = formatDnObj(issuer, 2),\n            nbDate = formatDate(cert.getNotBefore()),\n            naDate = formatDate(cert.getNotAfter()),\n            subjectStr = formatDnObj(subject, 2);\n\n        return `Version:          ${cert.version} (0x${Utils.hex(cert.version - 1)})\nSerial number:    ${new r.BigInteger(sn, 16).toString()} (0x${sn})\nAlgorithm ID:     ${cert.getSignatureAlgorithmField()}\nValidity\n  Not Before:     ${nbDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotBefore()})\n  Not After:      ${naDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotAfter()})\nIssuer\n${issuerStr}\nSubject\n${subjectStr}\nFingerprints\n  MD5:            ${runHash(\"md5\", hex)}\n  SHA1:           ${runHash(\"sha1\", hex)}\n  SHA256:         ${runHash(\"sha256\", hex)}\nPublic Key\n${pkStr.slice(0, -1)}\nCertificate Signature\n  Algorithm:      ${cert.getSignatureAlgorithmName()}\n${sigStr}\n\nExtensions\n${extensions}`;\n    }\n\n}\n\n/**\n * Formats dates.\n *\n * @param {string} dateStr\n * @returns {string}\n */\nfunction formatDate (dateStr) {\n    if (dateStr.length === 13) { // UTC Time\n        dateStr = (dateStr[0] < \"5\" ? \"20\" : \"19\") + dateStr;\n    }\n    return dateStr[6] + dateStr[7] + \"/\" +\n        dateStr[4] + dateStr[5] + \"/\" +\n        dateStr[0] + dateStr[1] + dateStr[2] + dateStr[3] + \" \" +\n        dateStr[8] + dateStr[9] + \":\" +\n        dateStr[10] + dateStr[11] + \":\" +\n        dateStr[12] + dateStr[13];\n}\n\nexport default ParseX509Certificate;\n"
  },
  {
    "path": "src/core/operations/PlayMedia.mjs",
    "content": "/**\n * @author anthony-arnold [anthony.arnold@uqconnect.edu.au]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport { fromBase64, toBase64 } from \"../lib/Base64.mjs\";\nimport { fromHex } from \"../lib/Hex.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { isType, detectFileType } from \"../lib/FileType.mjs\";\n\n/**\n * PlayMedia operation\n */\nclass PlayMedia extends Operation {\n\n    /**\n     * PlayMedia constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Play Media\";\n        this.module = \"Default\";\n        this.description = \"Plays the input as audio or video depending on the type.<br><br>Tags: sound, movie, mp3, mp4, mov, webm, wav, ogg\";\n        this.infoURL = \"\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Base64\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray} The multimedia data as bytes.\n     */\n    run(input, args) {\n        const [inputFormat] = args;\n\n        if (!input.length) return [];\n\n        // Convert input to raw bytes\n        switch (inputFormat) {\n            case \"Hex\":\n                input = fromHex(input);\n                break;\n            case \"Base64\":\n                // Don't trust the Base64 entered by the user.\n                // Unwrap it first, then re-encode later.\n                input = fromBase64(input, undefined, \"byteArray\");\n                break;\n            case \"Raw\":\n            default:\n                input = Utils.strToByteArray(input);\n                break;\n        }\n\n\n        // Determine file type\n        if (!isType(/^(audio|video)/, input)) {\n            throw new OperationError(\"Invalid or unrecognised file type\");\n        }\n\n        return input;\n    }\n\n    /**\n     * Displays an audio or video element that may be able to play the media\n     * file.\n     *\n     * @param {byteArray} data Data containing an audio or video file.\n     * @returns {string} Markup to display a media player.\n     */\n    async present(data) {\n        if (!data.length) return \"\";\n\n        const types = detectFileType(data);\n        const matches = /^audio|video/.exec(types[0].mime);\n        if (!matches) {\n            throw new OperationError(\"Invalid file type\");\n        }\n        const dataURI = `data:${types[0].mime};base64,${toBase64(data)}`;\n        const element = matches[0];\n\n        let html = `<${element} src='${dataURI}' type='${types[0].mime}' controls>`;\n        html += \"<p>Unsupported media type.</p>\";\n        html += `</${element}>`;\n        return html;\n    }\n}\n\nexport default PlayMedia;\n"
  },
  {
    "path": "src/core/operations/PowerSet.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Power Set operation\n */\nclass PowerSet extends Operation {\n\n    /**\n     * Power set constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Power Set\";\n        this.module = \"Default\";\n        this.description = \"Calculates all the subsets of a set.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Power_set\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Item delimiter\",\n                type: \"binaryString\",\n                value: \",\"\n            },\n        ];\n    }\n\n    /**\n     * Generate the power set\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        [this.itemDelimiter] = args;\n        // Split and filter empty strings\n        const inputArray = input.split(this.itemDelimiter).filter(a => a);\n\n        if (inputArray.length) {\n            return this.runPowerSet(inputArray);\n        }\n\n        return \"\";\n    }\n\n    /**\n     * Return the power set of the inputted set.\n     *\n     * @param {Object[]} a\n     * @returns {Object[]}\n     */\n    runPowerSet(a) {\n        // empty array items getting picked up\n        a = a.filter(i => i.length);\n        if (!a.length) {\n            return [];\n        }\n\n        /**\n         * Decimal to binary function\n         * @param {*} dec\n         */\n        const toBinary = (dec) => (dec >>> 0).toString(2);\n        const result = new Set();\n        // Get the decimal number to make a binary as long as the input\n        const maxBinaryValue = parseInt(Number(a.map(i => \"1\").reduce((p, c) => p + c)), 2);\n        // Make an array of each binary number from 0 to maximum\n        const binaries = [...Array(maxBinaryValue + 1).keys()]\n            .map(toBinary)\n            .map(i => i.padStart(toBinary(maxBinaryValue).length, \"0\"));\n\n        // XOR the input with each binary to get each unique permutation\n        binaries.forEach((binary) => {\n            const split = binary.split(\"\");\n            result.add(a.filter((item, index) => split[index] === \"1\"));\n        });\n\n        // map for formatting & put in length order.\n        return [...result]\n            .map(r => r.join(this.itemDelimiter)).sort((a, b) => a.length - b.length)\n            .map(i => `${i}\\n`).join(\"\");\n    }\n}\n\nexport default PowerSet;\n"
  },
  {
    "path": "src/core/operations/ProtobufDecode.mjs",
    "content": "/**\n * @author GCHQ Contributor [3]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Protobuf from \"../lib/Protobuf.mjs\";\n\n/**\n * Protobuf Decode operation\n */\nclass ProtobufDecode extends Operation {\n\n    /**\n     * ProtobufDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Protobuf Decode\";\n        this.module = \"Protobuf\";\n        this.description = \"Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.<br><br>If a .proto schema is defined, the encoded data will be decoded with reference to the schema. Only one message instance will be decoded. <br><br><u>Show Unknown Fields</u><br>When a schema is used, this option shows fields that are present in the input data but not defined in the schema.<br><br><u>Show Types</u><br>Show the type of a field next to its name. For undefined fields, the wiretype and example types are shown instead.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Protocol_Buffers\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"JSON\";\n        this.args = [\n            {\n                name: \"Schema (.proto text)\",\n                type: \"text\",\n                value: \"\",\n                rows: 8,\n                hint: \"Drag and drop is enabled on this ingredient\"\n            },\n            {\n                name: \"Show Unknown Fields\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Show Types\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        try {\n            return Protobuf.decode(input, args);\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default ProtobufDecode;\n"
  },
  {
    "path": "src/core/operations/ProtobufEncode.mjs",
    "content": "/**\n * @author GCHQ Contributor [3]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Protobuf from \"../lib/Protobuf.mjs\";\n\n/**\n * Protobuf Encode operation\n */\nclass ProtobufEncode extends Operation {\n\n    /**\n     * ProtobufEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Protobuf Encode\";\n        this.module = \"Protobuf\";\n        this.description = \"Encodes a valid JSON object into a protobuf byte array using the input .proto schema.\";\n        this.infoURL = \"https://developers.google.com/protocol-buffers/docs/encoding\";\n        this.inputType = \"JSON\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Schema (.proto text)\",\n                type: \"text\",\n                value: \"\",\n                rows: 8,\n                hint: \"Drag and drop is enabled on this ingredient\"\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        try {\n            return Protobuf.encode(input, args);\n        } catch (error) {\n            throw new OperationError(error);\n        }\n    }\n\n}\n\nexport default ProtobufEncode;\n"
  },
  {
    "path": "src/core/operations/PseudoRandomIntegerGenerator.mjs",
    "content": "/**\n * @author cktgh [chankaitung@gmail.com]\n * @copyright Crown Copyright 2026\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\nimport Utils, { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n/**\n * Pseudo-Random Integer Generator operation\n */\nclass PseudoRandomIntegerGenerator extends Operation {\n\n    // in theory 2**53 is the max range, but we use Number.MAX_SAFE_INTEGER (2**53 - 1) as it is more consistent.\n    static MAX_RANGE = Number.MAX_SAFE_INTEGER;\n    // arbitrary choice\n    static BUFFER_SIZE = 1024;\n\n    /**\n     * PseudoRandomIntegerGenerator constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Pseudo-Random Integer Generator\";\n        this.module = \"Ciphers\";\n        this.description = \"A cryptographically-secure pseudo-random number generator (PRNG).<br><br>Generates random integers within a specified range using the browser's built-in <code>crypto.getRandomValues()</code> method if available.<br><br>The supported range of integers is from <code>-(2^53 - 1)</code> to <code>(2^53 - 1)</code>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Pseudorandom_number_generator\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Number of Integers\",\n                \"type\": \"number\",\n                \"value\": 1,\n                \"min\": 1\n            },\n            {\n                \"name\": \"Min Value\",\n                \"type\": \"number\",\n                \"value\": 0,\n                \"min\": Number.MIN_SAFE_INTEGER,\n                \"max\": Number.MAX_SAFE_INTEGER\n            },\n            {\n                \"name\": \"Max Value\",\n                \"type\": \"number\",\n                \"value\": 99,\n                \"min\": Number.MIN_SAFE_INTEGER,\n                \"max\": Number.MAX_SAFE_INTEGER\n            },\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\", \"Decimal\"]\n            }\n        ];\n\n        // not using BigUint64Array to avoid BigInt handling overhead\n        this.randomBuffer = new Uint32Array(PseudoRandomIntegerGenerator.BUFFER_SIZE);\n        this.randomBufferOffset = PseudoRandomIntegerGenerator.BUFFER_SIZE;\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [numInts, minInt, maxInt, delimiter, outputType] = args;\n\n        if (minInt === null || maxInt === null) return \"\";\n\n        const min = Math.ceil(minInt);\n        const max = Math.floor(maxInt);\n        const delim = Utils.charRep(delimiter || \"Space\");\n\n        if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max)) {\n            throw new OperationError(\"Min and Max must be between `-(2^53 - 1)` and `2^53 - 1`.\");\n        }\n        if (min > max) {\n            throw new OperationError(\"Min cannot be larger than Max.\");\n        }\n        const range = max - min + 1; // inclusive range\n        if (range > PseudoRandomIntegerGenerator.MAX_RANGE) {\n            throw new OperationError(\"Range between Min and Max cannot be larger than `2^53`\");\n        }\n\n        // as large as possible while divisible by range\n        const rejectionThreshold = PseudoRandomIntegerGenerator.MAX_RANGE - (PseudoRandomIntegerGenerator.MAX_RANGE % range);\n        const output = [];\n        for (let i = 0; i < numInts; i++) {\n            const result = this._generateRandomValue(rejectionThreshold);\n            const intValue = min + (result % range);\n\n            switch (outputType) {\n                case \"Hex\":\n                    output.push(intValue.toString(16));\n                    break;\n                case \"Decimal\":\n                    output.push(intValue.toString(10));\n                    break;\n                case \"Raw\":\n                default:\n                    output.push(Utils.chr(intValue));\n            }\n        }\n\n        if (outputType === \"Raw\") {\n            return output.join(\"\");\n        }\n        return output.join(delim);\n    }\n\n    /**\n     * Generate a random value, result will be less than the rejection threshold (exclusive).\n     *\n     * @param {number} rejectionThreshold\n     * @returns {number}\n     */\n    _generateRandomValue(rejectionThreshold) {\n        let result;\n        do {\n            if (this.randomBufferOffset + 2 > this.randomBuffer.length) {\n                this._resetRandomBuffer();\n            }\n            // stitching a 53 bit number; not using BigUint64Array to avoid BigInt handling overhead\n            result = (this.randomBuffer[this.randomBufferOffset++] & 0x1f_ffff) * 0x1_0000_0000 +\n                this.randomBuffer[this.randomBufferOffset++];\n        } while (result >= rejectionThreshold);\n\n        return result;\n    }\n\n    /**\n     * Fill random buffer with new random values and rseet the offset.\n     */\n    _resetRandomBuffer() {\n        if (isWorkerEnvironment() && self.crypto) {\n            self.crypto.getRandomValues(this.randomBuffer);\n        } else {\n            const bytes = forge.random.getBytesSync(this.randomBuffer.length * 4);\n            for (let j = 0; j < this.randomBuffer.length; j++) {\n                this.randomBuffer[j] = (bytes.charCodeAt(j * 4) << 24) |\n                    (bytes.charCodeAt(j * 4 + 1) << 16) |\n                    (bytes.charCodeAt(j * 4 + 2) << 8) |\n                    bytes.charCodeAt(j * 4 + 3);\n            }\n        }\n        this.randomBufferOffset = 0;\n    }\n\n}\n\nexport default PseudoRandomIntegerGenerator;\n"
  },
  {
    "path": "src/core/operations/PseudoRandomNumberGenerator.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\nimport BigNumber from \"bignumber.js\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * Pseudo-Random Number Generator operation\n */\nclass PseudoRandomNumberGenerator extends Operation {\n\n    /**\n     * PseudoRandomNumberGenerator constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Pseudo-Random Number Generator\";\n        this.module = \"Ciphers\";\n        this.description = \"A cryptographically-secure pseudo-random number generator (PRNG).<br><br>This operation uses the browser's built-in <code>crypto.getRandomValues()</code> method if available. If this cannot be found, it falls back to a Fortuna-based PRNG algorithm.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Pseudorandom_number_generator\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Number of bytes\",\n                \"type\": \"number\",\n                \"value\": 32\n            },\n            {\n                \"name\": \"Output as\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Integer\", \"Byte array\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [numBytes, outputAs] = args;\n\n        let bytes;\n\n        if (isWorkerEnvironment() && self.crypto) {\n            bytes = new ArrayBuffer(numBytes);\n            const CHUNK_SIZE = 65536;\n            for (let i = 0; i < numBytes; i += CHUNK_SIZE) {\n                self.crypto.getRandomValues(new Uint8Array(bytes, i, Math.min(numBytes - i, CHUNK_SIZE)));\n            }\n            bytes = Utils.arrayBufferToStr(bytes);\n        } else {\n            bytes = forge.random.getBytesSync(numBytes);\n        }\n\n        let value = new BigNumber(0),\n            i;\n\n        switch (outputAs) {\n            case \"Hex\":\n                return forge.util.bytesToHex(bytes);\n            case \"Integer\":\n                for (i = bytes.length - 1; i >= 0; i--) {\n                    value = value.times(256).plus(bytes.charCodeAt(i));\n                }\n                return value.toFixed();\n            case \"Byte array\":\n                return JSON.stringify(Utils.strToCharcode(bytes));\n            case \"Raw\":\n            default:\n                return bytes;\n        }\n    }\n\n}\n\nexport default PseudoRandomNumberGenerator;\n"
  },
  {
    "path": "src/core/operations/PubKeyFromCert.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Public Key from Certificate operation\n */\nclass PubKeyFromCert extends Operation {\n\n    /**\n     * PubKeyFromCert constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Public Key from Certificate\";\n        this.module = \"PublicKey\";\n        this.description = \"Extracts the Public Key from a Certificate.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/X.509\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let output = \"\";\n        let match;\n        const regex = /-----BEGIN CERTIFICATE-----/g;\n        while ((match = regex.exec(input)) !== null) {\n            // find corresponding end tag\n            const indexBase64 = match.index + match[0].length;\n            const footer = \"-----END CERTIFICATE-----\";\n            const indexFooter = input.indexOf(footer, indexBase64);\n            if (indexFooter === -1) {\n                throw new OperationError(`PEM footer '${footer}' not found`);\n            }\n\n            const certPem = input.substring(match.index, indexFooter + footer.length);\n            const cert = new r.X509();\n            cert.readCertPEM(certPem);\n            let pubKey;\n            try {\n                pubKey = cert.getPublicKey();\n            } catch {\n                throw new OperationError(\"Unsupported public key type\");\n            }\n            const pubKeyPem = r.KEYUTIL.getPEM(pubKey);\n\n            // PEM ends with '\\n', so a new key always starts on a new line\n            output += pubKeyPem;\n        }\n        return output;\n    }\n}\n\nexport default PubKeyFromCert;\n"
  },
  {
    "path": "src/core/operations/PubKeyFromPrivKey.mjs",
    "content": "/**\n * @author cplussharp\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport r from \"jsrsasign\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Public Key from Private Key operation\n */\nclass PubKeyFromPrivKey extends Operation {\n\n    /**\n     * PubKeyFromPrivKey constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Public Key from Private Key\";\n        this.module = \"PublicKey\";\n        this.description = \"Extracts the Public Key from a Private Key.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/PKCS_8\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let output = \"\";\n        let match;\n        const regex = /-----BEGIN ((RSA |EC |DSA )?PRIVATE KEY)-----/g;\n        while ((match = regex.exec(input)) !== null) {\n            // find corresponding end tag\n            const indexBase64 = match.index + match[0].length;\n            const footer = `-----END ${match[1]}-----`;\n            const indexFooter = input.indexOf(footer, indexBase64);\n            if (indexFooter === -1) {\n                throw new OperationError(`PEM footer '${footer}' not found`);\n            }\n\n            const privKeyPem = input.substring(match.index, indexFooter + footer.length);\n            let privKey;\n            try {\n                privKey = r.KEYUTIL.getKey(privKeyPem);\n            } catch (err) {\n                throw new OperationError(`Unsupported key type: ${err}`);\n            }\n            let pubKey;\n            if (privKey.type && privKey.type === \"EC\") {\n                pubKey = new r.KJUR.crypto.ECDSA({ curve: privKey.curve });\n                pubKey.setPublicKeyHex(privKey.generatePublicKeyHex());\n            } else if (privKey.type && privKey.type === \"DSA\") {\n                if (!privKey.y) {\n                    throw new OperationError(`DSA Private Key in PKCS#8 is not supported`);\n                }\n                pubKey = new r.KJUR.crypto.DSA();\n                pubKey.setPublic(privKey.p, privKey.q, privKey.g, privKey.y);\n            } else if (privKey.n && privKey.e) {\n                pubKey = new r.RSAKey();\n                pubKey.setPublic(privKey.n, privKey.e);\n            } else {\n                throw new OperationError(`Unsupported key type`);\n            }\n            const pubKeyPem = r.KEYUTIL.getPEM(pubKey);\n\n            // PEM ends with '\\n', so a new key always starts on a new line\n            output += pubKeyPem;\n        }\n        return output;\n    }\n}\n\nexport default PubKeyFromPrivKey;\n"
  },
  {
    "path": "src/core/operations/RAKE.mjs",
    "content": "/**\n * @author sw5678\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * RAKE operation\n */\nclass RAKE extends Operation {\n\n    /**\n     * RAKE constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RAKE\";\n        this.module = \"Default\";\n        this.description = [\n            \"Rapid Keyword Extraction (RAKE)\",\n            \"<br><br>\",\n            \"RAKE is a domain-independent keyword extraction algorithm in Natural Language Processing.\",\n            \"<br><br>\",\n            \"The list of stop words are from the NLTK python package\",\n        ].join(\"\\n\");\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Word Delimiter (Regex)\",\n                type: \"text\",\n                value: \"\\\\s\"\n            },\n            {\n                name: \"Sentence Delimiter (Regex)\",\n                type: \"text\",\n                value: \"\\\\.\\\\s|\\\\n\"\n            },\n            {\n                name: \"Stop Words\",\n                type: \"text\",\n                value: \"i,me,my,myself,we,our,ours,ourselves,you,you're,you've,you'll,you'd,your,yours,yourself,yourselves,he,him,his,himself,she,she's,her,hers,herself,it,it's,its,itsef,they,them,their,theirs,themselves,what,which,who,whom,this,that,that'll,these,those,am,is,are,was,were,be,been,being,have,has,had,having,do,does',did,doing,a,an,the,and,but,if,or,because,as,until,while,of,at,by,for,with,about,against,between,into,through,during,before,after,above,below,to,from,up,down,in,out,on,off,over,under,again,further,then,once,here,there,when,where,why,how,all,any,both,each,few,more,most,other,some,such,no,nor,not,only,own,same,so,than,too,very,s,t,can,will,just,don,don't,should,should've,now,d,ll,m,o,re,ve,y,ain,aren,aren't,couldn,couldn't,didn,didn't,doesn,doesn't,hadn,hadn't,hasn,hasn't,haven,haven't,isn,isn't,ma,mightn,mightn't,mustn,mustn't,needn,needn't,shan,shan't,shouldn,shouldn't,wasn,wasn't,weren,weren't,won,won't,wouldn,wouldn't\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n\n        // Get delimiter regexs\n        const wordDelim =  new RegExp(args[0], \"g\");\n        const sentDelim = new RegExp(args[1], \"g\");\n\n        // Deduplicate the stop words and add the empty string\n        const stopWords = args[2].toLowerCase().replace(/ /g, \"\").split(\",\").unique();\n        stopWords.push(\"\");\n\n        // Lower case input and remove start and ending whitespace\n        input = input.toLowerCase().trim();\n\n        // Get tokens, token count, and phrases\n        const tokens = [];\n        const wordFrequencies = [];\n        let phrases = [];\n\n        // Build up list of phrases and token counts\n        const sentences = input.split(sentDelim);\n        for (const sent of sentences) {\n\n            // Split sentence into words\n            const splitSent = sent.split(wordDelim);\n            let startIndex = 0;\n\n            for (let i = 0; i < splitSent.length; i++) {\n                const token = splitSent[i];\n                if (stopWords.includes(token)) {\n                    // If token is stop word then split to create phrase\n                    phrases.push(splitSent.slice(startIndex, i));\n                    startIndex = i + 1;\n                } else {\n                    // If token is not a stop word add to the count of the list of words\n                    if (tokens.includes(token)) {\n                        wordFrequencies[tokens.indexOf(token)]+=1;\n                    } else {\n                        tokens.push(token);\n                        wordFrequencies.push(1);\n                    }\n                }\n            }\n            phrases.push(splitSent.slice(startIndex));\n        }\n\n        // remove empty phrases\n        phrases = phrases.filter(subArray => subArray.length > 0);\n\n        // Remove duplicate phrases\n        phrases = phrases.unique();\n\n        // Generate word_degree_matrix and populate\n        const wordDegreeMatrix = Array(tokens.length).fill().map(() => Array(tokens.length).fill(0));\n        for (const phrase of phrases) {\n            for (const word1 of phrase) {\n                for (const word2 of phrase) {\n                    wordDegreeMatrix[tokens.indexOf(word1)][tokens.indexOf(word2)]++;\n                }\n            }\n        }\n\n        // Calculate degree score for each token\n        const degreeScores = Array(tokens.length).fill(0);\n        for (let i=0; i<tokens.length; i++) {\n            let wordDegree = 0;\n            for (let j=0; j<wordDegreeMatrix.length; j++) {\n                wordDegree += wordDegreeMatrix[j][i];\n            }\n            degreeScores[i] = wordDegree / wordFrequencies[i];\n        }\n\n        // Calculate score for each phrase\n        const scores = phrases.map(function (phrase) {\n            let score = 0;\n            phrase.forEach(function (token) {\n                score += degreeScores[tokens.indexOf(token)];\n            });\n            return new Array(score, phrase.join(\" \"));\n        });\n        scores.sort((a, b) => b[0] - a[0]);\n        scores.unshift(new Array(\"Scores: \", \"Keywords: \"));\n\n        // Output works with the 'To Table' functionality already built into CC\n        return scores.map(function (score) {\n            return score.join(\", \");\n        }).join(\"\\n\");\n    }\n}\n\nexport default RAKE;\n"
  },
  {
    "path": "src/core/operations/RC2Decrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\n\n/**\n * RC2 Decrypt operation\n */\nclass RC2Decrypt extends Operation {\n\n    /**\n     * RC2Decrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RC2 Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RC2\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            [,, inputType, outputType] = args,\n            decipher = forge.rc2.createDecryptionCipher(key);\n\n        input = Utils.convertToByteString(input, inputType);\n\n        decipher.start(iv || null);\n        decipher.update(forge.util.createBuffer(input));\n        decipher.finish();\n\n        return outputType === \"Hex\" ? decipher.output.toHex() : decipher.output.getBytes();\n    }\n\n}\n\nexport default RC2Decrypt;\n"
  },
  {
    "path": "src/core/operations/RC2Encrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport forge from \"node-forge\";\n\n\n/**\n * RC2 Encrypt operation\n */\nclass RC2Encrypt extends Operation {\n\n    /**\n     * RC2Encrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RC2 Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RC2\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteString(args[1].string, args[1].option),\n            [,, inputType, outputType] = args,\n            cipher = forge.rc2.createEncryptionCipher(key);\n\n        input = Utils.convertToByteString(input, inputType);\n\n        cipher.start(iv || null);\n        cipher.update(forge.util.createBuffer(input));\n        cipher.finish();\n\n        return outputType === \"Hex\" ? cipher.output.toHex() : cipher.output.getBytes();\n    }\n\n}\n\nexport default RC2Encrypt;\n"
  },
  {
    "path": "src/core/operations/RC4.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport CryptoJS from \"crypto-js\";\nimport { format } from \"../lib/Ciphers.mjs\";\n\n/**\n * RC4 operation\n */\nclass RC4 extends Operation {\n\n    /**\n     * RC4 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RC4\";\n        this.module = \"Ciphers\";\n        this.description = \"RC4 (also known as ARC4) is a widely-used stream cipher designed by Ron Rivest. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RC4\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Passphrase\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"UTF8\", \"UTF16\", \"UTF16LE\", \"UTF16BE\", \"Latin1\", \"Hex\", \"Base64\"]\n            },\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"Latin1\", \"UTF8\", \"UTF16\", \"UTF16LE\", \"UTF16BE\", \"Hex\", \"Base64\"]\n            },\n            {\n                \"name\": \"Output format\",\n                \"type\": \"option\",\n                \"value\": [\"Latin1\", \"UTF8\", \"UTF16\", \"UTF16LE\", \"UTF16BE\", \"Hex\", \"Base64\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const message = format[args[1]].parse(input),\n            passphrase = format[args[0].option].parse(args[0].string),\n            encrypted = CryptoJS.RC4.encrypt(message, passphrase);\n\n        return encrypted.ciphertext.toString(format[args[2]]);\n    }\n\n    /**\n     * Highlight RC4\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight RC4 in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default RC4;\n"
  },
  {
    "path": "src/core/operations/RC4Drop.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { format } from \"../lib/Ciphers.mjs\";\nimport CryptoJS from \"crypto-js\";\n\n/**\n * RC4 Drop operation\n */\nclass RC4Drop extends Operation {\n\n    /**\n     * RC4Drop constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RC4 Drop\";\n        this.module = \"Ciphers\";\n        this.description = \"It was discovered that the first few bytes of the RC4 keystream are strongly non-random and leak information about the key. We can defend against this attack by discarding the initial portion of the keystream. This modified algorithm is traditionally called RC4-drop.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RC4#Fluhrer,_Mantin_and_Shamir_attack\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Passphrase\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"UTF8\", \"UTF16\", \"UTF16LE\", \"UTF16BE\", \"Latin1\", \"Hex\", \"Base64\"]\n            },\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"Latin1\", \"UTF8\", \"UTF16\", \"UTF16LE\", \"UTF16BE\", \"Hex\", \"Base64\"]\n            },\n            {\n                \"name\": \"Output format\",\n                \"type\": \"option\",\n                \"value\": [\"Latin1\", \"UTF8\", \"UTF16\", \"UTF16LE\", \"UTF16BE\", \"Hex\", \"Base64\"]\n            },\n            {\n                \"name\": \"Number of dwords to drop\",\n                \"type\": \"number\",\n                \"value\": 192\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const message = format[args[1]].parse(input),\n            passphrase = format[args[0].option].parse(args[0].string),\n            drop = args[3],\n            encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop });\n\n        return encrypted.ciphertext.toString(format[args[2]]);\n    }\n\n    /**\n     * Highlight RC4 Drop\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight RC4 Drop in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default RC4Drop;\n"
  },
  {
    "path": "src/core/operations/RC6Decrypt.mjs",
    "content": "/**\n * @author Medjedtxm\n * @copyright Crown Copyright 2026\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { decryptRC6, getBlockSize, getDefaultRounds } from \"../lib/RC6.mjs\";\n\n/**\n * RC6 Decrypt operation\n */\nclass RC6Decrypt extends Operation {\n\n    /**\n     * RC6Decrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RC6 Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists.<br><br>RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.<br><br><b>IV:</b> The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, the PKCS#7 padding scheme is used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RC6\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Padding\",\n                \"type\": \"option\",\n                \"value\": [\"PKCS5\", \"NO\", \"ZERO\", \"RANDOM\", \"BIT\"]\n            },\n            {\n                \"name\": \"Word Size\",\n                \"type\": \"number\",\n                \"value\": 32,\n                \"min\": 8,\n                \"max\": 256,\n                \"step\": 8\n            },\n            {\n                \"name\": \"Rounds\",\n                \"type\": \"number\",\n                \"value\": 20,\n                \"min\": 1,\n                \"max\": 255\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            [,, mode, inputType, outputType, padding, wordSize, rounds] = args;\n\n        // Validate word size\n        if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)\n            throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);\n\n        const blockSize = getBlockSize(wordSize);\n        const defaultRounds = getDefaultRounds(wordSize);\n\n        if (iv.length !== blockSize && iv.length !== 0 && mode !== \"ECB\")\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nRC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n\n        if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)\n            throw new OperationError(`Invalid number of rounds: ${rounds}\n\nRounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);\n\n        // Default IV to null bytes if empty (like AES)\n        const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;\n\n        input = Utils.convertToByteArray(input, inputType);\n        const output = decryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);\n        return outputType === \"Hex\" ? toHex(output, \"\") : Utils.byteArrayToUtf8(output);\n    }\n\n}\n\nexport default RC6Decrypt;\n"
  },
  {
    "path": "src/core/operations/RC6Encrypt.mjs",
    "content": "/**\n * @author Medjedtxm\n * @copyright Crown Copyright 2026\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { encryptRC6, getBlockSize, getDefaultRounds } from \"../lib/RC6.mjs\";\n\n/**\n * RC6 Encrypt operation\n */\nclass RC6Encrypt extends Operation {\n\n    /**\n     * RC6Encrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RC6 Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists.<br><br>RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.<br><br><b>IV:</b> The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, the PKCS#7 padding scheme is used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RC6\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Padding\",\n                \"type\": \"option\",\n                \"value\": [\"PKCS5\", \"NO\", \"ZERO\", \"RANDOM\", \"BIT\"]\n            },\n            {\n                \"name\": \"Word Size\",\n                \"type\": \"number\",\n                \"value\": 32,\n                \"min\": 8,\n                \"max\": 256,\n                \"step\": 8\n            },\n            {\n                \"name\": \"Rounds\",\n                \"type\": \"number\",\n                \"value\": 20,\n                \"min\": 1,\n                \"max\": 255\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            [,, mode, inputType, outputType, padding, wordSize, rounds] = args;\n\n        // Validate word size\n        if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)\n            throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);\n\n        const blockSize = getBlockSize(wordSize);\n        const defaultRounds = getDefaultRounds(wordSize);\n\n        if (iv.length !== blockSize && iv.length !== 0 && mode !== \"ECB\")\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nRC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n\n        if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)\n            throw new OperationError(`Invalid number of rounds: ${rounds}\n\nRounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);\n\n        // Default IV to null bytes if empty (like AES)\n        const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;\n\n        input = Utils.convertToByteArray(input, inputType);\n        const output = encryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);\n        return outputType === \"Hex\" ? toHex(output, \"\") : Utils.byteArrayToUtf8(output);\n    }\n\n}\n\nexport default RC6Encrypt;\n"
  },
  {
    "path": "src/core/operations/RIPEMD.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * RIPEMD operation\n */\nclass RIPEMD extends Operation {\n\n    /**\n     * RIPEMD constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RIPEMD\";\n        this.module = \"Crypto\";\n        this.description = \"RIPEMD (RACE Integrity Primitives Evaluation Message Digest) is a family of cryptographic hash functions developed in Leuven, Belgium, by Hans Dobbertin, Antoon Bosselaers and Bart Preneel at the COSIC research group at the Katholieke Universiteit Leuven, and first published in 1996.<br><br>RIPEMD was based upon the design principles used in MD4, and is similar in performance to the more popular SHA-1.<br><br>\";\n        this.infoURL = \"https://wikipedia.org/wiki/RIPEMD\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Size\",\n                \"type\": \"option\",\n                \"value\": [\"320\", \"256\", \"160\", \"128\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const size = args[0];\n        return runHash(\"ripemd\" + size, input);\n    }\n\n}\n\nexport default RIPEMD;\n"
  },
  {
    "path": "src/core/operations/ROT13.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n\n/**\n * ROT13 operation.\n */\nclass ROT13 extends Operation {\n\n    /**\n     * ROT13 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ROT13\";\n        this.module = \"Default\";\n        this.description = \"A simple caesar substitution cipher which rotates alphabet characters by the specified amount (default 13).\";\n        this.infoURL = \"https://wikipedia.org/wiki/ROT13\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Rotate lower case chars\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Rotate upper case chars\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Rotate numbers\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Amount\",\n                type: \"number\",\n                value: 13\n            },\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const output = input,\n            rot13Lowercase = args[0],\n            rot13Upperacse = args[1],\n            rotNumbers = args[2];\n        let amount = args[3],\n            amountNumbers = args[3];\n\n        if (amount) {\n            if (amount < 0) {\n                amount = 26 - (Math.abs(amount) % 26);\n                amountNumbers = 10 - (Math.abs(amountNumbers) % 10);\n            }\n\n            for (let i = 0; i < input.length; i++) {\n                let chr = input[i];\n                if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case\n                    chr = (chr - 65 + amount) % 26;\n                    output[i] = chr + 65;\n                } else if (rot13Lowercase && chr >= 97 && chr <= 122) { // Lower case\n                    chr = (chr - 97 + amount) % 26;\n                    output[i] = chr + 97;\n                } else if (rotNumbers && chr >= 48 && chr <= 57) { // Numbers\n                    chr = (chr - 48 + amountNumbers) % 10;\n                    output[i] = chr + 48;\n                }\n            }\n        }\n        return output;\n    }\n\n    /**\n     * Highlight ROT13\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight ROT13 in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n}\n\nexport default ROT13;\n"
  },
  {
    "path": "src/core/operations/ROT13BruteForce.mjs",
    "content": "/**\n * @author MikeCAT\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * ROT13 Brute Force operation.\n */\nclass ROT13BruteForce extends Operation {\n\n    /**\n     * ROT13BruteForce constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ROT13 Brute Force\";\n        this.module = \"Default\";\n        this.description = \"Try all meaningful amounts for ROT13.<br><br>Optionally you can enter your known plaintext (crib) to filter the result.\";\n        this.infoURL = \"https://wikipedia.org/wiki/ROT13\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Rotate lower case chars\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Rotate upper case chars\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Rotate numbers\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sample length\",\n                type: \"number\",\n                value: 100\n            },\n            {\n                name: \"Sample offset\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Print amount\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Crib (known plaintext string)\",\n                type: \"string\",\n                value: \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [rotateLower, rotateUpper, rotateNum, sampleLength, sampleOffset, printAmount, crib] = args;\n        const sample = input.slice(sampleOffset, sampleOffset + sampleLength);\n        const cribLower = crib.toLowerCase();\n        const lowerStart = \"a\".charCodeAt(0), upperStart = \"A\".charCodeAt(0), numStart = \"0\".charCodeAt(0);\n        const result = [];\n        for (let amount = 1; amount < 26; amount++) {\n            const rotated = sample.slice();\n            for (let i = 0; i < rotated.length; i++) {\n                if (rotateLower && lowerStart <= rotated[i] && rotated[i] < lowerStart + 26) {\n                    rotated[i] = (rotated[i] - lowerStart + amount) % 26 + lowerStart;\n                } else if (rotateUpper && upperStart <= rotated[i] && rotated[i] < upperStart + 26) {\n                    rotated[i] = (rotated[i] - upperStart + amount) % 26 + upperStart;\n                } else if (rotateNum && numStart <= rotated[i] && rotated[i] < numStart + 10) {\n                    rotated[i] = (rotated[i] - numStart + amount) % 10 + numStart;\n                }\n            }\n            const rotatedString = Utils.byteArrayToUtf8(rotated);\n            if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) {\n                const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString);\n                if (printAmount) {\n                    const amountStr = \"Amount = \" + (\" \" + amount).slice(-2) + \": \";\n                    result.push(amountStr + rotatedStringEscaped);\n                } else {\n                    result.push(rotatedStringEscaped);\n                }\n            }\n        }\n        return result.join(\"\\n\");\n    }\n}\n\nexport default ROT13BruteForce;\n"
  },
  {
    "path": "src/core/operations/ROT47.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n\n/**\n * ROT47 operation.\n */\nclass ROT47 extends Operation {\n\n    /**\n     * ROT47 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ROT47\";\n        this.module = \"Default\";\n        this.description = \"A slightly more complex variation of a caesar cipher, which includes ASCII characters from 33 '!' to 126 '~'. Default rotation: 47.\";\n        this.infoURL = \"https://wikipedia.org/wiki/ROT13#Variants\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Amount\",\n                type: \"number\",\n                value: 47\n            },\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const output = input;\n        let amount = args[0],\n            chr;\n\n        if (amount) {\n            if (amount < 0) {\n                amount = 94 - (Math.abs(amount) % 94);\n            }\n\n            for (let i = 0; i < input.length; i++) {\n                chr = input[i];\n                if (chr >= 33 && chr <= 126) {\n                    chr = (chr - 33 + amount) % 94;\n                    output[i] = chr + 33;\n                }\n            }\n        }\n        return output;\n    }\n\n    /**\n     * Highlight ROT47\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight ROT47 in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n}\n\nexport default ROT47;\n"
  },
  {
    "path": "src/core/operations/ROT47BruteForce.mjs",
    "content": "/**\n * @author MikeCAT\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * ROT47 Brute Force operation.\n */\nclass ROT47BruteForce extends Operation {\n\n    /**\n     * ROT47BruteForce constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"ROT47 Brute Force\";\n        this.module = \"Default\";\n        this.description = \"Try all meaningful amounts for ROT47.<br><br>Optionally you can enter your known plaintext (crib) to filter the result.\";\n        this.infoURL = \"https://wikipedia.org/wiki/ROT13#Variants\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Sample length\",\n                type: \"number\",\n                value: 100\n            },\n            {\n                name: \"Sample offset\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Print amount\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Crib (known plaintext string)\",\n                type: \"string\",\n                value: \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [sampleLength, sampleOffset, printAmount, crib] = args;\n        const sample = input.slice(sampleOffset, sampleOffset + sampleLength);\n        const cribLower = crib.toLowerCase();\n        const result = [];\n        for (let amount = 1; amount < 94; amount++) {\n            const rotated = sample.slice();\n            for (let i = 0; i < rotated.length; i++) {\n                if (33 <= rotated[i] && rotated[i] <= 126) {\n                    rotated[i] = (rotated[i] - 33 + amount) % 94 + 33;\n                }\n            }\n            const rotatedString = Utils.byteArrayToUtf8(rotated);\n            if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) {\n                const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString);\n                if (printAmount) {\n                    const amountStr = \"Amount = \" + (\" \" + amount).slice(-2) + \": \";\n                    result.push(amountStr + rotatedStringEscaped);\n                } else {\n                    result.push(rotatedStringEscaped);\n                }\n            }\n        }\n        return result.join(\"\\n\");\n    }\n}\n\nexport default ROT47BruteForce;\n"
  },
  {
    "path": "src/core/operations/ROT8000.mjs",
    "content": "/**\n * @author Daniel Temkin [http://danieltemkin.com]\n * @author Thomas Leplus [https://www.leplus.org]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * ROT8000 operation.\n */\nclass ROT8000 extends Operation {\n\n    /**\n     * ROT8000 constructor\n     */\n    constructor() {\n        super();\n        this.name = \"ROT8000\";\n        this.module = \"Default\";\n        this.description = \"The simple Caesar-cypher encryption that replaces each Unicode character with the one 0x8000 places forward or back along the alphabet.\";\n        this.infoURL = \"https://rot8000.com/info\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        // Inspired from https://github.com/rottytooth/rot8000/blob/main/rot8000.js\n        // these come from the valid-code-point-transitions.json file generated from the c# proj\n        // this is done bc: 1) don't trust JS's understanging of surrogate pairs and 2) consistency with original rot8000\n        const validCodePoints = {\n            \"33\": true,\n            \"127\": false,\n            \"161\": true,\n            \"5760\": false,\n            \"5761\": true,\n            \"8192\": false,\n            \"8203\": true,\n            \"8232\": false,\n            \"8234\": true,\n            \"8239\": false,\n            \"8240\": true,\n            \"8287\": false,\n            \"8288\": true,\n            \"12288\": false,\n            \"12289\": true,\n            \"55296\": false,\n            \"57344\": true\n        };\n        const bmpSize = 0x10000;\n        const rotList = {}; // the mapping of char to rotated char\n        const hiddenBlocks = [];\n        let startBlock = 0;\n        for (const key in validCodePoints) {\n            if (Object.prototype.hasOwnProperty.call(validCodePoints, key)) {\n                if (validCodePoints[key] === true)\n                    hiddenBlocks.push({ start: startBlock, end: parseInt(key, 10) - 1 });\n                else\n                    startBlock = parseInt(key, 10);\n            }\n        }\n        const validIntList = []; // list of all valid chars\n        let currValid = false;\n        for (let i = 0; i < bmpSize; i++) {\n            if (validCodePoints[i] !== undefined) {\n                currValid = validCodePoints[i];\n            }\n            if (currValid) validIntList.push(i);\n        }\n        const rotateNum = Object.keys(validIntList).length / 2;\n        // go through every valid char and find its match\n        for (let i = 0; i < validIntList.length; i++) {\n            rotList[String.fromCharCode(validIntList[i])] =\n                String.fromCharCode(validIntList[(i + rotateNum) % (rotateNum * 2)]);\n        }\n        let output = \"\";\n        for (let count = 0; count < input.length; count++) {\n            // if it is not in the mappings list, just add it directly (no rotation)\n            if (rotList[input[count]] === undefined) {\n                output += input[count];\n                continue;\n            }\n            // otherwise, rotate it and add it to the string\n            output += rotList[input[count]];\n        }\n        return output;\n    }\n\n    /**\n     * Highlight ROT8000\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight ROT8000 in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n}\n\nexport default ROT8000;\n"
  },
  {
    "path": "src/core/operations/RSADecrypt.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\nimport { MD_ALGORITHMS } from \"../lib/RSA.mjs\";\n\n/**\n * RSA Decrypt operation\n */\nclass RSADecrypt extends Operation {\n\n    /**\n     * RSADecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RSA Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Decrypt an RSA encrypted message with a PEM encoded private key.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RSA_(cryptosystem)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"RSA Private Key (PEM)\",\n                type: \"text\",\n                value: \"-----BEGIN RSA PRIVATE KEY-----\"\n            },\n            {\n                name: \"Key Password\",\n                type: \"text\",\n                value: \"\"\n            },\n            {\n                name: \"Encryption Scheme\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"RSA-OAEP\",\n                        on: [3]\n                    },\n                    {\n                        name: \"RSAES-PKCS1-V1_5\",\n                        off: [3]\n                    },\n                    {\n                        name: \"RAW\",\n                        off: [3]\n                    }]\n            },\n            {\n                name: \"Message Digest Algorithm\",\n                type: \"option\",\n                value: Object.keys(MD_ALGORITHMS)\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [pemKey, password, scheme, md] = args;\n        if (pemKey.replace(\"-----BEGIN RSA PRIVATE KEY-----\", \"\").length === 0) {\n            throw new OperationError(\"Please enter a private key.\");\n        }\n        try {\n            const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password);\n            const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()});\n            return forge.util.decodeUtf8(dMsg);\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default RSADecrypt;\n"
  },
  {
    "path": "src/core/operations/RSAEncrypt.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\nimport { MD_ALGORITHMS } from \"../lib/RSA.mjs\";\n\n/**\n * RSA Encrypt operation\n */\nclass RSAEncrypt extends Operation {\n\n    /**\n     * RSAEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RSA Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Encrypt a message with a PEM encoded RSA public key.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RSA_(cryptosystem)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"RSA Public Key (PEM)\",\n                type: \"text\",\n                value: \"-----BEGIN RSA PUBLIC KEY-----\"\n            },\n            {\n                name: \"Encryption Scheme\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"RSA-OAEP\",\n                        on: [2]\n                    },\n                    {\n                        name: \"RSAES-PKCS1-V1_5\",\n                        off: [2]\n                    },\n                    {\n                        name: \"RAW\",\n                        off: [2]\n                    }]\n            },\n            {\n                name: \"Message Digest Algorithm\",\n                type: \"option\",\n                value: Object.keys(MD_ALGORITHMS)\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [pemKey, scheme, md] = args;\n\n        if (pemKey.replace(\"-----BEGIN RSA PUBLIC KEY-----\", \"\").length === 0) {\n            throw new OperationError(\"Please enter a public key.\");\n        }\n        try {\n            // Load public key\n            const pubKey = forge.pki.publicKeyFromPem(pemKey);\n            // https://github.com/digitalbazaar/forge/issues/465#issuecomment-271097600\n            const plaintextBytes = forge.util.encodeUtf8(input);\n            // Encrypt message\n            const eMsg = pubKey.encrypt(plaintextBytes, scheme, {md: MD_ALGORITHMS[md].create()});\n            return eMsg;\n        } catch (err) {\n            if (err.message === \"RSAES-OAEP input message length is too long.\") {\n                throw new OperationError(`RSAES-OAEP input message length (${err.length}) is longer than the maximum allowed length (${err.maxLength}).`);\n            }\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default RSAEncrypt;\n"
  },
  {
    "path": "src/core/operations/RSASign.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @author gchq77703 []\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\nimport { MD_ALGORITHMS } from \"../lib/RSA.mjs\";\n\n/**\n * RSA Sign operation\n */\nclass RSASign extends Operation {\n\n    /**\n     * RSASign constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RSA Sign\";\n        this.module = \"Ciphers\";\n        this.description = \"Sign a plaintext message with a PEM encoded RSA key.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RSA_(cryptosystem)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"RSA Private Key (PEM)\",\n                type: \"text\",\n                value: \"-----BEGIN RSA PRIVATE KEY-----\"\n            },\n            {\n                name: \"Key Password\",\n                type: \"text\",\n                value: \"\"\n            },\n            {\n                name: \"Message Digest Algorithm\",\n                type: \"option\",\n                value: Object.keys(MD_ALGORITHMS)\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [key, password, mdAlgo] = args;\n        if (key.replace(\"-----BEGIN RSA PRIVATE KEY-----\", \"\").length === 0) {\n            throw new OperationError(\"Please enter a private key.\");\n        }\n        try {\n            const privateKey = forge.pki.decryptRsaPrivateKey(key, password);\n            // Generate message hash\n            const md = MD_ALGORITHMS[mdAlgo].create();\n            md.update(input, \"raw\");\n            // Sign message hash\n            const sig = privateKey.sign(md);\n            return sig;\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default RSASign;\n"
  },
  {
    "path": "src/core/operations/RSAVerify.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\nimport { MD_ALGORITHMS } from \"../lib/RSA.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * RSA Verify operation\n */\nclass RSAVerify extends Operation {\n\n    /**\n     * RSAVerify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"RSA Verify\";\n        this.module = \"Ciphers\";\n        this.description = \"Verify a message against a signature and a public PEM encoded RSA key.\";\n        this.infoURL = \"https://wikipedia.org/wiki/RSA_(cryptosystem)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"RSA Public Key (PEM)\",\n                type: \"text\",\n                value: \"-----BEGIN RSA PUBLIC KEY-----\"\n            },\n            {\n                name: \"Message\",\n                type: \"text\",\n                value: \"\"\n            },\n            {\n                name: \"Message format\",\n                type: \"option\",\n                value: [\"Raw\", \"Hex\", \"Base64\"]\n            },\n            {\n                name: \"Message Digest Algorithm\",\n                type: \"option\",\n                value: Object.keys(MD_ALGORITHMS)\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [pemKey, message, format, mdAlgo] = args;\n        if (pemKey.replace(\"-----BEGIN RSA PUBLIC KEY-----\", \"\").length === 0) {\n            throw new OperationError(\"Please enter a public key.\");\n        }\n        try {\n            // Load public key\n            const pubKey = forge.pki.publicKeyFromPem(pemKey);\n            // Generate message digest\n            const md = MD_ALGORITHMS[mdAlgo].create();\n            const messageStr = Utils.convertToByteString(message, format);\n            md.update(messageStr, \"raw\");\n            // Compare signed message digest and generated message digest\n            const result = pubKey.verify(md.digest().bytes(), input);\n            return result ? \"Verified OK\" : \"Verification Failure\";\n        } catch (err) {\n            if (err.message === \"Encrypted message length is invalid.\") {\n                throw new OperationError(`Signature length (${err.length}) does not match expected length based on key (${err.expected}).`);\n            }\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default RSAVerify;\n"
  },
  {
    "path": "src/core/operations/Rabbit.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHexFast } from \"../lib/Hex.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Rabbit operation\n */\nclass Rabbit extends Operation {\n\n    /**\n     * Rabbit constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rabbit\";\n        this.module = \"Ciphers\";\n        this.description = \"Rabbit is a high-speed stream cipher introduced in 2003 and defined in RFC 4503.<br><br>The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).<br><br>big-endian: based on RFC4503 and RFC3447<br>little-endian: compatible with Crypto++\";\n        this.infoURL = \"https://wikipedia.org/wiki/Rabbit_(cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Endianness\",\n                \"type\": \"option\",\n                \"value\": [\"Big\", \"Little\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            endianness = args[2],\n            inputType = args[3],\n            outputType = args[4];\n\n        const littleEndian = endianness === \"Little\";\n\n        if (key.length !== 16) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes (expected: 16)`);\n        }\n        if (iv.length !== 0 && iv.length !== 8) {\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes (expected: 0 or 8)`);\n        }\n\n        // Inner State\n        const X = new Uint32Array(8), C = new Uint32Array(8);\n        let b = 0;\n\n        // Counter System\n        const A = [\n            0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d,\n            0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3\n        ];\n        const counterUpdate = function() {\n            for (let j = 0; j < 8; j++) {\n                const temp = C[j] + A[j] + b;\n                b = (temp / ((1 << 30) * 4)) >>> 0;\n                C[j] = temp;\n            }\n        };\n\n        // Next-State Function\n        const g = function(u, v) {\n            const uv = (u + v) >>> 0;\n            const upper = uv >>> 16, lower = uv & 0xffff;\n            const upperUpper = upper * upper;\n            const upperLower2 = 2 * upper * lower;\n            const lowerLower = lower * lower;\n            const mswTemp = upperUpper + ((upperLower2 / (1 << 16)) >>> 0);\n            const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16);\n            const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0);\n            const lsw = lswTemp >>> 0;\n            return lsw ^ msw;\n        };\n        const leftRotate = function(value, width) {\n            return (value << width) | (value >>> (32 - width));\n        };\n        const nextStateHelper1 = function(v0, v1, v2) {\n            return v0 + leftRotate(v1, 16) + leftRotate(v2, 16);\n        };\n        const nextStateHelper2 = function(v0, v1, v2) {\n            return v0 + leftRotate(v1, 8) + v2;\n        };\n        const G = new Uint32Array(8);\n        const nextState = function() {\n            for (let j = 0; j < 8; j++) {\n                G[j] = g(X[j], C[j]);\n            }\n            X[0] = nextStateHelper1(G[0], G[7], G[6]);\n            X[1] = nextStateHelper2(G[1], G[0], G[7]);\n            X[2] = nextStateHelper1(G[2], G[1], G[0]);\n            X[3] = nextStateHelper2(G[3], G[2], G[1]);\n            X[4] = nextStateHelper1(G[4], G[3], G[2]);\n            X[5] = nextStateHelper2(G[5], G[4], G[3]);\n            X[6] = nextStateHelper1(G[6], G[5], G[4]);\n            X[7] = nextStateHelper2(G[7], G[6], G[5]);\n        };\n\n        // Key Setup Scheme\n        const K = new Uint16Array(8);\n        if (littleEndian) {\n            for (let i = 0; i < 8; i++) {\n                K[i] = (key[1 + 2 * i] << 8) | key[2 * i];\n            }\n        } else {\n            for (let i = 0; i < 8; i++) {\n                K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i];\n            }\n        }\n        for (let j = 0; j < 8; j++) {\n            if (j % 2 === 0) {\n                X[j] = (K[(j + 1) % 8] << 16) | K[j];\n                C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8];\n            } else {\n                X[j] = (K[(j + 5) % 8] << 16) | K[(j + 4) % 8];\n                C[j] = (K[j] << 16) | K[(j + 1) % 8];\n            }\n        }\n        for (let i = 0; i < 4; i++) {\n            counterUpdate();\n            nextState();\n        }\n        for (let j = 0; j < 8; j++) {\n            C[j] = C[j] ^ X[(j + 4) % 8];\n        }\n\n        // IV Setup Scheme\n        if (iv.length === 8) {\n            const getIVValue = function(a, b, c, d) {\n                if (littleEndian) {\n                    return (iv[a] << 24) | (iv[b] << 16) |\n                        (iv[c] << 8) | iv[d];\n                } else {\n                    return (iv[7 - a] << 24) | (iv[7 - b] << 16) |\n                        (iv[7 - c] << 8) | iv[7 - d];\n                }\n            };\n            C[0] = C[0] ^ getIVValue(3, 2, 1, 0);\n            C[1] = C[1] ^ getIVValue(7, 6, 3, 2);\n            C[2] = C[2] ^ getIVValue(7, 6, 5, 4);\n            C[3] = C[3] ^ getIVValue(5, 4, 1, 0);\n            C[4] = C[4] ^ getIVValue(3, 2, 1, 0);\n            C[5] = C[5] ^ getIVValue(7, 6, 3, 2);\n            C[6] = C[6] ^ getIVValue(7, 6, 5, 4);\n            C[7] = C[7] ^ getIVValue(5, 4, 1, 0);\n            for (let i = 0; i < 4; i++) {\n                counterUpdate();\n                nextState();\n            }\n        }\n\n        // Extraction Scheme\n        const S = new Uint8Array(16);\n        const extract = function() {\n            let pos = 0;\n            const addPart = function(value) {\n                S[pos++] = value >>> 8;\n                S[pos++] = value & 0xff;\n            };\n            counterUpdate();\n            nextState();\n            addPart((X[6] >>> 16) ^ (X[1] & 0xffff));\n            addPart((X[6] & 0xffff) ^ (X[3] >>> 16));\n            addPart((X[4] >>> 16) ^ (X[7] & 0xffff));\n            addPart((X[4] & 0xffff) ^ (X[1] >>> 16));\n            addPart((X[2] >>> 16) ^ (X[5] & 0xffff));\n            addPart((X[2] & 0xffff) ^ (X[7] >>> 16));\n            addPart((X[0] >>> 16) ^ (X[3] & 0xffff));\n            addPart((X[0] & 0xffff) ^ (X[5] >>> 16));\n            if (littleEndian) {\n                for (let i = 0, j = S.length - 1; i < j;) {\n                    const temp = S[i];\n                    S[i] = S[j];\n                    S[j] = temp;\n                    i++;\n                    j--;\n                }\n            }\n        };\n\n        const data = Utils.convertToByteString(input, inputType);\n        const result = new Uint8Array(data.length);\n        for (let i = 0; i <= data.length - 16; i += 16) {\n            extract();\n            for (let j = 0; j < 16; j++) {\n                result[i + j] = data.charCodeAt(i + j) ^ S[j];\n            }\n        }\n        if (data.length % 16 !== 0) {\n            const offset = data.length - data.length % 16;\n            const length = data.length - offset;\n            extract();\n            if (littleEndian) {\n                for (let j = 0; j < length; j++) {\n                    result[offset + j] = data.charCodeAt(offset + j) ^ S[j];\n                }\n            } else {\n                for (let j = 0; j < length; j++) {\n                    result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j];\n                }\n            }\n        }\n        if (outputType === \"Hex\") {\n            return toHexFast(result);\n        }\n        return Utils.byteArrayToChars(result);\n    }\n\n}\n\nexport default Rabbit;\n"
  },
  {
    "path": "src/core/operations/RailFenceCipherDecode.mjs",
    "content": "/**\n * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Rail Fence Cipher Decode operation\n */\nclass RailFenceCipherDecode extends Operation {\n\n    /**\n     * RailFenceCipherDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rail Fence Cipher Decode\";\n        this.module = \"Ciphers\";\n        this.description = \"Decodes Strings that were created using the Rail fence Cipher provided a key and an offset\";\n        this.infoURL = \"https://wikipedia.org/wiki/Rail_fence_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"number\",\n                value: 2\n            },\n            {\n                name: \"Offset\",\n                type: \"number\",\n                value: 0\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [key, offset] = args;\n\n        const cipher = input;\n\n        if (key < 2) {\n            throw new OperationError(\"Key has to be bigger than 2\");\n        } else if (key > cipher.length) {\n            throw new OperationError(\"Key should be smaller than the cipher's length\");\n        }\n\n        if (offset < 0) {\n            throw new OperationError(\"Offset has to be a positive integer\");\n        }\n\n        const cycle = (key - 1) * 2;\n        const plaintext = new Array(cipher.length);\n\n        let j = 0;\n        let x, y;\n\n        for (y = 0; y < key; y++) {\n            for (x = 0; x < cipher.length; x++) {\n                if ((y + x + offset) % cycle === 0 || (y - x - offset) % cycle === 0) {\n                    plaintext[x] = cipher[j++];\n                }\n            }\n        }\n\n        return plaintext.join(\"\");\n    }\n\n}\n\n\nexport default RailFenceCipherDecode;\n"
  },
  {
    "path": "src/core/operations/RailFenceCipherEncode.mjs",
    "content": "/**\n * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Rail Fence Cipher Encode operation\n */\nclass RailFenceCipherEncode extends Operation {\n\n    /**\n     * RailFenceCipherEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rail Fence Cipher Encode\";\n        this.module = \"Ciphers\";\n        this.description = \"Encodes Strings using the Rail fence Cipher provided a key and an offset\";\n        this.infoURL = \"https://wikipedia.org/wiki/Rail_fence_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Key\",\n                type: \"number\",\n                value: 2\n            },\n            {\n                name: \"Offset\",\n                type: \"number\",\n                value: 0\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [key, offset] = args;\n\n        const plaintext = input;\n        if (key < 2) {\n            throw new OperationError(\"Key has to be bigger than 2\");\n        } else if (key > plaintext.length) {\n            throw new OperationError(\"Key should be smaller than the plain text's length\");\n        }\n\n        if (offset < 0) {\n            throw new OperationError(\"Offset has to be a positive integer\");\n        }\n\n        const cycle = (key - 1) * 2;\n        const rows = new Array(key).fill(\"\");\n\n        for (let pos = 0; pos < plaintext.length; pos++) {\n            const rowIdx = key - 1 - Math.abs(cycle / 2 - (pos + offset) % cycle);\n\n            rows[rowIdx] += plaintext[pos];\n        }\n\n        return rows.join(\"\");\n    }\n\n}\n\nexport default RailFenceCipherEncode;\n"
  },
  {
    "path": "src/core/operations/RandomizeColourPalette.mjs",
    "content": "/**\n * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { runHash } from \"../lib/Hash.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { Jimp } from \"jimp\";\n\n/**\n * Randomize Colour Palette operation\n */\nclass RandomizeColourPalette extends Operation {\n    /**\n     * RandomizeColourPalette constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Randomize Colour Palette\";\n        this.module = \"Image\";\n        this.description =\n            \"Randomizes each colour in an image's colour palette. This can often reveal text or symbols that were previously a very similar colour to their surroundings, a technique sometimes used in Steganography.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Indexed_color\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Seed\",\n                type: \"string\",\n                value: \"\",\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    async run(input, args) {\n        if (!isImage(input))\n            throw new OperationError(\"Please enter a valid image file.\");\n\n        const seed = args[0] || Math.random().toString().substr(2),\n            parsedImage = await Jimp.read(input),\n            width = parsedImage.bitmap.width,\n            height = parsedImage.bitmap.height;\n\n        let rgbString, rgbHash, rgbHex;\n\n        parsedImage.scan(0, 0, width, height, function (x, y, idx) {\n            rgbString = this.bitmap.data.slice(idx, idx + 3).join(\".\");\n            rgbHash = runHash(\"md5\", Utils.strToArrayBuffer(seed + rgbString));\n            rgbHex = rgbHash.substr(0, 6) + \"ff\";\n            parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y);\n        });\n\n        const imageBuffer = await parsedImage.getBuffer(parsedImage.mime);\n\n        return new Uint8Array(imageBuffer).buffer;\n    }\n\n    /**\n     * Displays the extracted data as an image for web apps.\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const type = isImage(data);\n\n        return `<img src=\"data:${type};base64,${toBase64(data)}\">`;\n    }\n}\n\nexport default RandomizeColourPalette;\n"
  },
  {
    "path": "src/core/operations/RawDeflate.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {COMPRESSION_TYPE} from \"../lib/Zlib.mjs\";\nimport rawdeflate from \"zlibjs/bin/rawdeflate.min.js\";\n\nconst Zlib = rawdeflate.Zlib;\n\nconst RAW_COMPRESSION_TYPE_LOOKUP = {\n    \"Fixed Huffman Coding\":   Zlib.RawDeflate.CompressionType.FIXED,\n    \"Dynamic Huffman Coding\": Zlib.RawDeflate.CompressionType.DYNAMIC,\n    \"None (Store)\":           Zlib.RawDeflate.CompressionType.NONE,\n};\n\n/**\n * Raw Deflate operation\n */\nclass RawDeflate extends Operation {\n\n    /**\n     * RawDeflate constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Raw Deflate\";\n        this.module = \"Compression\";\n        this.description = \"Compresses data using the deflate algorithm with no headers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/DEFLATE\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Compression type\",\n                type: \"option\",\n                value: COMPRESSION_TYPE\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const deflate = new Zlib.RawDeflate(new Uint8Array(input), {\n            compressionType: RAW_COMPRESSION_TYPE_LOOKUP[args[0]]\n        });\n        return new Uint8Array(deflate.compress()).buffer;\n    }\n\n}\n\nexport default RawDeflate;\n"
  },
  {
    "path": "src/core/operations/RawInflate.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {INFLATE_BUFFER_TYPE} from \"../lib/Zlib.mjs\";\nimport rawinflate from \"zlibjs/bin/rawinflate.min.js\";\n\nconst Zlib = rawinflate.Zlib;\n\nconst RAW_BUFFER_TYPE_LOOKUP = {\n    \"Adaptive\": Zlib.RawInflate.BufferType.ADAPTIVE,\n    \"Block\":    Zlib.RawInflate.BufferType.BLOCK,\n};\n\n/**\n * Raw Inflate operation\n */\nclass RawInflate extends Operation {\n\n    /**\n     * RawInflate constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Raw Inflate\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data which has been compressed using the deflate algorithm with no headers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/DEFLATE\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Start index\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Initial output buffer size\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Buffer expansion type\",\n                type: \"option\",\n                value: INFLATE_BUFFER_TYPE\n            },\n            {\n                name: \"Resize buffer after decompression\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Verify result\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n        this.checks = [\n            {\n                entropyRange: [7.5, 8],\n                args: [0, 0, INFLATE_BUFFER_TYPE, false, false]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const inflate = new Zlib.RawInflate(new Uint8Array(input), {\n                index: args[0],\n                bufferSize: args[1],\n                bufferType: RAW_BUFFER_TYPE_LOOKUP[args[2]],\n                resize: args[3],\n                verify: args[4]\n            }),\n            result = new Uint8Array(inflate.decompress());\n\n        return result.buffer;\n    }\n\n}\n\nexport default RawInflate;\n"
  },
  {
    "path": "src/core/operations/Register.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Dish from \"../Dish.mjs\";\nimport XRegExp from \"xregexp\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * Register operation\n */\nclass Register extends Operation {\n\n    /**\n     * Register constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Register\";\n        this.flowControl = true;\n        this.module = \"Regex\";\n        this.description = \"Extract data from the input and store it in registers which can then be passed into subsequent operations as arguments. Regular expression capture groups are used to select the data to extract.<br><br>To use registers in arguments, refer to them using the notation <code>$Rn</code> where n is the register number, starting at 0.<br><br>For example:<br>Input: <code>Test</code><br>Extractor: <code>(.*)</code><br>Argument: <code>$R0</code> becomes <code>Test</code><br><br>Registers can be escaped in arguments using a backslash. e.g. <code>\\\\$R0</code> would become <code>$R0</code> rather than <code>Test</code>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Regular_expression#Syntax\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Extractor\",\n                \"type\": \"binaryString\",\n                \"value\": \"([\\\\s\\\\S]*)\"\n            },\n            {\n                \"name\": \"Case insensitive\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Multiline matching\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Dot matches all\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @returns {Object} The updated state of the recipe.\n     */\n    async run(state) {\n        const ings = state.opList[state.progress].ingValues;\n        const [extractorStr, i, m, s] = ings;\n\n        let modifiers = \"\";\n        if (i) modifiers += \"i\";\n        if (m) modifiers += \"m\";\n        if (s) modifiers += \"s\";\n\n        const extractor = new XRegExp(extractorStr, modifiers),\n            input = await state.dish.get(Dish.STRING),\n            registers = input.match(extractor);\n\n        if (!registers) return state;\n\n        if (isWorkerEnvironment()) {\n            self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1));\n        }\n\n        /**\n         * Replaces references to registers (e.g. $R0) with the contents of those registers.\n         *\n         * @param {string} str\n         * @returns {string}\n         */\n        function replaceRegister(str) {\n            // Replace references to registers ($Rn) with contents of registers\n            return str.replace(/(\\\\*)\\$R(\\d{1,2})/g, (match, slashes, regNum) => {\n                const index = parseInt(regNum, 10) + 1;\n                if (index <= state.numRegisters || index >= state.numRegisters + registers.length)\n                    return match;\n                if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape\n                return slashes + registers[index - state.numRegisters];\n            });\n        }\n\n        // Step through all subsequent ops and replace registers in args with extracted content\n        for (let i = state.progress + 1; i < state.opList.length; i++) {\n            if (state.opList[i].disabled) continue;\n\n            let args = state.opList[i].ingValues;\n            args = args.map(arg => {\n                if (typeof arg !== \"string\" && typeof arg !== \"object\") return arg;\n\n                if (typeof arg === \"object\" && Object.prototype.hasOwnProperty.call(arg, \"string\")) {\n                    arg.string = replaceRegister(arg.string);\n                    return arg;\n                }\n                return replaceRegister(arg);\n            });\n            state.opList[i].ingValues = args;\n        }\n\n        state.numRegisters += registers.length - 1;\n        return state;\n    }\n\n}\n\nexport default Register;\n"
  },
  {
    "path": "src/core/operations/RegularExpression.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport XRegExp from \"xregexp\";\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Regular expression operation\n */\nclass RegularExpression extends Operation {\n\n    /**\n     * RegularExpression constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Regular expression\";\n        this.module = \"Regex\";\n        this.description = \"Define your own regular expression (regex) to search the input data with, optionally choosing from a list of pre-defined patterns.<br><br>Supports extended regex syntax including the 'dot matches all' flag, named capture groups, full unicode coverage (including <code>\\\\p{}</code> categories and scripts as well as astral codes) and recursive matching.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Regular_expression\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Built in regexes\",\n                \"type\": \"populateOption\",\n                \"value\": [\n                    {\n                        name: \"User defined\",\n                        value: \"\"\n                    },\n                    {\n                        name: \"IPv4 address\",\n                        value: \"(?:(?:\\\\d|[01]?\\\\d\\\\d|2[0-4]\\\\d|25[0-5])\\\\.){3}(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d\\\\d|\\\\d)(?:\\\\/\\\\d{1,2})?\"\n                    },\n                    {\n                        name: \"IPv6 address\",\n                        value: \"((?=.*::)(?!.*::.+::)(::)?([\\\\dA-Fa-f]{1,4}:(:|\\\\b)|){5}|([\\\\dA-Fa-f]{1,4}:){6})((([\\\\dA-Fa-f]{1,4}((?!\\\\3)::|:\\\\b|(?![\\\\dA-Fa-f])))|(?!\\\\2\\\\3)){2}|(((2[0-4]|1\\\\d|[1-9])?\\\\d|25[0-5])\\\\.?\\\\b){4})\"\n                    },\n                    {\n                        name: \"Email address\",\n                        value: \"(?:[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\\\.[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\\\"(?:[\\\\x01-\\\\x08\\\\x0b\\\\x0c\\\\x0e-\\\\x1f\\\\x21\\\\x23-\\\\x5b\\\\x5d-\\\\x7f]|\\\\[\\\\x01-\\\\x09\\\\x0b\\\\x0c\\\\x0e-\\\\x7f])*\\\")@(?:(?:[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFFa-z0-9](?:[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFF-a-z0-9-]*[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFFa-z0-9])?\\\\.)+[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFFa-z0-9](?:[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFFa-z0-9-]*[\\\\u00A0-\\\\uD7FF\\\\uE000-\\\\uFFFFa-z0-9])?|\\\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\\\.){3}\\\\])\"\n                    },\n                    {\n                        name: \"URL\",\n                        value: \"([A-Za-z]+://)([-\\\\w]+(?:\\\\.\\\\w[-\\\\w]*)+)(:\\\\d+)?(/[^.!,?\\\"<>\\\\[\\\\]{}\\\\s\\\\x7F-\\\\xFF]*(?:[.!,?]+[^.!,?\\\"<>\\\\[\\\\]{}\\\\s\\\\x7F-\\\\xFF]+)*)?\"\n                    },\n                    {\n                        name: \"Domain\",\n                        value: \"\\\\b((?=[a-z0-9-]{1,63}\\\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\\\.)+[a-z]{2,63}\\\\b\"\n                    },\n                    {\n                        name: \"Windows file path\",\n                        value: \"([A-Za-z]):\\\\\\\\((?:[A-Za-z\\\\d][A-Za-z\\\\d\\\\- \\\\x27_\\\\(\\\\)~]{0,61}\\\\\\\\?)*[A-Za-z\\\\d][A-Za-z\\\\d\\\\- \\\\x27_\\\\(\\\\)]{0,61})(\\\\.[A-Za-z\\\\d]{1,6})?\"\n                    },\n                    {\n                        name: \"UNIX file path\",\n                        value: \"(?:/[A-Za-z\\\\d.][A-Za-z\\\\d\\\\-.]{0,61})+\"\n                    },\n                    {\n                        name: \"MAC address\",\n                        value: \"[A-Fa-f\\\\d]{2}(?:[:-][A-Fa-f\\\\d]{2}){5}\"\n                    },\n                    {\n                        name: \"UUID\",\n                        value: \"[0-9a-fA-F]{8}\\\\b-[0-9a-fA-F]{4}\\\\b-[0-9a-fA-F]{4}\\\\b-[0-9a-fA-F]{4}\\\\b-[0-9a-fA-F]{12}\"\n                    },\n                    {\n                        name: \"Date (yyyy-mm-dd)\",\n                        value: \"((?:19|20)\\\\d\\\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])\"\n                    },\n                    {\n                        name: \"Date (dd/mm/yyyy)\",\n                        value: \"(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\\\d\\\\d)\"\n                    },\n                    {\n                        name: \"Date (mm/dd/yyyy)\",\n                        value: \"(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\\\d\\\\d)\"\n                    },\n                    {\n                        name: \"Strings\",\n                        value: \"[A-Za-z\\\\d/\\\\-:.,_$%\\\\x27\\\"()<>= !\\\\[\\\\]{}@]{4,}\"\n                    },\n                ],\n                \"target\": 1\n            },\n            {\n                \"name\": \"Regex\",\n                \"type\": \"text\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Case insensitive\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"^ and $ match at newlines\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Dot matches all\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Unicode support\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Astral support\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Display total\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Output format\",\n                \"type\": \"option\",\n                \"value\": [\"Highlight matches\", \"List matches\", \"List capture groups\", \"List matches with capture groups\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const [,\n            userRegex,\n            i, m, s, u, a,\n            displayTotal,\n            outputFormat\n        ] = args;\n        let modifiers = \"g\";\n\n        if (i) modifiers += \"i\";\n        if (m) modifiers += \"m\";\n        if (s) modifiers += \"s\";\n        if (u) modifiers += \"u\";\n        if (a) modifiers += \"A\";\n\n        if (userRegex && userRegex !== \"^\" && userRegex !== \"$\") {\n            try {\n                const regex = new XRegExp(userRegex, modifiers);\n\n                switch (outputFormat) {\n                    case \"Highlight matches\":\n                        return regexHighlight(input, regex, displayTotal);\n                    case \"List matches\":\n                        return Utils.escapeHtml(regexList(input, regex, displayTotal, true, false));\n                    case \"List capture groups\":\n                        return Utils.escapeHtml(regexList(input, regex, displayTotal, false, true));\n                    case \"List matches with capture groups\":\n                        return Utils.escapeHtml(regexList(input, regex, displayTotal, true, true));\n                    default:\n                        throw new OperationError(\"Error: Invalid output format\");\n                }\n            } catch (err) {\n                throw new OperationError(\"Invalid regex. Details: \" + err.message);\n            }\n        } else {\n            return Utils.escapeHtml(input);\n        }\n    }\n\n}\n\n/**\n * Creates a string listing the matches within a string.\n *\n * @param {string} input\n * @param {RegExp} regex\n * @param {boolean} displayTotal\n * @param {boolean} matches - Display full match\n * @param {boolean} captureGroups - Display each of the capture groups separately\n * @returns {string}\n */\nfunction regexList(input, regex, displayTotal, matches, captureGroups) {\n    let output = \"\",\n        total = 0,\n        match;\n\n    while ((match = regex.exec(input))) {\n        // Moves pointer when an empty string is matched (prevents infinite loop)\n        if (match.index === regex.lastIndex) {\n            regex.lastIndex++;\n        }\n\n        total++;\n        if (matches) {\n            output += match[0] + \"\\n\";\n        }\n        if (captureGroups) {\n            for (let i = 1; i < match.length; i++) {\n                if (matches) {\n                    output += \"  Group \" + i + \": \";\n                }\n                output += match[i] + \"\\n\";\n            }\n        }\n    }\n\n    if (displayTotal)\n        output = \"Total found: \" + total + \"\\n\\n\" + output;\n\n    return output.slice(0, -1);\n}\n\n/**\n * Adds HTML highlights to matches within a string.\n *\n * @private\n * @param {string} input\n * @param {RegExp} regex\n * @param {boolean} displayTotal\n * @returns {string}\n */\nfunction regexHighlight(input, regex, displayTotal) {\n    let output = \"\",\n        title = \"\",\n        hl = 1,\n        total = 0;\n    const captureGroups = [];\n\n    output = input.replace(regex, (match, ...args) => {\n        args.pop(); // Throw away full string\n        const offset = args.pop(),\n            groups = args;\n\n        title = `Offset: ${offset}\\n`;\n        if (groups.length) {\n            title += \"Groups:\\n\";\n            for (let i = 0; i < groups.length; i++) {\n                title += `\\t${i+1}: ${Utils.escapeHtml(groups[i] || \"\")}\\n`;\n            }\n        }\n\n        // Switch highlight\n        hl = hl === 1 ? 2 : 1;\n\n        // Store highlighted match and replace with a placeholder\n        captureGroups.push(`<span class='hl${hl}' title='${title}'>${Utils.escapeHtml(match)}</span>`);\n        return `[cc_capture_group_${total++}]`;\n    });\n\n    // Safely escape all remaining text, then replace placeholders\n    output = Utils.escapeHtml(output);\n    output = output.replace(/\\[cc_capture_group_(\\d+)\\]/g, (_, i) => {\n        return captureGroups[i];\n    });\n\n    if (displayTotal)\n        output = \"Total found: \" + total + \"\\n\\n\" + output;\n\n    return output;\n}\n\nexport default RegularExpression;\n"
  },
  {
    "path": "src/core/operations/RemoveDiacritics.mjs",
    "content": "/**\n * @author Klaxon [klaxon@veyr.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Remove Diacritics operation\n */\nclass RemoveDiacritics extends Operation {\n\n    /**\n     * RemoveDiacritics constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Remove Diacritics\";\n        this.module = \"Default\";\n        this.description = \"Replaces accented characters with their latin character equivalent. Accented characters are made up of Unicode combining characters, so unicode text formatting such as strikethroughs and underlines will also be removed.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Diacritic\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        // reference: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463\n        return input.normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\");\n    }\n\n}\n\nexport default RemoveDiacritics;\n"
  },
  {
    "path": "src/core/operations/RemoveEXIF.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport { removeEXIF } from \"../vendor/remove-exif.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Remove EXIF operation\n */\nclass RemoveEXIF extends Operation {\n\n    /**\n     * RemoveEXIF constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Remove EXIF\";\n        this.module = \"Image\";\n        this.description = [\n            \"Removes EXIF data from a JPEG image.\",\n            \"<br><br>\",\n            \"EXIF data embedded in photos usually contains information about the image file itself as well as the device used to create it.\",\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Exif\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        // Do nothing if input is empty\n        if (input.length === 0) return input;\n\n        try {\n            return removeEXIF(input);\n        } catch (err) {\n            // Simply return input if no EXIF data is found\n            if (err === \"Exif not found.\") return input;\n            throw new OperationError(`Could not remove EXIF data from image: ${err}`);\n        }\n    }\n\n}\n\nexport default RemoveEXIF;\n"
  },
  {
    "path": "src/core/operations/RemoveLineNumbers.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Remove line numbers operation\n */\nclass RemoveLineNumbers extends Operation {\n\n    /**\n     * RemoveLineNumbers constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Remove line numbers\";\n        this.module = \"Default\";\n        this.description = \"Removes line numbers from the output if they can be trivially detected.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return input.replace(/^[ \\t]{0,5}\\d+[\\s:|\\-,.)\\]]/gm, \"\");\n    }\n\n}\n\nexport default RemoveLineNumbers;\n"
  },
  {
    "path": "src/core/operations/RemoveNullBytes.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Remove null bytes operation\n */\nclass RemoveNullBytes extends Operation {\n\n    /**\n     * RemoveNullBytes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Remove null bytes\";\n        this.module = \"Default\";\n        this.description = \"Removes all null bytes (<code>0x00</code>) from the input.\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const output = [];\n        for (let i = 0; i < input.length; i++) {\n            if (input[i] !== 0) output.push(input[i]);\n        }\n        return output;\n    }\n\n}\n\nexport default RemoveNullBytes;\n"
  },
  {
    "path": "src/core/operations/RemoveWhitespace.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Remove whitespace operation\n */\nclass RemoveWhitespace extends Operation {\n\n    /**\n     * RemoveWhitespace constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Remove whitespace\";\n        this.module = \"Default\";\n        this.description = \"Optionally removes all spaces, carriage returns, line feeds, tabs and form feeds from the input data.<br><br>This operation also supports the removal of full stops which are sometimes used to represent non-printable bytes in ASCII output.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Spaces\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Carriage returns (\\\\r)\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Line feeds (\\\\n)\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Tabs\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Form feeds (\\\\f)\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Full stops\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [\n            removeSpaces,\n            removeCarriageReturns,\n            removeLineFeeds,\n            removeTabs,\n            removeFormFeeds,\n            removeFullStops\n        ] = args;\n        let data = input;\n\n        if (removeSpaces) data = data.replace(/ /g, \"\");\n        if (removeCarriageReturns) data = data.replace(/\\r/g, \"\");\n        if (removeLineFeeds) data = data.replace(/\\n/g, \"\");\n        if (removeTabs) data = data.replace(/\\t/g, \"\");\n        if (removeFormFeeds) data = data.replace(/\\f/g, \"\");\n        if (removeFullStops) data = data.replace(/\\./g, \"\");\n        return data;\n    }\n\n}\n\nexport default RemoveWhitespace;\n"
  },
  {
    "path": "src/core/operations/RenderImage.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport { fromBase64, toBase64 } from \"../lib/Base64.mjs\";\nimport { fromHex } from \"../lib/Hex.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {isImage} from \"../lib/FileType.mjs\";\n\n/**\n * Render Image operation\n */\nclass RenderImage extends Operation {\n\n    /**\n     * RenderImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Render Image\";\n        this.module = \"Image\";\n        this.description = \"Displays the input as an image. Supports the following formats:<br><br><ul><li>jpg/jpeg</li><li>png</li><li>gif</li><li>webp</li><li>bmp</li><li>ico</li></ul>\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Base64\", \"Hex\"]\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^(?:\\\\xff\\\\xd8\\\\xff|\\\\x89\\\\x50\\\\x4e\\\\x47|\\\\x47\\\\x49\\\\x46|.{8}\\\\x57\\\\x45\\\\x42\\\\x50|\\\\x42\\\\x4d)\",\n                flags: \"\",\n                args: [\"Raw\"],\n                useful: true,\n                output: {\n                    mime: \"image\"\n                }\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const inputFormat = args[0];\n\n        if (!input.length) return [];\n\n        // Convert input to raw bytes\n        switch (inputFormat) {\n            case \"Hex\":\n                input = fromHex(input);\n                break;\n            case \"Base64\":\n                // Don't trust the Base64 entered by the user.\n                // Unwrap it first, then re-encode later.\n                input = fromBase64(input, undefined, \"byteArray\");\n                break;\n            case \"Raw\":\n            default:\n                input = Utils.strToByteArray(input);\n                break;\n        }\n\n        // Determine file type\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type\");\n        }\n\n        return input;\n    }\n\n    /**\n     * Displays the image using HTML for web apps.\n     *\n     * @param {byteArray} data\n     * @returns {html}\n     */\n    async present(data) {\n        if (!data.length) return \"\";\n\n        let dataURI = \"data:\";\n\n        // Determine file type\n        const mime = isImage(data);\n        if (mime) {\n            dataURI += mime + \";\";\n        } else {\n            throw new OperationError(\"Invalid file type\");\n        }\n\n        // Add image data to URI\n        dataURI += \"base64,\" + toBase64(data);\n\n        return \"<img src='\" + dataURI + \"'>\";\n    }\n\n}\n\nexport default RenderImage;\n"
  },
  {
    "path": "src/core/operations/RenderMarkdown.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport MarkdownIt from \"markdown-it\";\nimport hljs from \"highlight.js\";\n\n/**\n * Render Markdown operation\n */\nclass RenderMarkdown extends Operation {\n\n    /**\n     * RenderMarkdown constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Render Markdown\";\n        this.module = \"Code\";\n        this.description = \"Renders input Markdown as HTML. HTML rendering is disabled to avoid XSS.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Markdown\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                name: \"Autoconvert URLs to links\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Enable syntax highlighting\",\n                type: \"boolean\",\n                value: true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const [convertLinks, enableHighlighting] = args,\n            md = new MarkdownIt({\n                linkify: convertLinks,\n                html: false, // Explicitly disable HTML rendering\n                highlight: function(str, lang) {\n                    if (lang && hljs.getLanguage(lang) && enableHighlighting) {\n                        try {\n                            return hljs.highlight(lang, str).value;\n                        } catch (__) {}\n                    }\n\n                    return \"\";\n                }\n            }),\n            rendered = md.render(input);\n\n        return `<div style=\"font-family: var(--primary-font-family)\">${rendered}</div>`;\n    }\n\n}\n\nexport default RenderMarkdown;\n"
  },
  {
    "path": "src/core/operations/ResizeImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime, ResizeStrategy } from \"jimp\";\n\n/**\n * Resize Image operation\n */\nclass ResizeImage extends Operation {\n    /**\n     * ResizeImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Resize Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Resizes an image to the specified width and height values.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Image_scaling\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Width\",\n                type: \"number\",\n                value: 100,\n                min: 1,\n            },\n            {\n                name: \"Height\",\n                type: \"number\",\n                value: 100,\n                min: 1,\n            },\n            {\n                name: \"Unit type\",\n                type: \"option\",\n                value: [\"Pixels\", \"Percent\"],\n            },\n            {\n                name: \"Maintain aspect ratio\",\n                type: \"boolean\",\n                value: false,\n            },\n            {\n                name: \"Resizing algorithm\",\n                type: \"option\",\n                value: [\n                    \"Nearest Neighbour\",\n                    \"Bilinear\",\n                    \"Bicubic\",\n                    \"Hermite\",\n                    \"Bezier\",\n                ],\n                defaultIndex: 1,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        let width = args[0],\n            height = args[1];\n        const unit = args[2],\n            aspect = args[3],\n            resizeAlg = args[4];\n\n        const resizeMap = {\n            \"Nearest Neighbour\": ResizeStrategy.NEAREST_NEIGHBOR,\n            Bilinear: ResizeStrategy.BILINEAR,\n            Bicubic: ResizeStrategy.BICUBIC,\n            Hermite: ResizeStrategy.HERMITE,\n            Bezier: ResizeStrategy.BEZIER,\n        };\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (unit === \"Percent\") {\n                width = image.width * (width / 100);\n                height = image.height * (height / 100);\n            }\n\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Resizing image...\");\n            if (aspect) {\n                image.scaleToFit({\n                    w: width,\n                    h: height,\n                    mode: resizeMap[resizeAlg],\n                });\n            } else {\n                image.resize({\n                    w: width,\n                    h: height,\n                    mode: resizeMap[resizeAlg],\n                });\n            }\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error resizing image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the resized image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default ResizeImage;\n"
  },
  {
    "path": "src/core/operations/Return.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Return operation\n */\nclass Return extends Operation {\n\n    /**\n     * Return constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Return\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        this.description = \"End execution of operations at this point in the recipe.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on.\n     * @param {Operation[]} state.opList - The list of operations in the recipe.\n     * @returns {Object} The updated state of the recipe.\n     */\n    run(state) {\n        state.progress = state.opList.length;\n        return state;\n    }\n\n}\n\nexport default Return;\n"
  },
  {
    "path": "src/core/operations/Reverse.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Reverse operation\n */\nclass Reverse extends Operation {\n\n    /**\n     * Reverse constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Reverse\";\n        this.module = \"Default\";\n        this.description = \"Reverses the input string.\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"By\",\n                \"type\": \"option\",\n                \"value\": [\"Byte\", \"Character\", \"Line\"],\n                \"defaultIndex\": 1\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        let i;\n        if (args[0] === \"Line\") {\n            const lines = [];\n            let line = [],\n                result = [];\n            for (i = 0; i < input.length; i++) {\n                if (input[i] === 0x0a) {\n                    lines.push(line);\n                    line = [];\n                } else {\n                    line.push(input[i]);\n                }\n            }\n            lines.push(line);\n            lines.reverse();\n            for (i = 0; i < lines.length; i++) {\n                result = result.concat(lines[i]);\n                result.push(0x0a);\n            }\n            return result.slice(0, input.length);\n        } else if (args[0] === \"Character\") {\n            const inputString = Utils.byteArrayToUtf8(input);\n            let result = \"\";\n            for (let i = inputString.length - 1; i >= 0; i--) {\n                const c = inputString.charCodeAt(i);\n                if (i > 0 && 0xdc00 <= c && c <= 0xdfff) {\n                    const c2 = inputString.charCodeAt(i - 1);\n                    if (0xd800 <= c2 && c2 <= 0xdbff) {\n                        // surrogates\n                        result += inputString.charAt(i - 1);\n                        result += inputString.charAt(i);\n                        i--;\n                        continue;\n                    }\n                }\n                result += inputString.charAt(i);\n            }\n            return Utils.strToUtf8ByteArray(result);\n        } else {\n            return input.reverse();\n        }\n    }\n\n}\n\nexport default Reverse;\n"
  },
  {
    "path": "src/core/operations/RisonDecode.mjs",
    "content": "/**\n * @author sg5506844 [sg5506844@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport rison from \"rison\";\n\n/**\n * Rison Decode operation\n */\nclass RisonDecode extends Operation {\n\n    /**\n     * RisonDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rison Decode\";\n        this.module = \"Encodings\";\n        this.description = \"Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork.\";\n        this.infoURL = \"https://github.com/Nanonid/rison\";\n        this.inputType = \"string\";\n        this.outputType = \"Object\";\n        this.args = [\n            {\n                name: \"Decode Option\",\n                type: \"editableOption\",\n                value: [\"Decode\", \"Decode Object\", \"Decode Array\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {Object}\n     */\n    run(input, args) {\n        const [decodeOption] = args;\n        switch (decodeOption) {\n            case \"Decode\":\n                return rison.decode(input);\n            case \"Decode Object\":\n                return rison.decode_object(input);\n            case \"Decode Array\":\n                return rison.decode_array(input);\n            default:\n                throw new OperationError(\"Invalid Decode option\");\n        }\n    }\n}\n\nexport default RisonDecode;\n"
  },
  {
    "path": "src/core/operations/RisonEncode.mjs",
    "content": "/**\n * @author sg5506844 [sg5506844@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport rison from \"rison\";\n\n/**\n * Rison Encode operation\n */\nclass RisonEncode extends Operation {\n\n    /**\n     * RisonEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rison Encode\";\n        this.module = \"Encodings\";\n        this.description = \"Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork.\";\n        this.infoURL = \"https://github.com/Nanonid/rison\";\n        this.inputType = \"Object\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Encode Option\",\n                type: \"option\",\n                value: [\"Encode\", \"Encode Object\", \"Encode Array\", \"Encode URI\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {Object} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [encodeOption] = args;\n        switch (encodeOption) {\n            case \"Encode\":\n                return rison.encode(input);\n            case \"Encode Object\":\n                return rison.encode_object(input);\n            case \"Encode Array\":\n                return rison.encode_array(input);\n            case \"Encode URI\":\n                return rison.encode_uri(input);\n            default:\n                throw new OperationError(\"Invalid encode option\");\n        }\n    }\n}\n\nexport default RisonEncode;\n"
  },
  {
    "path": "src/core/operations/RotateImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Rotate Image operation\n */\nclass RotateImage extends Operation {\n    /**\n     * RotateImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rotate Image\";\n        this.module = \"Image\";\n        this.description =\n            \"Rotates an image by the specified number of degrees.\";\n        this.infoURL = \"\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Rotation amount (degrees)\",\n                type: \"number\",\n                value: 90,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [degrees] = args;\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Rotating image...\");\n            image.rotate(degrees);\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error rotating image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the rotated image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default RotateImage;\n"
  },
  {
    "path": "src/core/operations/RotateLeft.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {rot, rotl, rotlCarry} from \"../lib/Rotate.mjs\";\n\n\n/**\n * Rotate left operation.\n */\nclass RotateLeft extends Operation {\n\n    /**\n     * RotateLeft constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rotate left\";\n        this.module = \"Default\";\n        this.description = \"Rotates each byte to the left by the number of bits specified, optionally carrying the excess bits over to the next byte. Currently only supports 8-bit values.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Amount\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Carry through\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        if (args[1]) {\n            return rotlCarry(input, args[0]);\n        } else {\n            return rot(input, args[0], rotl);\n        }\n    }\n\n    /**\n     * Highlight rotate left\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight rotate left in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n}\n\nexport default RotateLeft;\n"
  },
  {
    "path": "src/core/operations/RotateRight.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {rot, rotr, rotrCarry} from \"../lib/Rotate.mjs\";\n\n\n/**\n * Rotate right operation.\n */\nclass RotateRight extends Operation {\n\n    /**\n     * RotateRight constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Rotate right\";\n        this.module = \"Default\";\n        this.description = \"Rotates each byte to the right by the number of bits specified, optionally carrying the excess bits over to the next byte. Currently only supports 8-bit values.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Amount\",\n                type: \"number\",\n                value: 1\n            },\n            {\n                name: \"Carry through\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        if (args[1]) {\n            return rotrCarry(input, args[0]);\n        } else {\n            return rot(input, args[0], rotr);\n        }\n    }\n\n    /**\n     * Highlight rotate right\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight rotate right in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n}\n\nexport default RotateRight;\n"
  },
  {
    "path": "src/core/operations/SHA0.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * SHA0 operation\n */\nclass SHA0 extends Operation {\n\n    /**\n     * SHA0 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SHA0\";\n        this.module = \"Crypto\";\n        this.description = \"SHA-0 is a retronym applied to the original version of the 160-bit hash function published in 1993 under the name 'SHA'. It was withdrawn shortly after publication due to an undisclosed 'significant flaw' and replaced by the slightly revised version SHA-1. The message digest algorithm consists, by default, of 80 rounds.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SHA-1#SHA-0\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 80,\n                min: 16\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return runHash(\"sha0\", input, {rounds: args[0]});\n    }\n\n}\n\nexport default SHA0;\n"
  },
  {
    "path": "src/core/operations/SHA1.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * SHA1 operation\n */\nclass SHA1 extends Operation {\n\n    /**\n     * SHA1 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SHA1\";\n        this.module = \"Crypto\";\n        this.description = \"The SHA (Secure Hash Algorithm) hash functions were designed by the NSA. SHA-1 is the most established of the existing SHA hash functions and it is used in a variety of security applications and protocols.<br><br>However, SHA-1's collision resistance has been weakening as new attacks are discovered or improved. The message digest algorithm consists, by default, of 80 rounds.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SHA-1\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 80,\n                min: 16\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return runHash(\"sha1\", input, {rounds: args[0]});\n    }\n\n}\n\nexport default SHA1;\n"
  },
  {
    "path": "src/core/operations/SHA2.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * SHA2 operation\n */\nclass SHA2 extends Operation {\n\n    /**\n     * SHA2 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SHA2\";\n        this.module = \"Crypto\";\n        this.description = \"The SHA-2 (Secure Hash Algorithm 2) hash functions were designed by the NSA. SHA-2 includes significant changes from its predecessor, SHA-1. The SHA-2 family consists of hash functions with digests (hash values) that are 224, 256, 384 or 512 bits: SHA224, SHA256, SHA384, SHA512.<br><br><ul><li>SHA-512 operates on 64-bit words.</li><li>SHA-256 operates on 32-bit words.</li><li>SHA-384 is largely identical to SHA-512 but is truncated to 384 bytes.</li><li>SHA-224 is largely identical to SHA-256 but is truncated to 224 bytes.</li><li>SHA-512/224 and SHA-512/256 are truncated versions of SHA-512, but the initial values are generated using the method described in Federal Information Processing Standards (FIPS) PUB 180-4.</li></ul> The message digest algorithm for SHA256 variants consists, by default, of 64 rounds, and for SHA512 variants, it is, by default, 160.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SHA-2\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Size\",\n                type: \"argSelector\",\n                value: [\n                    {\n                        name: \"512\",\n                        on: [2],\n                        off: [1]\n                    },\n                    {\n                        name: \"384\",\n                        on: [2],\n                        off: [1]\n                    },\n                    {\n                        name: \"256\",\n                        on: [1],\n                        off: [2]\n                    },\n                    {\n                        name: \"224\",\n                        on: [1],\n                        off: [2]\n                    },\n                    {\n                        name: \"512/256\",\n                        on: [2],\n                        off: [1]\n                    },\n                    {\n                        name: \"512/224\",\n                        on: [2],\n                        off: [1]\n                    }\n                ]\n            },\n            {\n                name: \"Rounds\", // For SHA256 variants\n                type: \"number\",\n                value: 64,\n                min: 16\n            },\n            {\n                name: \"Rounds\", // For SHA512 variants\n                type: \"number\",\n                value: 160,\n                min: 32\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const size = args[0];\n        const rounds = (size === \"256\" || size === \"224\") ? args[1] : args[2];\n        return runHash(\"sha\" + size, input, {rounds: rounds});\n    }\n\n}\n\nexport default SHA2;\n"
  },
  {
    "path": "src/core/operations/SHA3.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport JSSHA3 from \"js-sha3\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * SHA3 operation\n */\nclass SHA3 extends Operation {\n\n    /**\n     * SHA3 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SHA3\";\n        this.module = \"Crypto\";\n        this.description = \"The SHA-3 (Secure Hash Algorithm 3) hash functions were released by NIST on August 5, 2015. Although part of the same series of standards, SHA-3 is internally quite different from the MD5-like structure of SHA-1 and SHA-2.<br><br>SHA-3 is a subset of the broader cryptographic primitive family Keccak designed by Guido Bertoni, Joan Daemen, Micha\\xebl Peeters, and Gilles Van Assche, building upon RadioGat\\xfan.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SHA-3\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Size\",\n                \"type\": \"option\",\n                \"value\": [\"512\", \"384\", \"256\", \"224\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const size = parseInt(args[0], 10);\n        let algo;\n\n        switch (size) {\n            case 224:\n                algo = JSSHA3.sha3_224;\n                break;\n            case 384:\n                algo = JSSHA3.sha3_384;\n                break;\n            case 256:\n                algo = JSSHA3.sha3_256;\n                break;\n            case 512:\n                algo = JSSHA3.sha3_512;\n                break;\n            default:\n                throw new OperationError(\"Invalid size\");\n        }\n\n        return algo(input);\n    }\n\n}\n\nexport default SHA3;\n"
  },
  {
    "path": "src/core/operations/SIGABA.mjs",
    "content": "/**\n * Emulation of the SIGABA machine.\n *\n * @author hettysymes\n * @copyright hettysymes 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {LETTERS} from \"../lib/Enigma.mjs\";\nimport {NUMBERS, CR_ROTORS, I_ROTORS, SigabaMachine, CRRotor, IRotor} from \"../lib/SIGABA.mjs\";\n\n/**\n * Sigaba operation\n */\nclass Sigaba extends Operation {\n\n    /**\n     * Sigaba constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SIGABA\";\n        this.module = \"Bletchley\";\n        this.description = \"Encipher/decipher with the WW2 SIGABA machine. <br><br>SIGABA, otherwise known as ECM Mark II, was used by the United States for message encryption during WW2 up to the 1950s. It was developed in the 1930s by the US Army and Navy, and has up to this day never been broken. Consisting of 15 rotors: 5 cipher rotors and 10 rotors (5 control rotors and 5 index rotors) controlling the stepping of the cipher rotors, the rotor stepping for SIGABA is much more complex than other rotor machines of its time, such as Enigma. All example rotor wirings are random example sets.<br><br>To configure rotor wirings, for the cipher and control rotors enter a string of letters which map from A to Z, and for the index rotors enter a sequence of numbers which map from 0 to 9. Note that encryption is not the same as decryption, so first choose the desired mode. <br><br> Note: Whilst this has been tested against other software emulators, it has not been tested against hardware.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SIGABA\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"1st (left-hand) cipher rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"1st cipher rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"1st cipher rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"2nd cipher rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"2nd cipher rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"2nd cipher rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"3rd (middle) cipher rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"3rd cipher rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"3rd cipher rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"4th cipher rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"4th cipher rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"4th cipher rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"5th (right-hand) cipher rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"5th cipher rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"5th cipher rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"1st (left-hand) control rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"1st control rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"1st control rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"2nd control rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"2nd control rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"2nd control rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"3rd (middle) control rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"3rd control rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"3rd control rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"4th control rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"4th control rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"4th control rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"5th (right-hand) control rotor\",\n                type: \"editableOption\",\n                value: CR_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"5th control rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"5th control rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"1st (left-hand) index rotor\",\n                type: \"editableOption\",\n                value: I_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"1st index rotor initial value\",\n                type: \"option\",\n                value: NUMBERS\n            },\n            {\n                name: \"2nd index rotor\",\n                type: \"editableOption\",\n                value: I_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"2nd index rotor initial value\",\n                type: \"option\",\n                value: NUMBERS\n            },\n            {\n                name: \"3rd (middle) index rotor\",\n                type: \"editableOption\",\n                value: I_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"3rd index rotor initial value\",\n                type: \"option\",\n                value: NUMBERS\n            },\n            {\n                name: \"4th index rotor\",\n                type: \"editableOption\",\n                value: I_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"4th index rotor initial value\",\n                type: \"option\",\n                value: NUMBERS\n            },\n            {\n                name: \"5th (right-hand) index rotor\",\n                type: \"editableOption\",\n                value: I_ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"5th index rotor initial value\",\n                type: \"option\",\n                value: NUMBERS\n            },\n            {\n                name: \"SIGABA mode\",\n                type: \"option\",\n                value: [\"Encrypt\", \"Decrypt\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const sigabaSwitch = args[40];\n        const cipherRotors = [];\n        const controlRotors = [];\n        const indexRotors = [];\n        for (let i=0; i<5; i++) {\n            const rotorWiring = args[i*3];\n            cipherRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1]));\n        }\n        for (let i=5; i<10; i++) {\n            const rotorWiring = args[i*3];\n            controlRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1]));\n        }\n        for (let i=15; i<20; i++) {\n            const rotorWiring = args[i*2];\n            indexRotors.push(new IRotor(rotorWiring, args[i*2+1]));\n        }\n        const sigaba = new SigabaMachine(cipherRotors, controlRotors, indexRotors);\n        let result;\n        if (sigabaSwitch === \"Encrypt\") {\n            result = sigaba.encrypt(input);\n        } else if (sigabaSwitch === \"Decrypt\") {\n            result = sigaba.decrypt(input);\n        }\n        return result;\n    }\n\n}\nexport default Sigaba;\n"
  },
  {
    "path": "src/core/operations/SM2Decrypt.mjs",
    "content": "/**\n * @author flakjacket95 [dflack95@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Operation from \"../Operation.mjs\";\n\nimport { SM2 } from \"../lib/SM2.mjs\";\n\n/**\n * SM2Decrypt operation\n */\nclass SM2Decrypt extends Operation {\n\n    /**\n     * SM2Decrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SM2 Decrypt\";\n        this.module = \"Crypto\";\n        this.description = \"Decrypts a message utilizing the SM2 standard\";\n        this.infoURL = \"\"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)\n        this.inputType = \"string\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Private Key\",\n                type: \"string\",\n                value: \"DEADBEEF\"\n            },\n            {\n                \"name\": \"Input Format\",\n                \"type\": \"option\",\n                \"value\": [\"C1C3C2\", \"C1C2C3\"],\n                \"defaultIndex\": 0\n            },\n            {\n                name: \"Curve\",\n                type: \"option\",\n                \"value\": [\"sm2p256v1\"],\n                \"defaultIndex\": 0\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const [privateKey, inputFormat, curveName] = args;\n\n        if (privateKey.length !== 64) {\n            throw new OperationError(\"Input private key must be in hex; and should be 32 bytes\");\n        }\n\n        const sm2 = new SM2(curveName, inputFormat);\n        sm2.setPrivateKey(privateKey);\n\n        const result = sm2.decrypt(input);\n        return result;\n    }\n\n}\n\nexport default SM2Decrypt;\n"
  },
  {
    "path": "src/core/operations/SM2Encrypt.mjs",
    "content": "/**\n * @author flakjacket95 [dflack95@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Operation from \"../Operation.mjs\";\n\nimport { SM2 } from \"../lib/SM2.mjs\";\n\n/**\n * SM2 Encrypt operation\n */\nclass SM2Encrypt extends Operation {\n\n    /**\n     * SM2Encrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SM2 Encrypt\";\n        this.module = \"Crypto\";\n        this.description = \"Encrypts a message utilizing the SM2 standard\";\n        this.infoURL = \"\"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n\n        this.args = [\n            {\n                name: \"Public Key X\",\n                type: \"string\",\n                value: \"DEADBEEF\"\n            },\n            {\n                name: \"Public Key Y\",\n                type: \"string\",\n                value: \"DEADBEEF\"\n            },\n            {\n                \"name\": \"Output Format\",\n                \"type\": \"option\",\n                \"value\": [\"C1C3C2\", \"C1C2C3\"],\n                \"defaultIndex\": 0\n            },\n            {\n                name: \"Curve\",\n                type: \"option\",\n                \"value\": [\"sm2p256v1\"],\n                \"defaultIndex\": 0\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const [publicKeyX, publicKeyY, outputFormat, curveName] = args;\n        this.outputFormat = outputFormat;\n\n        if (publicKeyX.length !== 64 || publicKeyY.length !== 64) {\n            throw new OperationError(\"Invalid Public Key - Ensure each component is 32 bytes in size and in hex\");\n        }\n\n        const sm2 = new SM2(curveName, outputFormat);\n        sm2.setPublicKey(publicKeyX, publicKeyY);\n\n        const result = sm2.encrypt(new Uint8Array(input));\n        return result;\n    }\n}\n\nexport default SM2Encrypt;\n"
  },
  {
    "path": "src/core/operations/SM3.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Sm3 from \"crypto-api/src/hasher/sm3.mjs\";\nimport {toHex} from \"crypto-api/src/encoder/hex.mjs\";\n\n/**\n * SM3 operation\n */\nclass SM3 extends Operation {\n\n    /**\n     * SM3 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SM3\";\n        this.module = \"Crypto\";\n        this.description = \"SM3 is a cryptographic hash function used in the Chinese National Standard. SM3 is mainly used in digital signatures, message authentication codes, and pseudorandom number generators. The message digest algorithm consists, by default, of 64 rounds and length of 256.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SM3_(hash_function)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Length\",\n                type: \"number\",\n                value: 256\n            },\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 64,\n                min: 16\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const msg = Utils.arrayBufferToStr(input, false);\n        const hasher = new Sm3({length: args[0], rounds: args[1]});\n        hasher.update(msg);\n        return toHex(hasher.finalize());\n    }\n}\n\nexport default SM3;\n"
  },
  {
    "path": "src/core/operations/SM4Decrypt.mjs",
    "content": "/**\n * @author swesven\n * @copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { decryptSM4 } from \"../lib/SM4.mjs\";\n\n/**\n * SM4 Decrypt operation\n */\nclass SM4Decrypt extends Operation {\n\n    /**\n     * SM4Encrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SM4 Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"SM4 is a 128-bit block cipher, currently established as a national standard (GB/T 32907-2016) of China.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SM4_(cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\", \"CBC/NoPadding\", \"ECB/NoPadding\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            [,, mode, inputType, outputType] = args;\n\n        if (key.length !== 16)\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nSM4 uses a key length of 16 bytes (128 bits).`);\n        if (iv.length !== 16 && !mode.startsWith(\"ECB\"))\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nSM4 uses an IV length of 16 bytes (128 bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n\n        input = Utils.convertToByteArray(input, inputType);\n        const output = decryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith(\"NoPadding\"));\n        return outputType === \"Hex\" ? toHex(output) : Utils.byteArrayToUtf8(output);\n    }\n\n}\n\nexport default SM4Decrypt;\n"
  },
  {
    "path": "src/core/operations/SM4Encrypt.mjs",
    "content": "/**\n * @author swesven\n * @copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { encryptSM4 } from \"../lib/SM4.mjs\";\n\n/**\n * SM4 Encrypt operation\n */\nclass SM4Encrypt extends Operation {\n\n    /**\n     * SM4Encrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SM4 Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"SM4 is a 128-bit block cipher, currently established as a national standard (GB/T 32907-2016) of China. Multiple block cipher modes are supported. When using CBC or ECB mode, the PKCS#7 padding scheme is used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SM4_(cipher)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            [,, mode, inputType, outputType] = args;\n\n        if (key.length !== 16)\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nSM4 uses a key length of 16 bytes (128 bits).`);\n        if (iv.length !== 16 && !mode.startsWith(\"ECB\"))\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nSM4 uses an IV length of 16 bytes (128 bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n\n        input = Utils.convertToByteArray(input, inputType);\n        const output = encryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith(\"NoPadding\"));\n        return outputType === \"Hex\" ? toHex(output) : Utils.byteArrayToUtf8(output);\n    }\n\n}\n\nexport default SM4Encrypt;\n"
  },
  {
    "path": "src/core/operations/SQLBeautify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\nimport { format } from \"sql-formatter\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * SQL Beautify operation\n */\nclass SQLBeautify extends Operation {\n\n    /**\n     * SQLBeautify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SQL Beautify\";\n        this.module = \"Code\";\n        this.description = \"Indents and prettifies Structured Query Language (SQL) code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Indent string\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\t\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const indentStr = args[0];\n        // Extract and replace bind variables like :Bind1 with __BIND_0__\n        const bindRegex = /:\\w+/g;\n        const bindMap = {};\n        let bindCounter=0;\n        const placeholderInput = input.replace(bindRegex, (match) => {\n            const placeholder = `__BIND_${bindCounter++}__`;\n            bindMap[placeholder] = match;\n            return placeholder;\n        });\n        // Format the SQL with chosen options\n        let formatted= format(placeholderInput, {\n            language: \"mysql\", // Use MySQL as the default dialect for better compatibility with real-world SQL\n            useTabs: indentStr===\"\\t\", // true if tab, false if spaces\n            tabWidth: indentStr.length || 4,     // fallback if empty\n            indentStyle: \"standard\"              // fine for most SQL\n        });\n        // Replace placeholders back with original bind variables\n        formatted = formatted.replace(/__BIND_\\d+__/g, match => bindMap[match] || match);\n\n        return formatted;\n    }\n\n}\n\nexport default SQLBeautify;\n"
  },
  {
    "path": "src/core/operations/SQLMinify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport vkbeautify from \"vkbeautify\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * SQL Minify operation\n */\nclass SQLMinify extends Operation {\n\n    /**\n     * SQLMinify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SQL Minify\";\n        this.module = \"Code\";\n        this.description = \"Compresses Structured Query Language (SQL) code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return vkbeautify.sqlmin(input);\n    }\n\n}\n\nexport default SQLMinify;\n"
  },
  {
    "path": "src/core/operations/SSDEEP.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport ssdeepjs from \"ssdeep.js\";\n\n/**\n * SSDEEP operation\n */\nclass SSDEEP extends Operation {\n\n    /**\n     * SSDEEP constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SSDEEP\";\n        this.module = \"Crypto\";\n        this.description = \"SSDEEP is a program for computing context triggered piecewise hashes (CTPH). Also called fuzzy hashes, CTPH can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>SSDEEP hashes are now widely used for simple identification purposes (e.g. the 'Basic Properties' section in VirusTotal). Although 'better' fuzzy hashes are available, SSDEEP is still one of the primary choices because of its speed and being a de facto standard.<br><br>This operation is fundamentally the same as the CTPH operation, however their outputs differ in format.\";\n        this.infoURL = \"https://forensics.wiki/ssdeep\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return ssdeepjs.digest(input);\n    }\n\n}\n\nexport default SSDEEP;\n"
  },
  {
    "path": "src/core/operations/SUB.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { bitOp, sub, BITWISE_OP_DELIMS } from \"../lib/BitwiseOp.mjs\";\n\n/**\n * SUB operation\n */\nclass SUB extends Operation {\n\n    /**\n     * SUB constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"SUB\";\n        this.module = \"Default\";\n        this.description = \"SUB the input with the given key (e.g. <code>fe023da5</code>), MOD 255\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bitwise_operation#Bitwise_operators\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": BITWISE_OP_DELIMS\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string || \"\", args[0].option);\n\n        return bitOp(input, key, sub);\n    }\n\n    /**\n     * Highlight SUB\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight SUB in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default SUB;\n"
  },
  {
    "path": "src/core/operations/Salsa20.mjs",
    "content": "/**\n * @author joostrijneveld [joost@joostrijneveld.nl]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { salsa20Block } from \"../lib/Salsa20.mjs\";\n\n/**\n * Salsa20 operation\n */\nclass Salsa20 extends Operation {\n\n    /**\n     * Salsa20 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Salsa20\";\n        this.module = \"Ciphers\";\n        this.description = \"Salsa20 is a stream cipher designed by Daniel J. Bernstein and submitted to the eSTREAM project; Salsa20/8 and Salsa20/12 are round-reduced variants. It is closely related to the ChaCha stream cipher.<br><br><b>Key:</b> Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> Salsa20 uses a nonce of 8 bytes (64 bits).<br><br><b>Counter:</b> Salsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Salsa20\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Nonce\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\", \"Integer\"]\n            },\n            {\n                \"name\": \"Counter\",\n                \"type\": \"number\",\n                \"value\": 0,\n                \"min\": 0\n            },\n            {\n                \"name\": \"Rounds\",\n                \"type\": \"option\",\n                \"value\": [\"20\", \"12\", \"8\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            nonceType = args[1].option,\n            rounds = parseInt(args[3], 10),\n            inputType = args[4],\n            outputType = args[5];\n\n        if (key.length !== 16 && key.length !== 32) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes.\n\nSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`);\n        }\n\n        let counter, nonce;\n        if (nonceType === \"Integer\") {\n            nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, \"little\");\n        } else {\n            nonce = Utils.convertToByteArray(args[1].string, args[1].option);\n            if (!(nonce.length === 8)) {\n                throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.\n\nSalsa20 uses a nonce of 8 bytes (64 bits).`);\n            }\n        }\n        counter = Utils.intToByteArray(args[2], 8, \"little\");\n\n        const output = [];\n        input = Utils.convertToByteArray(input, inputType);\n\n        let counterAsInt = Utils.byteArrayToInt(counter, \"little\");\n        for (let i = 0; i < input.length; i += 64) {\n            counter = Utils.intToByteArray(counterAsInt, 8, \"little\");\n            const stream = salsa20Block(key, nonce, counter, rounds);\n            for (let j = 0; j < 64 && i + j < input.length; j++) {\n                output.push(input[i + j] ^ stream[j]);\n            }\n            counterAsInt++;\n        }\n\n        if (outputType === \"Hex\") {\n            return toHex(output);\n        } else {\n            return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);\n        }\n    }\n\n    /**\n     * Highlight Salsa20\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        const inputType = args[4],\n            outputType = args[5];\n        if (inputType === \"Raw\" && outputType === \"Raw\") {\n            return pos;\n        }\n    }\n\n    /**\n     * Highlight Salsa20 in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        const inputType = args[4],\n            outputType = args[5];\n        if (inputType === \"Raw\" && outputType === \"Raw\") {\n            return pos;\n        }\n    }\n\n}\n\nexport default Salsa20;\n"
  },
  {
    "path": "src/core/operations/ScanForEmbeddedFiles.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { scanForFileTypes } from \"../lib/FileType.mjs\";\nimport { FILE_SIGNATURES } from \"../lib/FileSignatures.mjs\";\n\n/**\n * Scan for Embedded Files operation\n */\nclass ScanForEmbeddedFiles extends Operation {\n\n    /**\n     * ScanForEmbeddedFiles constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Scan for Embedded Files\";\n        this.module = \"Default\";\n        this.description = \"Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.<br><br>WARNING: Files over about 100KB in size will take a VERY long time to process.\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_file_signatures\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = Object.keys(FILE_SIGNATURES).map(cat => {\n            return {\n                name: cat,\n                type: \"boolean\",\n                value: cat === \"Miscellaneous\" ? false : true\n            };\n        });\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let output = \"Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treated as reliable. Any sufficiently long file is likely to contain these magic bytes coincidentally.\\n\",\n            numFound = 0;\n        const categories = [],\n            data = new Uint8Array(input);\n\n        args.forEach((cat, i) => {\n            if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);\n        });\n\n        const types = scanForFileTypes(data, categories);\n\n        if (types.length) {\n            types.forEach(type => {\n                numFound++;\n                output += `\\nOffset ${type.offset} (0x${Utils.hex(type.offset)}):\n  File type:   ${type.fileDetails.name}\n  Extension:   ${type.fileDetails.extension}\n  MIME type:   ${type.fileDetails.mime}\\n`;\n\n                if (type?.fileDetails?.description?.length) {\n                    output += `  Description: ${type.fileDetails.description}\\n`;\n                }\n            });\n        }\n\n        if (numFound === 0) {\n            output += \"\\nNo embedded files were found.\";\n        }\n\n        return output;\n    }\n\n}\n\nexport default ScanForEmbeddedFiles;\n"
  },
  {
    "path": "src/core/operations/ScatterChart.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport * as d3temp from \"d3\";\nimport * as nodomtemp from \"nodom\";\nimport { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from \"../lib/Charts.mjs\";\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\nconst d3 = d3temp.default ? d3temp.default : d3temp;\nconst nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;\n\n/**\n * Scatter chart operation\n */\nclass ScatterChart extends Operation {\n\n    /**\n     * ScatterChart constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Scatter chart\";\n        this.module = \"Charts\";\n        this.description = \"Plots two-variable data as single points on a graph.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Scatter_plot\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                name: \"Record delimiter\",\n                type: \"option\",\n                value: RECORD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"Field delimiter\",\n                type: \"option\",\n                value: FIELD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"Use column headers as labels\",\n                type: \"boolean\",\n                value: true,\n            },\n            {\n                name: \"X label\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Y label\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Colour\",\n                type: \"string\",\n                value: COLOURS.max,\n            },\n            {\n                name: \"Point radius\",\n                type: \"number\",\n                value: 10,\n            },\n            {\n                name: \"Use colour from third column\",\n                type: \"boolean\",\n                value: false,\n            }\n        ];\n    }\n\n    /**\n     * Scatter chart operation.\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const recordDelimiter = Utils.charRep(args[0]),\n            fieldDelimiter = Utils.charRep(args[1]),\n            columnHeadingsAreIncluded = args[2],\n            fillColour = Utils.escapeHtml(args[5]),\n            radius = args[6],\n            colourInInput = args[7],\n            dimension = 500;\n\n        let xLabel = args[3],\n            yLabel = args[4];\n\n        const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues;\n        const { headings, values } = dataFunction(\n            input,\n            recordDelimiter,\n            fieldDelimiter,\n            columnHeadingsAreIncluded\n        );\n\n        if (headings) {\n            xLabel = headings.x;\n            yLabel = headings.y;\n        }\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${dimension} ${dimension}`);\n\n        const margin = {\n                top: 10,\n                right: 0,\n                bottom: 40,\n                left: 30,\n            },\n            width = dimension - margin.left - margin.right,\n            height = dimension - margin.top - margin.bottom,\n            marginedSpace = svg.append(\"g\")\n                .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\");\n\n        const xExtent = d3.extent(values, d => d[0]),\n            xDelta = xExtent[1] - xExtent[0],\n            yExtent = d3.extent(values, d => d[1]),\n            yDelta = yExtent[1] - yExtent[0],\n            xAxis = d3.scaleLinear()\n                .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)])\n                .range([0, width]),\n            yAxis = d3.scaleLinear()\n                .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)])\n                .range([height, 0]);\n\n        marginedSpace.append(\"clipPath\")\n            .attr(\"id\", \"clip\")\n            .append(\"rect\")\n            .attr(\"width\", width)\n            .attr(\"height\", height);\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"points\")\n            .attr(\"clip-path\", \"url(#clip)\")\n            .selectAll(\"circle\")\n            .data(values)\n            .enter()\n            .append(\"circle\")\n            .attr(\"cx\", (d) => xAxis(d[0]))\n            .attr(\"cy\", (d) => yAxis(d[1]))\n            .attr(\"r\", d => radius)\n            .attr(\"fill\", d => {\n                return colourInInput ? d[2] : fillColour;\n            })\n            .attr(\"stroke\", \"rgba(0, 0, 0, 0.5)\")\n            .attr(\"stroke-width\", \"0.5\")\n            .append(\"title\")\n            .text(d => {\n                const x = d[0],\n                    y = d[1],\n                    tooltip = `X: ${x}\\n\n                               Y: ${y}\\n\n                    `.replace(/\\s{2,}/g, \"\\n\");\n                return tooltip;\n            });\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"axis axis--y\")\n            .call(d3.axisLeft(yAxis).tickSizeOuter(-width));\n\n        svg.append(\"text\")\n            .attr(\"transform\", \"rotate(-90)\")\n            .attr(\"y\", -margin.left)\n            .attr(\"x\", -(height / 2))\n            .attr(\"dy\", \"1em\")\n            .style(\"text-anchor\", \"middle\")\n            .text(yLabel);\n\n        marginedSpace.append(\"g\")\n            .attr(\"class\", \"axis axis--x\")\n            .attr(\"transform\", \"translate(0,\" + height + \")\")\n            .call(d3.axisBottom(xAxis).tickSizeOuter(-height));\n\n        svg.append(\"text\")\n            .attr(\"x\", width / 2)\n            .attr(\"y\", dimension)\n            .style(\"text-anchor\", \"middle\")\n            .text(xLabel);\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n}\n\nexport default ScatterChart;\n"
  },
  {
    "path": "src/core/operations/Scrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport scryptsy from \"scryptsy\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * Scrypt operation\n */\nclass Scrypt extends Operation {\n\n    /**\n     * Scrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Scrypt\";\n        this.module = \"Crypto\";\n        this.description = \"scrypt is a password-based key derivation function (PBKDF) created by Colin Percival. The algorithm was specifically designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory. In 2016, the scrypt algorithm was published by IETF as RFC 7914.<br><br>Enter the password in the input to generate its hash.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Scrypt\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Salt\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"Base64\", \"UTF8\", \"Latin1\"]\n            },\n            {\n                \"name\": \"Iterations (N)\",\n                \"type\": \"number\",\n                \"value\": 16384\n            },\n            {\n                \"name\": \"Memory factor (r)\",\n                \"type\": \"number\",\n                \"value\": 8\n            },\n            {\n                \"name\": \"Parallelization factor (p)\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"Key length\",\n                \"type\": \"number\",\n                \"value\": 64\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const salt = Buffer.from(Utils.convertToByteArray(args[0].string || \"\", args[0].option)),\n            iterations = args[1],\n            memFactor = args[2],\n            parallelFactor = args[3],\n            keyLength = args[4];\n\n        try {\n            const data = scryptsy(\n                input, salt, iterations, memFactor, parallelFactor, keyLength,\n                p => {\n                    // Progress callback\n                    if (isWorkerEnvironment())\n                        self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`);\n                }\n            );\n\n            return data.toString(\"hex\");\n        } catch (err) {\n            throw new OperationError(\"Error: \" + err.toString());\n        }\n    }\n\n}\n\nexport default Scrypt;\n"
  },
  {
    "path": "src/core/operations/SeriesChart.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport * as d3temp from \"d3\";\nimport * as nodomtemp from \"nodom\";\nimport { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from \"../lib/Charts.mjs\";\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\nconst d3 = d3temp.default ? d3temp.default : d3temp;\nconst nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;\n\n/**\n * Series chart operation\n */\nclass SeriesChart extends Operation {\n\n    /**\n     * SeriesChart constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Series chart\";\n        this.module = \"Charts\";\n        this.description = \"A time series graph is a line graph of repeated measurements taken over regular time intervals.\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                name: \"Record delimiter\",\n                type: \"option\",\n                value: RECORD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"Field delimiter\",\n                type: \"option\",\n                value: FIELD_DELIMITER_OPTIONS,\n            },\n            {\n                name: \"X label\",\n                type: \"string\",\n                value: \"\",\n            },\n            {\n                name: \"Point radius\",\n                type: \"number\",\n                value: 1,\n            },\n            {\n                name: \"Series colours\",\n                type: \"string\",\n                value: \"mediumseagreen, dodgerblue, tomato\",\n            },\n        ];\n    }\n\n    /**\n     * Series chart operation.\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const recordDelimiter = Utils.charRep(args[0]),\n            fieldDelimiter = Utils.charRep(args[1]),\n            xLabel = args[2],\n            pipRadius = args[3],\n            // Escape HTML from all colours to prevent reflected XSS. See https://github.com/gchq/CyberChef/issues/1265\n            seriesColours = args[4].split(\",\").map((colour) => {\n                return Utils.escapeHtml(colour);\n            }),\n            svgWidth = 500,\n            interSeriesPadding = 20,\n            xAxisHeight = 50,\n            seriesLabelWidth = 50,\n            seriesHeight = 100,\n            seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding;\n\n        const { xValues, series } = getSeriesValues(input, recordDelimiter, fieldDelimiter),\n            allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight),\n            svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding;\n\n        const document = new nodom.Document();\n        let svg = document.createElement(\"svg\");\n        svg = d3.select(svg)\n            .attr(\"width\", \"100%\")\n            .attr(\"height\", \"100%\")\n            .attr(\"viewBox\", `0 0 ${svgWidth} ${svgHeight}`);\n\n        const xAxis = d3.scalePoint()\n            .domain(xValues)\n            .range([0, seriesWidth]);\n\n        svg.append(\"g\")\n            .attr(\"class\", \"axis axis--x\")\n            .attr(\"transform\", `translate(${seriesLabelWidth}, ${xAxisHeight})`)\n            .call(\n                d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => {\n                    return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0;\n                }))\n            );\n\n        svg.append(\"text\")\n            .attr(\"x\", svgWidth / 2)\n            .attr(\"y\", xAxisHeight / 2)\n            .style(\"text-anchor\", \"middle\")\n            .text(xLabel);\n\n        const tooltipText = {},\n            tooltipAreaWidth = seriesWidth / xValues.length;\n\n        xValues.forEach(x => {\n            const tooltip = [];\n\n            series.forEach(serie => {\n                const y = serie.data[x];\n                if (typeof y === \"undefined\") return;\n\n                tooltip.push(`${serie.name}: ${y}`);\n            });\n\n            tooltipText[x] = tooltip.join(\"\\n\");\n        });\n\n        const chartArea = svg.append(\"g\")\n            .attr(\"transform\", `translate(${seriesLabelWidth}, ${xAxisHeight})`);\n\n        chartArea\n            .append(\"g\")\n            .selectAll(\"rect\")\n            .data(xValues)\n            .enter()\n            .append(\"rect\")\n            .attr(\"x\", x => {\n                return xAxis(x) - (tooltipAreaWidth / 2);\n            })\n            .attr(\"y\", 0)\n            .attr(\"width\", tooltipAreaWidth)\n            .attr(\"height\", allSeriesHeight)\n            .attr(\"stroke\", \"none\")\n            .attr(\"fill\", \"transparent\")\n            .append(\"title\")\n            .text(x => {\n                return `${x}\\n\n                    --\\n\n                    ${tooltipText[x]}\\n\n                `.replace(/\\s{2,}/g, \"\\n\");\n            });\n\n        const yAxesArea = svg.append(\"g\")\n            .attr(\"transform\", `translate(0, ${xAxisHeight})`);\n\n        series.forEach((serie, seriesIndex) => {\n            const yExtent = d3.extent(Object.values(serie.data)),\n                yAxis = d3.scaleLinear()\n                    .domain(yExtent)\n                    .range([seriesHeight, 0]);\n\n            const seriesGroup = chartArea\n                .append(\"g\")\n                .attr(\"transform\", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`);\n\n            let path = \"\";\n            xValues.forEach((x, xIndex) => {\n                let nextX = xValues[xIndex + 1],\n                    y = serie.data[x],\n                    nextY= serie.data[nextX];\n\n                if (typeof y === \"undefined\" || typeof nextY === \"undefined\") return;\n\n                x = xAxis(x); nextX = xAxis(nextX);\n                y = yAxis(y); nextY = yAxis(nextY);\n\n                path += `M ${x} ${y} L ${nextX} ${nextY} z `;\n            });\n\n            seriesGroup\n                .append(\"path\")\n                .attr(\"d\", path)\n                .attr(\"fill\", \"none\")\n                .attr(\"stroke\", seriesColours[seriesIndex % seriesColours.length])\n                .attr(\"stroke-width\", \"1\");\n\n            xValues.forEach(x => {\n                const y = serie.data[x];\n                if (typeof y === \"undefined\") return;\n\n                seriesGroup\n                    .append(\"circle\")\n                    .attr(\"cx\", xAxis(x))\n                    .attr(\"cy\", yAxis(y))\n                    .attr(\"r\", pipRadius)\n                    .attr(\"fill\", seriesColours[seriesIndex % seriesColours.length])\n                    .append(\"title\")\n                    .text(d => {\n                        return `${x}\\n\n                            --\\n\n                            ${tooltipText[x]}\\n\n                        `.replace(/\\s{2,}/g, \"\\n\");\n                    });\n            });\n\n            yAxesArea\n                .append(\"g\")\n                .attr(\"transform\", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)\n                .attr(\"class\", \"axis axis--y\")\n                .call(d3.axisLeft(yAxis).ticks(5));\n\n            yAxesArea\n                .append(\"g\")\n                .attr(\"transform\", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)\n                .append(\"text\")\n                .style(\"text-anchor\", \"middle\")\n                .attr(\"transform\", \"rotate(-90)\")\n                .text(serie.name);\n        });\n\n        return svg._groups[0][0].outerHTML;\n    }\n\n}\n\nexport default SeriesChart;\n"
  },
  {
    "path": "src/core/operations/SetDifference.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Set Difference operation\n */\nclass SetDifference extends Operation {\n\n    /**\n     * Set Difference constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Set Difference\";\n        this.module = \"Default\";\n        this.description = \"Calculates the difference, or relative complement, of two sets.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Complement_(set_theory)#Relative_complement\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Sample delimiter\",\n                type: \"binaryString\",\n                value: \"\\\\n\\\\n\"\n            },\n            {\n                name: \"Item delimiter\",\n                type: \"binaryString\",\n                value: \",\"\n            },\n        ];\n    }\n\n    /**\n     * Validate input length\n     *\n     * @param {Object[]} sets\n     * @throws {Error} if not two sets\n     */\n    validateSampleNumbers(sets) {\n        if (!sets || (sets.length !== 2)) {\n            throw new OperationError(\"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\");\n        }\n    }\n\n    /**\n     * Run the difference operation\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     * @throws {OperationError}\n     */\n    run(input, args) {\n        [this.sampleDelim, this.itemDelimiter] = args;\n        const sets = input.split(this.sampleDelim);\n\n        this.validateSampleNumbers(sets);\n\n        return this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter)));\n    }\n\n    /**\n     * Get elements in set a that are not in set b\n     *\n     * @param {Object[]} a\n     * @param {Object[]} b\n     * @returns {Object[]}\n     */\n    runSetDifference(a, b) {\n        return a\n            .filter((item) => {\n                return b.indexOf(item) === -1;\n            })\n            .join(this.itemDelimiter);\n    }\n\n}\n\nexport default SetDifference;\n"
  },
  {
    "path": "src/core/operations/SetIntersection.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Set Intersection operation\n */\nclass SetIntersection extends Operation {\n\n    /**\n     * Set Intersection constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Set Intersection\";\n        this.module = \"Default\";\n        this.description = \"Calculates the intersection of two sets.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Intersection_(set_theory)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Sample delimiter\",\n                type: \"binaryString\",\n                value: \"\\\\n\\\\n\"\n            },\n            {\n                name: \"Item delimiter\",\n                type: \"binaryString\",\n                value: \",\"\n            },\n        ];\n    }\n\n    /**\n     * Validate input length\n     *\n     * @param {Object[]} sets\n     * @throws {Error} if not two sets\n     */\n    validateSampleNumbers(sets) {\n        if (!sets || (sets.length !== 2)) {\n            throw new OperationError(\"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\");\n        }\n    }\n\n    /**\n     * Run the intersection operation\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     * @throws {OperationError}\n     */\n    run(input, args) {\n        [this.sampleDelim, this.itemDelimiter] = args;\n        const sets = input.split(this.sampleDelim);\n\n        this.validateSampleNumbers(sets);\n\n        return this.runIntersect(...sets.map(s => s.split(this.itemDelimiter)));\n    }\n\n    /**\n     * Get the intersection of the two sets.\n     *\n     * @param {Object[]} a\n     * @param {Object[]} b\n     * @returns {Object[]}\n     */\n    runIntersect(a, b) {\n        return a\n            .filter((item) => {\n                return b.indexOf(item) > -1;\n            })\n            .join(this.itemDelimiter);\n    }\n\n}\n\nexport default SetIntersection;\n"
  },
  {
    "path": "src/core/operations/SetUnion.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Set Union operation\n */\nclass SetUnion extends Operation {\n\n    /**\n     * Set Union constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Set Union\";\n        this.module = \"Default\";\n        this.description = \"Calculates the union of two sets.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Union_(set_theory)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Sample delimiter\",\n                type: \"binaryString\",\n                value: \"\\\\n\\\\n\"\n            },\n            {\n                name: \"Item delimiter\",\n                type: \"binaryString\",\n                value: \",\"\n            },\n        ];\n    }\n\n    /**\n     * Validate input length\n     *\n     * @param {Object[]} sets\n     * @throws {Error} if not two sets\n     */\n    validateSampleNumbers(sets) {\n        if (!sets || (sets.length !== 2)) {\n            throw new OperationError(\"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\");\n        }\n    }\n\n    /**\n     * Run the union operation\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     * @throws {OperationError}\n     */\n    run(input, args) {\n        [this.sampleDelim, this.itemDelimiter] = args;\n        const sets = input.split(this.sampleDelim);\n\n        this.validateSampleNumbers(sets);\n\n        return this.runUnion(...sets.map(s => s.split(this.itemDelimiter)));\n    }\n\n    /**\n     * Get the union of the two sets.\n     *\n     * @param {Object[]} a\n     * @param {Object[]} b\n     * @returns {Object[]}\n     */\n    runUnion(a, b) {\n        const result = {};\n\n        /**\n         * Only add non-existing items\n         * @param {Object} hash\n         */\n        const addUnique = (hash) => (item) => {\n            if (!hash[item]) {\n                hash[item] = true;\n            }\n        };\n\n        a.map(addUnique(result));\n        b.map(addUnique(result));\n\n        return Object.keys(result).join(this.itemDelimiter);\n    }\n}\n\nexport default SetUnion;\n"
  },
  {
    "path": "src/core/operations/Shake.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport JSSHA3 from \"js-sha3\";\n\n/**\n * Shake operation\n */\nclass Shake extends Operation {\n\n    /**\n     * Shake constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Shake\";\n        this.module = \"Crypto\";\n        this.description = \"Shake is an Extendable Output Function (XOF) of the SHA-3 hash algorithm, part of the Keccak family, allowing for variable output length/size.\";\n        this.infoURL = \"https://wikipedia.org/wiki/SHA-3#Instances\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Capacity\",\n                \"type\": \"option\",\n                \"value\": [\"256\", \"128\"]\n            },\n            {\n                \"name\": \"Size\",\n                \"type\": \"number\",\n                \"value\": 512\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const capacity = parseInt(args[0], 10),\n            size = args[1];\n        let algo;\n\n        if (size < 0)\n            throw new OperationError(\"Size must be greater than 0\");\n\n        switch (capacity) {\n            case 128:\n                algo = JSSHA3.shake128;\n                break;\n            case 256:\n                algo = JSSHA3.shake256;\n                break;\n            default:\n                throw new OperationError(\"Invalid size\");\n        }\n\n        return algo(input, size);\n    }\n\n}\n\nexport default Shake;\n"
  },
  {
    "path": "src/core/operations/SharpenImage.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Sharpen Image operation\n */\nclass SharpenImage extends Operation {\n    /**\n     * SharpenImage constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Sharpen Image\";\n        this.module = \"Image\";\n        this.description = \"Sharpens an image (Unsharp mask)\";\n        this.infoURL = \"https://wikipedia.org/wiki/Unsharp_masking\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Radius\",\n                type: \"number\",\n                value: 2,\n                min: 1,\n            },\n            {\n                name: \"Amount\",\n                type: \"number\",\n                value: 1,\n                min: 0,\n                step: 0.1,\n            },\n            {\n                name: \"Threshold\",\n                type: \"number\",\n                value: 10,\n                min: 0,\n                max: 100,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    async run(input, args) {\n        const [radius, amount, threshold] = args;\n\n        if (!isImage(input)) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        let image;\n        try {\n            image = await Jimp.read(input);\n        } catch (err) {\n            throw new OperationError(`Error loading image. (${err})`);\n        }\n\n        try {\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\"Sharpening image... (Cloning image)\");\n            const blurMask = image.clone();\n\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\n                    \"Sharpening image... (Blurring cloned image)\",\n                );\n            const blurImage = image.clone().gaussian(radius);\n\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\n                    \"Sharpening image... (Creating unsharp mask)\",\n                );\n            blurMask.scan(\n                0,\n                0,\n                blurMask.bitmap.width,\n                blurMask.bitmap.height,\n                function (x, y, idx) {\n                    const blurRed = blurImage.bitmap.data[idx];\n                    const blurGreen = blurImage.bitmap.data[idx + 1];\n                    const blurBlue = blurImage.bitmap.data[idx + 2];\n\n                    const normalRed = this.bitmap.data[idx];\n                    const normalGreen = this.bitmap.data[idx + 1];\n                    const normalBlue = this.bitmap.data[idx + 2];\n\n                    // Subtract blurred pixel value from normal image\n                    this.bitmap.data[idx] =\n                        normalRed > blurRed ? normalRed - blurRed : 0;\n                    this.bitmap.data[idx + 1] =\n                        normalGreen > blurGreen ? normalGreen - blurGreen : 0;\n                    this.bitmap.data[idx + 2] =\n                        normalBlue > blurBlue ? normalBlue - blurBlue : 0;\n                },\n            );\n\n            if (isWorkerEnvironment())\n                self.sendStatusMessage(\n                    \"Sharpening image... (Merging with unsharp mask)\",\n                );\n            image.scan(\n                0,\n                0,\n                image.bitmap.width,\n                image.bitmap.height,\n                function (x, y, idx) {\n                    let maskRed = blurMask.bitmap.data[idx];\n                    let maskGreen = blurMask.bitmap.data[idx + 1];\n                    let maskBlue = blurMask.bitmap.data[idx + 2];\n\n                    const normalRed = this.bitmap.data[idx];\n                    const normalGreen = this.bitmap.data[idx + 1];\n                    const normalBlue = this.bitmap.data[idx + 2];\n\n                    // Calculate luminance\n                    const maskLuminance =\n                        0.2126 * maskRed +\n                        0.7152 * maskGreen +\n                        0.0722 * maskBlue;\n                    const normalLuminance =\n                        0.2126 * normalRed +\n                        0.7152 * normalGreen +\n                        0.0722 * normalBlue;\n\n                    let luminanceDiff;\n                    if (maskLuminance > normalLuminance) {\n                        luminanceDiff = maskLuminance - normalLuminance;\n                    } else {\n                        luminanceDiff = normalLuminance - maskLuminance;\n                    }\n\n                    // Scale mask colours by amount\n                    maskRed = maskRed * amount;\n                    maskGreen = maskGreen * amount;\n                    maskBlue = maskBlue * amount;\n\n                    // Only change pixel value if the difference is higher than threshold\n                    if ((luminanceDiff / 255) * 100 >= threshold) {\n                        this.bitmap.data[idx] =\n                            normalRed + maskRed <= 255 ?\n                                normalRed + maskRed :\n                                255;\n                        this.bitmap.data[idx + 1] =\n                            normalGreen + maskGreen <= 255 ?\n                                normalGreen + maskGreen :\n                                255;\n                        this.bitmap.data[idx + 2] =\n                            normalBlue + maskBlue <= 255 ?\n                                normalBlue + maskBlue :\n                                255;\n                    }\n                },\n            );\n\n            let imageBuffer;\n            if (image.mime === \"image/gif\") {\n                imageBuffer = await image.getBuffer(JimpMime.png);\n            } else {\n                imageBuffer = await image.getBuffer(image.mime);\n            }\n            return imageBuffer.buffer;\n        } catch (err) {\n            throw new OperationError(`Error sharpening image. (${err})`);\n        }\n    }\n\n    /**\n     * Displays the sharpened image using HTML for web apps\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const dataArray = new Uint8Array(data);\n\n        const type = isImage(dataArray);\n        if (!type) {\n            throw new OperationError(\"Invalid file type.\");\n        }\n\n        return `<img src=\"data:${type};base64,${toBase64(dataArray)}\">`;\n    }\n}\n\nexport default SharpenImage;\n"
  },
  {
    "path": "src/core/operations/ShowBase64Offsets.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {fromBase64, toBase64} from \"../lib/Base64.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Show Base64 offsets operation\n */\nclass ShowBase64Offsets extends Operation {\n\n    /**\n     * ShowBase64Offsets constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Show Base64 offsets\";\n        this.module = \"Default\";\n        this.description = \"When a string is within a block of data and the whole block is Base64'd, the string itself could be represented in Base64 in three distinct ways depending on its offset within the block.<br><br>This operation shows all possible offsets for a given string so that each possible encoding can be considered.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Base64#Output_padding\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"binaryString\",\n                value: \"A-Za-z0-9+/=\"\n            },\n            {\n                name: \"Show variable chars and padding\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Input format\",\n                type: \"option\",\n                value: [\"Raw\", \"Base64\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const [alphabet, showVariable, format] = args;\n\n        if (format === \"Base64\") {\n            input = fromBase64(Utils.byteArrayToUtf8(input), null, \"byteArray\");\n        }\n\n        let offset0 = toBase64(input, alphabet),\n            offset1 = toBase64([0].concat(input), alphabet),\n            offset2 = toBase64([0, 0].concat(input), alphabet),\n            staticSection = \"\",\n            padding = \"\";\n\n        const len0 = offset0.indexOf(\"=\"),\n            len1 = offset1.indexOf(\"=\"),\n            len2 = offset2.indexOf(\"=\"),\n            script = \"<script type='application/javascript'>$('[data-toggle=\\\"tooltip\\\"]').tooltip()</script>\";\n\n        if (input.length < 1) {\n            throw new OperationError(\"Please enter a string.\");\n        }\n\n        // Highlight offset 0\n        if (len0 % 4 === 2) {\n            staticSection = offset0.slice(0, -3);\n            offset0 = \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(staticSection, alphabet).slice(0, -2)) + \"'>\" +\n                staticSection + \"</span>\" +\n                \"<span class='hl5'>\" + offset0.substr(offset0.length - 3, 1) + \"</span>\" +\n                \"<span class='hl3'>\" + offset0.substr(offset0.length - 2) + \"</span>\";\n        } else if (len0 % 4 === 3) {\n            staticSection = offset0.slice(0, -2);\n            offset0 = \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(staticSection, alphabet).slice(0, -1)) + \"'>\" +\n                staticSection + \"</span>\" +\n                \"<span class='hl5'>\" + offset0.substr(offset0.length - 2, 1) + \"</span>\" +\n                \"<span class='hl3'>\" + offset0.substr(offset0.length - 1) + \"</span>\";\n        } else {\n            staticSection = offset0;\n            offset0 = \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(staticSection, alphabet)) + \"'>\" +\n                staticSection + \"</span>\";\n        }\n\n        if (!showVariable) {\n            offset0 = staticSection;\n        }\n\n\n        // Highlight offset 1\n        padding = \"<span class='hl3'>\" + offset1.substr(0, 1) + \"</span>\" +\n            \"<span class='hl5'>\" + offset1.substr(1, 1) + \"</span>\";\n        offset1 = offset1.substr(2);\n        if (len1 % 4 === 2) {\n            staticSection = offset1.slice(0, -3);\n            offset1 = padding + \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(\"AA\" + staticSection, alphabet).slice(1, -2)) + \"'>\" +\n                staticSection + \"</span>\" +\n                \"<span class='hl5'>\" + offset1.substr(offset1.length - 3, 1) + \"</span>\" +\n                \"<span class='hl3'>\" + offset1.substr(offset1.length - 2) + \"</span>\";\n        } else if (len1 % 4 === 3) {\n            staticSection = offset1.slice(0, -2);\n            offset1 = padding + \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(\"AA\" + staticSection, alphabet).slice(1, -1)) + \"'>\" +\n                staticSection + \"</span>\" +\n                \"<span class='hl5'>\" + offset1.substr(offset1.length - 2, 1) + \"</span>\" +\n                \"<span class='hl3'>\" + offset1.substr(offset1.length - 1) + \"</span>\";\n        } else {\n            staticSection = offset1;\n            offset1 = padding +  \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(\"AA\" + staticSection, alphabet).slice(1)) + \"'>\" +\n                staticSection + \"</span>\";\n        }\n\n        if (!showVariable) {\n            offset1 = staticSection;\n        }\n\n        // Highlight offset 2\n        padding = \"<span class='hl3'>\" + offset2.substr(0, 2) + \"</span>\" +\n            \"<span class='hl5'>\" + offset2.substr(2, 1) + \"</span>\";\n        offset2 = offset2.substr(3);\n        if (len2 % 4 === 2) {\n            staticSection = offset2.slice(0, -3);\n            offset2 = padding + \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(\"AAA\" + staticSection, alphabet).slice(2, -2)) + \"'>\" +\n                staticSection + \"</span>\" +\n                \"<span class='hl5'>\" + offset2.substr(offset2.length - 3, 1) + \"</span>\" +\n                \"<span class='hl3'>\" + offset2.substr(offset2.length - 2) + \"</span>\";\n        } else if (len2 % 4 === 3) {\n            staticSection = offset2.slice(0, -2);\n            offset2 = padding + \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(\"AAA\" + staticSection, alphabet).slice(2, -2)) + \"'>\" +\n                staticSection + \"</span>\" +\n                \"<span class='hl5'>\" + offset2.substr(offset2.length - 2, 1) + \"</span>\" +\n                \"<span class='hl3'>\" + offset2.substr(offset2.length - 1) + \"</span>\";\n        } else {\n            staticSection = offset2;\n            offset2 = padding +  \"<span data-toggle='tooltip' data-placement='top' title='\" +\n                Utils.escapeHtml(fromBase64(\"AAA\" + staticSection, alphabet).slice(2)) + \"'>\" +\n                staticSection + \"</span>\";\n        }\n\n        if (!showVariable) {\n            offset2 = staticSection;\n        }\n\n        return (showVariable ? \"Characters highlighted in <span class='hl5'>green</span> could change if the input is surrounded by more data.\" +\n            \"\\nCharacters highlighted in <span class='hl3'>red</span> are for padding purposes only.\" +\n            \"\\nUnhighlighted characters are <span data-toggle='tooltip' data-placement='top' title='Tooltip on left'>static</span>.\" +\n            \"\\nHover over the static sections to see what they decode to on their own.\\n\" +\n            \"\\nOffset 0: \" + offset0 +\n            \"\\nOffset 1: \" + offset1 +\n            \"\\nOffset 2: \" + offset2 +\n            script :\n            offset0 + \"\\n\" + offset1 + \"\\n\" + offset2);\n    }\n\n}\n\nexport default ShowBase64Offsets;\n"
  },
  {
    "path": "src/core/operations/ShowOnMap.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @author 0xff1ce [github.com/0xff1ce]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {FORMATS, convertCoordinates} from \"../lib/ConvertCoordinates.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Show on map operation\n */\nclass ShowOnMap extends Operation {\n\n    /**\n     * ShowOnMap constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Show on map\";\n        this.module = \"Hashing\";\n        this.description = \"Displays co-ordinates on a slippy map.<br><br>Co-ordinates will be converted to decimal degrees before being shown on the map.<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>This operation will not work offline.\";\n        this.infoURL = \"https://osmfoundation.org/wiki/Terms_of_Use\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Zoom Level\",\n                type: \"number\",\n                value: 13\n            },\n            {\n                name: \"Input Format\",\n                type: \"option\",\n                value: [\"Auto\"].concat(FORMATS)\n            },\n            {\n                name: \"Input Delimiter\",\n                type: \"option\",\n                value: [\n                    \"Auto\",\n                    \"Direction Preceding\",\n                    \"Direction Following\",\n                    \"\\\\n\",\n                    \"Comma\",\n                    \"Semi-colon\",\n                    \"Colon\"\n                ]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (input.replace(/\\s+/g, \"\") !== \"\") {\n            const inFormat = args[1],\n                inDelim = args[2];\n            let latLong;\n            try {\n                latLong = convertCoordinates(input, inFormat, inDelim, \"Decimal Degrees\", \"Comma\", \"None\", 5);\n            } catch (error) {\n                throw new OperationError(error);\n            }\n            latLong = latLong.replace(/[,]$/, \"\");\n            latLong = latLong.replace(/°/g, \"\");\n            return latLong;\n        }\n        return input;\n    }\n\n    /**\n     * @param {string} data\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async present(data, args) {\n        if (data.replace(/\\s+/g, \"\") === \"\") {\n            data = \"0, 0\";\n        }\n        const zoomLevel = args[0];\n        const tileUrl = \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\n            tileAttribution = \"&copy; <a href=\\\"https://www.openstreetmap.org/copyright\\\">OpenStreetMap</a> contributors\",\n            leafletUrl = \"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js\",\n            leafletCssUrl = \"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\";\n        return `<link rel=\"stylesheet\" href=\"${leafletCssUrl}\" crossorigin=\"\"/>\n<style>\n    #output-text .cm-content,\n    #output-text .cm-line,\n    #output-html {\n        padding: 0;\n        white-space: normal;\n    }\n</style>\n<div id=\"presentedMap\" style=\"width: 100%; height: 100%;\"></div>\n<script type=\"text/javascript\">\nvar mapscript = document.createElement('script');\ndocument.body.appendChild(mapscript);\nmapscript.onload = function() {\n    var presentMap = L.map('presentedMap').setView([${data}], ${zoomLevel});\n    L.tileLayer('${tileUrl}', {\n        attribution: '${tileAttribution}'\n    }).addTo(presentMap);\n\n    L.marker([${data}]).addTo(presentMap)\n        .bindPopup('${data}')\n        .openPopup();\n};\nmapscript.src = \"${leafletUrl}\";\n</script>`;\n    }\n}\n\nexport default ShowOnMap;\n"
  },
  {
    "path": "src/core/operations/Shuffle.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {INPUT_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * Shuffle operation\n */\nclass Shuffle extends Operation {\n\n    /**\n     * Shuffle constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Shuffle\";\n        this.module = \"Default\";\n        this.description = \"Randomly reorders input elements.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Shuffling\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: INPUT_DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0]);\n        if (input.length === 0) return input;\n\n        // return a random number in [0, 1)\n        const rng = (typeof crypto) !== \"undefined\" && crypto.getRandomValues ? (function() {\n            const buf = new Uint32Array(2);\n            return function() {\n                // generate 53-bit random integer: 21 + 32 bits\n                crypto.getRandomValues(buf);\n                const value = (buf[0] >>> (32 - 21)) * ((1 << 30) * 4) + buf[1];\n                return value / ((1 << 23) * (1 << 30));\n            };\n        })() : Math.random;\n\n        // return a random integer in [0, max)\n        const randint = function(max) {\n            return Math.floor(rng() * max);\n        };\n\n        // Split input into shuffleable sections\n        const toShuffle = input.split(delim);\n\n        // shuffle elements\n        for (let i = toShuffle.length - 1; i > 0; i--) {\n            const idx = randint(i + 1);\n            const tmp = toShuffle[idx];\n            toShuffle[idx] = toShuffle[i];\n            toShuffle[i] = tmp;\n        }\n\n        return toShuffle.join(delim);\n    }\n\n}\n\nexport default Shuffle;\n"
  },
  {
    "path": "src/core/operations/Sleep.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Sleep operation\n */\nclass Sleep extends Operation {\n\n    /**\n     * Sleep constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Sleep\";\n        this.module = \"Default\";\n        this.description = \"Sleep causes the recipe to wait for a specified number of milliseconds before continuing execution.\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Time (ms)\",\n                \"type\": \"number\",\n                \"value\": 1000\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    async run(input, args) {\n        const ms = args[0];\n        await new Promise(r => setTimeout(r, ms));\n        return input;\n    }\n\n}\n\nexport default Sleep;\n"
  },
  {
    "path": "src/core/operations/Snefru.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * Snefru operation\n */\nclass Snefru extends Operation {\n\n    /**\n     * Snefru constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Snefru\";\n        this.module = \"Crypto\";\n        this.description = \"Snefru is a cryptographic hash function invented by Ralph Merkle in 1990 while working at Xerox PARC. The function supports 128-bit and 256-bit output. It was named after the Egyptian Pharaoh Sneferu, continuing the tradition of the Khufu and Khafre block ciphers.<br><br>The original design of Snefru was shown to be insecure by Eli Biham and Adi Shamir who were able to use differential cryptanalysis to find hash collisions. The design was then modified by increasing the number of iterations of the main pass of the algorithm from two to eight.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Snefru\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Size\",\n                type: \"number\",\n                value: 128,\n                min: 32,\n                max: 480,\n                step: 32\n            },\n            {\n                name: \"Rounds\",\n                type: \"option\",\n                value: [\"8\", \"4\", \"2\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return runHash(\"snefru\", input, {\n            length: args[0],\n            rounds: args[1]\n        });\n    }\n\n}\n\nexport default Snefru;\n"
  },
  {
    "path": "src/core/operations/Sort.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {INPUT_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort, lengthSort} from \"../lib/Sort.mjs\";\n\n/**\n * Sort operation\n */\nclass Sort extends Operation {\n\n    /**\n     * Sort constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Sort\";\n        this.module = \"Default\";\n        this.description = \"Alphabetically sorts strings separated by the specified delimiter.<br><br>The IP address option supports IPv4 only.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": INPUT_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Reverse\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Order\",\n                \"type\": \"option\",\n                \"value\": [\"Alphabetical (case sensitive)\", \"Alphabetical (case insensitive)\", \"IP address\", \"Numeric\", \"Numeric (hexadecimal)\", \"Length\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0]),\n            sortReverse = args[1],\n            order = args[2];\n        let sorted = input.split(delim);\n\n        if (order === \"Alphabetical (case sensitive)\") {\n            sorted = sorted.sort();\n        } else if (order === \"Alphabetical (case insensitive)\") {\n            sorted = sorted.sort(caseInsensitiveSort);\n        } else if (order === \"IP address\") {\n            sorted = sorted.sort(ipSort);\n        } else if (order === \"Numeric\") {\n            sorted = sorted.sort(numericSort);\n        } else if (order === \"Numeric (hexadecimal)\") {\n            sorted = sorted.sort(hexadecimalSort);\n        } else if (order === \"Length\") {\n            sorted = sorted.sort(lengthSort);\n        }\n\n        if (sortReverse) sorted.reverse();\n        return sorted.join(delim);\n    }\n\n}\n\nexport default Sort;\n"
  },
  {
    "path": "src/core/operations/Split.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * Split operation\n */\nclass Split extends Operation {\n\n    /**\n     * Split constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Split\";\n        this.module = \"Default\";\n        this.description = \"Splits a string into sections around a given delimiter.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Split delimiter\",\n                \"type\": \"editableOptionShort\",\n                \"value\": SPLIT_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Join delimiter\",\n                \"type\": \"editableOptionShort\",\n                \"value\": JOIN_DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const splitDelim = args[0],\n            joinDelim = args[1],\n            sections = input.split(splitDelim);\n\n        return sections.join(joinDelim);\n    }\n\n}\n\nexport default Split;\n"
  },
  {
    "path": "src/core/operations/SplitColourChannels.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Split Colour Channels operation\n */\nclass SplitColourChannels extends Operation {\n    /**\n     * SplitColourChannels constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Split Colour Channels\";\n        this.module = \"Image\";\n        this.description =\n            \"Splits the given image into its red, green and blue colour channels.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Channel_(digital_image)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"List<File>\";\n        this.presentType = \"html\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {List<File>}\n     */\n    async run(input, args) {\n        input = new Uint8Array(input);\n        // Make sure that the input is an image\n        if (!isImage(input)) throw new OperationError(\"Invalid file type.\");\n\n        const parsedImage = await Jimp.read(Buffer.from(input));\n\n        const red = new Promise(async (resolve, reject) => {\n            try {\n                const split = parsedImage\n                    .clone()\n                    .color([\n                        { apply: \"blue\", params: [-255] },\n                        { apply: \"green\", params: [-255] },\n                    ])\n                    .getBuffer(JimpMime.png);\n                resolve(\n                    new File(\n                        [new Uint8Array((await split).values())],\n                        \"red.png\",\n                        { type: \"image/png\" },\n                    ),\n                );\n            } catch (err) {\n                reject(\n                    new OperationError(`Could not split red channel: ${err}`),\n                );\n            }\n        });\n\n        const green = new Promise(async (resolve, reject) => {\n            try {\n                const split = parsedImage\n                    .clone()\n                    .color([\n                        { apply: \"red\", params: [-255] },\n                        { apply: \"blue\", params: [-255] },\n                    ])\n                    .getBuffer(JimpMime.png);\n                resolve(\n                    new File(\n                        [new Uint8Array((await split).values())],\n                        \"green.png\",\n                        { type: \"image/png\" },\n                    ),\n                );\n            } catch (err) {\n                reject(\n                    new OperationError(`Could not split green channel: ${err}`),\n                );\n            }\n        });\n\n        const blue = new Promise(async (resolve, reject) => {\n            try {\n                const split = parsedImage\n                    .color([\n                        { apply: \"red\", params: [-255] },\n                        { apply: \"green\", params: [-255] },\n                    ])\n                    .getBuffer(JimpMime.png);\n                resolve(\n                    new File(\n                        [new Uint8Array((await split).values())],\n                        \"blue.png\",\n                        { type: \"image/png\" },\n                    ),\n                );\n            } catch (err) {\n                reject(\n                    new OperationError(`Could not split blue channel: ${err}`),\n                );\n            }\n        });\n\n        return await Promise.all([red, green, blue]);\n    }\n\n    /**\n     * Displays the files in HTML for web apps.\n     *\n     * @param {File[]} files\n     * @returns {html}\n     */\n    async present(files) {\n        return await Utils.displayFilesAsHTML(files);\n    }\n}\n\nexport default SplitColourChannels;\n"
  },
  {
    "path": "src/core/operations/StandardDeviation.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport BigNumber from \"bignumber.js\";\nimport Operation from \"../Operation.mjs\";\nimport { stdDev, createNumArray } from \"../lib/Arithmetic.mjs\";\nimport { ARITHMETIC_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n\n/**\n * Standard Deviation operation\n */\nclass StandardDeviation extends Operation {\n\n    /**\n     * StandardDeviation constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Standard Deviation\";\n        this.module = \"Default\";\n        this.description = \"Computes the standard deviation of a number list. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>4.089281382128433</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Standard_deviation\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": ARITHMETIC_DELIM_OPTIONS,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const val = stdDev(createNumArray(input, args[0]));\n        return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);\n\n    }\n\n}\n\nexport default StandardDeviation;\n"
  },
  {
    "path": "src/core/operations/Streebog.mjs",
    "content": "/**\n * @author mshwed [m@ttshwed.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport GostDigest from \"../vendor/gost/gostDigest.mjs\";\nimport {toHexFast} from \"../lib/Hex.mjs\";\n\n/**\n * Streebog operation\n */\nclass Streebog extends Operation {\n\n    /**\n     * Streebog constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Streebog\";\n        this.module = \"Hashing\";\n        this.description = \"Streebog is a cryptographic hash function defined in the Russian national standard GOST R 34.11-2012 <i>Information Technology \\u2013 Cryptographic Information Security \\u2013 Hash Function</i>. It was created to replace an obsolete GOST hash function defined in the old standard GOST R 34.11-94, and as an asymmetric reply to SHA-3 competition by the US National Institute of Standards and Technology.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Streebog\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Digest length\",\n                \"type\": \"option\",\n                \"value\": [\"256\", \"512\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [length] = args;\n\n        const algorithm = {\n            version: 2012,\n            mode: \"HASH\",\n            length: parseInt(length, 10)\n        };\n\n        try {\n            const gostDigest = new GostDigest(algorithm);\n\n            return toHexFast(gostDigest.digest(input));\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default Streebog;\n"
  },
  {
    "path": "src/core/operations/Strings.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport XRegExp from \"xregexp\";\nimport { search } from \"../lib/Extract.mjs\";\nimport { caseInsensitiveSort } from \"../lib/Sort.mjs\";\n\n/**\n * Strings operation\n */\nclass Strings extends Operation {\n\n    /**\n     * Strings constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Strings\";\n        this.module = \"Regex\";\n        this.description = \"Extracts all strings from the input.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Strings_(Unix)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Encoding\",\n                type: \"option\",\n                value: [\"Single byte\", \"16-bit littleendian\", \"16-bit bigendian\", \"All\"]\n            },\n            {\n                name: \"Minimum length\",\n                type: \"number\",\n                value: 4\n            },\n            {\n                name: \"Match\",\n                type: \"option\",\n                value: [\n                    \"[ASCII]\", \"Alphanumeric + punctuation (A)\", \"All printable chars (A)\", \"Null-terminated strings (A)\",\n                    \"[Unicode]\", \"Alphanumeric + punctuation (U)\", \"All printable chars (U)\", \"Null-terminated strings (U)\"\n                ]\n            },\n            {\n                name: \"Display total\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Sort\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Unique\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [encoding, minLen, matchType, displayTotal, sort, unique] = args,\n            alphanumeric = \"A-Z\\\\d\",\n            punctuation = \"/\\\\-:.,_$%'\\\"()<>= !\\\\[\\\\]{}@\",\n            printable = \"\\x20-\\x7e\",\n            uniAlphanumeric = \"\\\\pL\\\\pN\",\n            uniPunctuation = \"\\\\pP\\\\pZ\",\n            uniPrintable = \"\\\\pL\\\\pM\\\\pZ\\\\pS\\\\pN\\\\pP\";\n\n        let strings = \"\";\n\n        switch (matchType) {\n            case \"Alphanumeric + punctuation (A)\":\n                strings = `[${alphanumeric + punctuation}]`;\n                break;\n            case \"All printable chars (A)\":\n            case \"Null-terminated strings (A)\":\n                strings = `[${printable}]`;\n                break;\n            case \"Alphanumeric + punctuation (U)\":\n                strings = `[${uniAlphanumeric + uniPunctuation}]`;\n                break;\n            case \"All printable chars (U)\":\n            case \"Null-terminated strings (U)\":\n                strings = `[${uniPrintable}]`;\n                break;\n        }\n\n        // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars\n        switch (encoding) {\n            case \"All\":\n                strings = `(\\x00?${strings}\\x00?)`;\n                break;\n            case \"16-bit littleendian\":\n                strings = `(${strings}\\x00)`;\n                break;\n            case \"16-bit bigendian\":\n                strings = `(\\x00${strings})`;\n                break;\n            case \"Single byte\":\n            default:\n                break;\n        }\n\n        strings = `${strings}{${minLen},}`;\n\n        if (matchType.includes(\"Null-terminated\")) {\n            strings += \"\\x00\";\n        }\n\n        const regex = new XRegExp(strings, \"ig\");\n        const results = search(\n            input,\n            regex,\n            null,\n            sort ? caseInsensitiveSort : null,\n            unique\n        );\n\n        if (displayTotal) {\n            return `Total found: ${results.length}\\n\\n${results.join(\"\\n\")}`;\n        } else {\n            return results.join(\"\\n\");\n        }\n    }\n\n}\n\nexport default Strings;\n"
  },
  {
    "path": "src/core/operations/StripHTMLTags.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Strip HTML tags operation\n */\nclass StripHTMLTags extends Operation {\n\n    /**\n     * StripHTMLTags constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Strip HTML tags\";\n        this.module = \"Default\";\n        this.description = \"Removes all HTML tags from the input.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Remove indentation\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Remove excess line breaks\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n        this.checks = [\n            {\n                pattern:  \"(</html>|</div>|</body>)\",\n                flags:  \"i\",\n                args:   [true, true]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [removeIndentation, removeLineBreaks] = args;\n\n        input = Utils.stripHtmlTags(input);\n\n        if (removeIndentation) {\n            input = input.replace(/\\n[ \\f\\t]+/g, \"\\n\");\n        }\n\n        if (removeLineBreaks) {\n            input = input\n                .replace(/^\\s*\\n/, \"\") // first line\n                .replace(/(\\n\\s*){2,}/g, \"\\n\"); // all others\n        }\n\n        return input;\n    }\n\n}\n\nexport default StripHTMLTags;\n"
  },
  {
    "path": "src/core/operations/StripHTTPHeaders.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Strip HTTP headers operation\n */\nclass StripHTTPHeaders extends Operation {\n\n    /**\n     * StripHTTPHeaders constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Strip HTTP headers\";\n        this.module = \"Default\";\n        this.description = \"Removes HTTP headers from a request or response by looking for the first instance of a double newline.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Hypertext_Transfer_Protocol#Message_format\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n        this.checks = [\n            {\n                pattern:  \"^HTTP(.|\\\\s)+?(\\\\r?\\\\n){2}\",\n                flags:  \"\",\n                args:   []\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let headerEnd = input.indexOf(\"\\r\\n\\r\\n\");\n        headerEnd = (headerEnd < 0) ? input.indexOf(\"\\n\\n\") + 2 : headerEnd + 4;\n\n        return (headerEnd < 2) ? input : input.slice(headerEnd, input.length);\n    }\n\n}\n\nexport default StripHTTPHeaders;\n"
  },
  {
    "path": "src/core/operations/StripIPv4Header.mjs",
    "content": "/**\n * @author c65722 []\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\n\n/**\n * Strip IPv4 header operation\n */\nclass StripIPv4Header extends Operation {\n\n    /**\n     * StripIPv4Header constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Strip IPv4 header\";\n        this.module = \"Default\";\n        this.description = \"Strips the IPv4 header from an IPv4 packet, outputting the payload.\";\n        this.infoURL = \"https://wikipedia.org/wiki/IPv4\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const MIN_HEADER_LEN = 20;\n\n        const s = new Stream(new Uint8Array(input));\n        if (s.length < MIN_HEADER_LEN) {\n            throw new OperationError(\"Input length is less than minimum IPv4 header length\");\n        }\n\n        const ihl = s.readInt(1) & 0x0f;\n        const dataOffsetBytes = ihl * 4;\n        if (s.length < dataOffsetBytes) {\n            throw new OperationError(\"Input length is less than IHL\");\n        }\n\n        s.moveTo(dataOffsetBytes);\n\n        return s.getBytes().buffer;\n    }\n\n}\n\nexport default StripIPv4Header;\n"
  },
  {
    "path": "src/core/operations/StripTCPHeader.mjs",
    "content": "/**\n * @author c65722 []\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\n\n/**\n * Strip TCP header operation\n */\nclass StripTCPHeader extends Operation {\n\n    /**\n     * StripTCPHeader constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Strip TCP header\";\n        this.module = \"Default\";\n        this.description = \"Strips the TCP header from a TCP segment, outputting the payload.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Transmission_Control_Protocol\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const MIN_HEADER_LEN = 20;\n        const DATA_OFFSET_OFFSET = 12;\n        const DATA_OFFSET_LEN_BITS = 4;\n\n        const s = new Stream(new Uint8Array(input));\n        if (s.length < MIN_HEADER_LEN) {\n            throw new OperationError(\"Need at least 20 bytes for a TCP Header\");\n        }\n\n        s.moveTo(DATA_OFFSET_OFFSET);\n        const dataOffsetWords = s.readBits(DATA_OFFSET_LEN_BITS);\n        const dataOffsetBytes = dataOffsetWords * 4;\n        if (s.length < dataOffsetBytes) {\n            throw new OperationError(\"Input length is less than data offset\");\n        }\n\n        s.moveTo(dataOffsetBytes);\n\n        return s.getBytes().buffer;\n    }\n\n}\n\nexport default StripTCPHeader;\n"
  },
  {
    "path": "src/core/operations/StripUDPHeader.mjs",
    "content": "/**\n * @author c65722 []\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Strip UDP header operation\n */\nclass StripUDPHeader extends Operation {\n\n    /**\n     * StripUDPHeader constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Strip UDP header\";\n        this.module = \"Default\";\n        this.description = \"Strips the UDP header from a UDP datagram, outputting the payload.\";\n        this.infoURL = \"https://wikipedia.org/wiki/User_Datagram_Protocol\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const HEADER_LEN = 8;\n\n        const s = new Stream(new Uint8Array(input));\n        if (s.length < HEADER_LEN) {\n            throw new OperationError(\"Need 8 bytes for a UDP Header\");\n        }\n\n        s.moveTo(HEADER_LEN);\n\n        return s.getBytes().buffer;\n    }\n\n}\n\nexport default StripUDPHeader;\n"
  },
  {
    "path": "src/core/operations/Subsection.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Recipe from \"../Recipe.mjs\";\nimport Dish from \"../Dish.mjs\";\n\n/**\n * Subsection operation\n */\nclass Subsection extends Operation {\n\n    /**\n     * Subsection constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Subsection\";\n        this.flowControl = true;\n        this.module = \"Default\";\n        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.<br><br>Use the Merge operation to reset the effects of subsection.\";\n        this.infoURL = \"\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Section (regex)\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Case sensitive matching\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Global matching\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Ignore errors\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {Object} state - The current state of the recipe.\n     * @param {number} state.progress - The current position in the recipe.\n     * @param {Dish} state.dish - The Dish being operated on\n     * @param {Operation[]} state.opList - The list of operations in the recipe\n     * @returns {Object} - The updated state of the recipe\n     */\n    async run(state) {\n        const opList    = state.opList,\n            inputType   = opList[state.progress].inputType,\n            outputType  = opList[state.progress].outputType,\n            input       = await state.dish.get(inputType),\n            ings        = opList[state.progress].ingValues,\n            [section, caseSensitive, global, ignoreErrors] = ings,\n            subOpList   = [];\n\n        if (input && section !== \"\") {\n            // Set to 1 as if we are here, then there is one, the current one.\n            let numOp = 1;\n            // Create subOpList for each tranche to operate on\n            // all remaining operations unless we encounter a Merge\n            for (let i = state.progress + 1; i < opList.length; i++) {\n                if (opList[i].name === \"Merge\" && !opList[i].disabled) {\n                    numOp--;\n                    if (numOp === 0 || opList[i].ingValues[0])\n                        break;\n                    else\n                        // Not this subsection's Merge.\n                        subOpList.push(opList[i]);\n                } else {\n                    if (opList[i].name === \"Fork\" || opList[i].name === \"Subsection\")\n                        numOp++;\n                    subOpList.push(opList[i]);\n                }\n            }\n\n            let flags = \"\",\n                inOffset = 0,\n                output = \"\",\n                m,\n                progress = 0;\n\n            if (!caseSensitive) flags += \"i\";\n            if (global) flags += \"g\";\n\n            const regex = new RegExp(section, flags),\n                recipe = new Recipe();\n\n            recipe.addOperations(subOpList);\n            state.forkOffset += state.progress + 1;\n\n            // Take a deep(ish) copy of the ingredient values\n            const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));\n            let matched = false;\n\n            // Run recipe over each match\n            while ((m = regex.exec(input))) {\n                matched = true;\n                // Add up to match\n                let matchStr = m[0];\n\n                if (m.length === 1) { // No capture groups\n                    output += input.slice(inOffset, m.index);\n                    inOffset = m.index + m[0].length;\n                } else if (m.length >= 2) {\n                    matchStr = m[1];\n\n                    // Need to add some of the matched string that isn't in the capture group\n                    output += input.slice(inOffset, m.index + m[0].indexOf(m[1]));\n                    // Set i to be after the end of the first capture group\n                    inOffset = m.index + m[0].indexOf(m[1]) + m[1].length;\n                }\n\n                // Baseline ing values for each tranche so that registers are reset\n                recipe.opList.forEach((op, i) => {\n                    op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));\n                });\n\n                const dish = new Dish();\n                dish.set(matchStr, inputType);\n\n                try {\n                    progress = await recipe.execute(dish, 0, state);\n                } catch (err) {\n                    if (!ignoreErrors) {\n                        throw err;\n                    }\n                    progress = err.progress + 1;\n                }\n                output += await dish.get(outputType);\n                if (!regex.global) break;\n            }\n\n            // If no matches were found, advance progress to after a Merge op\n            // Otherwise, the operations below Subsection will be run on all the input data\n            if (!matched) {\n                state.progress += subOpList.length + 1;\n            }\n\n            output += input.slice(inOffset);\n            state.progress += progress;\n            state.dish.set(output, outputType);\n        }\n        return state;\n    }\n\n}\n\nexport default Subsection;\n"
  },
  {
    "path": "src/core/operations/Substitute.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Substitute operation\n */\nclass Substitute extends Operation {\n\n    /**\n     * Substitute constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Substitute\";\n        this.module = \"Default\";\n        this.description = \"A substitution cipher allowing you to specify bytes to replace with other byte values. This can be used to create Caesar ciphers but is more powerful as any byte value can be substituted, not just letters, and the substitution values need not be in order.<br><br>Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.<br><br>Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either <code>\\\\n</code> or <code>\\\\x0a</code>.<br><br>Byte ranges can be specified using a hyphen. For example, the sequence <code>0123456789</code> can be written as <code>0-9</code>.<br><br>Note that blackslash characters are used to escape special characters, so will need to be escaped themselves if you want to use them on their own (e.g.<code>\\\\\\\\</code>).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Substitution_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Plaintext\",\n                \"type\": \"binaryString\",\n                \"value\": \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n            },\n            {\n                \"name\": \"Ciphertext\",\n                \"type\": \"binaryString\",\n                \"value\": \"XYZABCDEFGHIJKLMNOPQRSTUVW\"\n            },\n            {\n                \"name\": \"Ignore case\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * Convert a single character using the dictionary, if ignoreCase is true then\n     * check in the dictionary for both upper and lower case versions of the character.\n     * In output the input character case is preserved.\n     * @param {string} char\n     * @param {Object} dict\n     * @param {boolean} ignoreCase\n     * @returns {string}\n     */\n    cipherSingleChar(char, dict, ignoreCase) {\n        if (!ignoreCase)\n            return dict[char] || char;\n\n        const isUpperCase = char === char.toUpperCase();\n\n        // convert using the dictionary keeping the case of the input character\n\n        if (dict[char] !== undefined) {\n            // if the character is in the dictionary return the value with the input case\n            return isUpperCase ? dict[char].toUpperCase() : dict[char].toLowerCase();\n        }\n\n        // check for the other case, if it is in the dictionary return the value with the right case\n        if (isUpperCase) {\n            if (dict[char.toLowerCase()] !== undefined)\n                return dict[char.toLowerCase()].toUpperCase();\n        } else {\n            if (dict[char.toUpperCase()] !== undefined)\n                return dict[char.toUpperCase()].toLowerCase();\n        }\n\n        return char;\n    }\n\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const plaintext = Utils.expandAlphRange([...args[0]]),\n            ciphertext = Utils.expandAlphRange([...args[1]]),\n            ignoreCase = args[2];\n        let output = \"\";\n\n        if (plaintext.length !== ciphertext.length) {\n            output = \"Warning: Plaintext and Ciphertext lengths differ\\n\\n\";\n        }\n\n        // create dictionary for conversion\n        const dict = {};\n        for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) {\n            dict[plaintext[i]] = ciphertext[i];\n        }\n\n        // map every letter with the conversion function\n        for (const character of input) {\n            output += this.cipherSingleChar(character, dict, ignoreCase);\n        }\n\n        return output;\n    }\n\n}\n\nexport default Substitute;\n"
  },
  {
    "path": "src/core/operations/Subtract.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport BigNumber from \"bignumber.js\";\nimport Operation from \"../Operation.mjs\";\nimport { sub, createNumArray } from \"../lib/Arithmetic.mjs\";\nimport { ARITHMETIC_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n\n/**\n * Subtract operation\n */\nclass Subtract extends Operation {\n\n    /**\n     * Subtract constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Subtract\";\n        this.module = \"Default\";\n        this.description = \"Subtracts a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>1.5</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Subtraction\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": ARITHMETIC_DELIM_OPTIONS,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const val = sub(createNumArray(input, args[0]));\n        return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);\n    }\n\n}\n\nexport default Subtract;\n"
  },
  {
    "path": "src/core/operations/Sum.mjs",
    "content": "/**\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport BigNumber from \"bignumber.js\";\nimport Operation from \"../Operation.mjs\";\nimport { sum, createNumArray } from \"../lib/Arithmetic.mjs\";\nimport { ARITHMETIC_DELIM_OPTIONS } from \"../lib/Delim.mjs\";\n\n\n/**\n * Sum operation\n */\nclass Sum extends Operation {\n\n    /**\n     * Sum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Sum\";\n        this.module = \"Default\";\n        this.description = \"Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>18.5</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Summation\";\n        this.inputType = \"string\";\n        this.outputType = \"BigNumber\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": ARITHMETIC_DELIM_OPTIONS,\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {BigNumber}\n     */\n    run(input, args) {\n        const val = sum(createNumArray(input, args[0]));\n        return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);\n    }\n\n}\n\nexport default Sum;\n"
  },
  {
    "path": "src/core/operations/SwapCase.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Swap case operation\n */\nclass SwapCase extends Operation {\n\n    /**\n     * SwapCase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Swap case\";\n        this.module = \"Default\";\n        this.description = \"Converts uppercase letters to lowercase ones, and lowercase ones to uppercase ones.\";\n        this.infoURL = \"\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let result = \"\";\n        for (let i = 0; i < input.length; i++) {\n            const c = input.charAt(i);\n            const upper = c.toUpperCase();\n            if (c === upper) {\n                result += c.toLowerCase();\n            } else {\n                result += upper;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Highlight Swap case\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Swap case in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default SwapCase;\n"
  },
  {
    "path": "src/core/operations/SwapEndianness.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {toHex, fromHex} from \"../lib/Hex.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Swap endianness operation\n */\nclass SwapEndianness extends Operation {\n\n    /**\n     * SwapEndianness constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Swap endianness\";\n        this.module = \"Default\";\n        this.description = \"Switches the data from big-endian to little-endian or vice-versa. Data can be read in as hexadecimal or raw bytes. It will be returned in the same format as it is entered.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Endianness\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Data format\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Word length (bytes)\",\n                \"type\": \"number\",\n                \"value\": 4\n            },\n            {\n                \"name\": \"Pad incomplete words\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [dataFormat, wordLength, padIncompleteWords] = args,\n            result = [],\n            words = [];\n        let i = 0,\n            j = 0,\n            data = [];\n\n        if (wordLength <= 0) {\n            throw new OperationError(\"Word length must be greater than 0\");\n        }\n\n        // Convert input to raw data based on specified data format\n        switch (dataFormat) {\n            case \"Hex\":\n                data = fromHex(input);\n                break;\n            case \"Raw\":\n                data = Utils.strToByteArray(input);\n                break;\n            default:\n                data = input;\n        }\n\n        // Split up into words\n        for (i = 0; i < data.length; i += wordLength) {\n            const word = data.slice(i, i + wordLength);\n\n            // Pad word if too short\n            if (padIncompleteWords && word.length < wordLength) {\n                for (j = word.length; j < wordLength; j++) {\n                    word.push(0);\n                }\n            }\n\n            words.push(word);\n        }\n\n        // Swap endianness and flatten\n        for (i = 0; i < words.length; i++) {\n            j = words[i].length;\n            while (j--) {\n                result.push(words[i][j]);\n            }\n        }\n\n        // Convert data back to specified data format\n        switch (dataFormat) {\n            case \"Hex\":\n                return toHex(result);\n            case \"Raw\":\n                return Utils.byteArrayToUtf8(result);\n            default:\n                return result;\n        }\n    }\n\n    /**\n     * Highlight Swap endianness\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Swap endianness in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default SwapEndianness;\n"
  },
  {
    "path": "src/core/operations/SymmetricDifference.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Utils from \"../Utils.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Set Symmetric Difference operation\n */\nclass SymmetricDifference extends Operation {\n\n    /**\n     * Symmetric Difference constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Symmetric Difference\";\n        this.module = \"Default\";\n        this.description = \"Calculates the symmetric difference of two sets.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Symmetric_difference\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Sample delimiter\",\n                type: \"binaryString\",\n                value: Utils.escapeHtml(\"\\\\n\\\\n\")\n            },\n            {\n                name: \"Item delimiter\",\n                type: \"binaryString\",\n                value: \",\"\n            },\n        ];\n    }\n\n    /**\n     * Validate input length\n     *\n     * @param {Object[]} sets\n     * @throws {Error} if not two sets\n     */\n    validateSampleNumbers(sets) {\n        if (!sets || (sets.length !== 2)) {\n            throw new OperationError(\"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\");\n        }\n    }\n\n    /**\n     * Run the difference operation\n     *\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     * @throws {OperationError}\n     */\n    run(input, args) {\n        [this.sampleDelim, this.itemDelimiter] = args;\n        const sets = input.split(this.sampleDelim);\n\n        this.validateSampleNumbers(sets);\n\n        return this.runSymmetricDifference(...sets.map(s => s.split(this.itemDelimiter)));\n    }\n\n    /**\n     * Get elements in set a that are not in set b\n     *\n     * @param {Object[]} a\n     * @param {Object[]} b\n     * @returns {Object[]}\n     */\n    runSetDifference(a, b) {\n        return a.filter((item) => {\n            return b.indexOf(item) === -1;\n        });\n    }\n\n    /**\n     * Get elements of each set that aren't in the other set.\n     *\n     * @param {Object[]} a\n     * @param {Object[]} b\n     * @return {Object[]}\n     */\n    runSymmetricDifference(a, b) {\n        return this.runSetDifference(a, b)\n            .concat(this.runSetDifference(b, a))\n            .join(this.itemDelimiter);\n    }\n\n}\n\nexport default SymmetricDifference;\n"
  },
  {
    "path": "src/core/operations/SyntaxHighlighter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport hljs from \"highlight.js\";\n\n/**\n * Syntax highlighter operation\n */\nclass SyntaxHighlighter extends Operation {\n\n    /**\n     * SyntaxHighlighter constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Syntax highlighter\";\n        this.module = \"Code\";\n        this.description = \"Adds syntax highlighting to a range of source code languages. Note that this will not indent the code. Use one of the 'Beautify' operations for that.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Syntax_highlighting\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Language\",\n                \"type\": \"option\",\n                \"value\": [\"auto detect\"].concat(hljs.listLanguages())\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const language = args[0];\n\n        if (language === \"auto detect\") {\n            return hljs.highlightAuto(input).value;\n        }\n\n        return hljs.highlight(language, input, true).value;\n    }\n\n    /**\n     * Highlight Syntax highlighter\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Syntax highlighter in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default SyntaxHighlighter;\n"
  },
  {
    "path": "src/core/operations/TCPIPChecksum.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * TCP/IP Checksum operation\n */\nclass TCPIPChecksum extends Operation {\n\n    /**\n     * TCPIPChecksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"TCP/IP Checksum\";\n        this.module = \"Crypto\";\n        this.description = \"Calculates the checksum for a TCP (Transport Control Protocol) or IP (Internet Protocol) header from an input of raw bytes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/IPv4_header_checksum\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        let csum = 0;\n\n        for (let i = 0; i < input.length; i++) {\n            if (i % 2 === 0) {\n                csum += (input[i] << 8);\n            } else {\n                csum += input[i];\n            }\n        }\n\n        csum = (csum >> 16) + (csum & 0xffff);\n\n        return Utils.hex(0xffff - csum);\n    }\n\n}\n\nexport default TCPIPChecksum;\n"
  },
  {
    "path": "src/core/operations/Tail.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {INPUT_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * Tail operation\n */\nclass Tail extends Operation {\n\n    /**\n     * Tail constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Tail\";\n        this.module = \"Default\";\n        this.description = \"Like the UNIX tail utility.<br>Gets the last n lines.<br>Optionally you can select all lines after line n by entering a negative value for n.<br>The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Tail_(Unix)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": INPUT_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Number\",\n                \"type\": \"number\",\n                \"value\": 10\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let delimiter = args[0];\n        const number = args[1];\n\n        delimiter = Utils.charRep(delimiter);\n        const splitInput = input.split(delimiter);\n\n        return splitInput\n            .filter((line, lineIndex) => {\n                lineIndex += 1;\n\n                if (number < 0) {\n                    return lineIndex > -number;\n                } else {\n                    return lineIndex > splitInput.length - number;\n                }\n            })\n            .join(delimiter);\n\n    }\n\n}\n\nexport default Tail;\n"
  },
  {
    "path": "src/core/operations/TakeBytes.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * Take bytes operation\n */\nclass TakeBytes extends Operation {\n\n    /**\n     * TakeBytes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Take bytes\";\n        this.module = \"Default\";\n        this.description = \"Takes a slice of the specified number of bytes from the data. Negative values are allowed.\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Start\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Length\",\n                \"type\": \"number\",\n                \"value\": 5\n            },\n            {\n                \"name\": \"Apply to each line\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     *\n     * @throws {OperationError} if invalid value\n     */\n    run(input, args) {\n        let start = args[0],\n            length = args[1];\n        const applyToEachLine = args[2];\n\n        if (!applyToEachLine) {\n            if (start < 0) { // Take from the end\n                start = input.byteLength + start;\n            }\n\n            if (length < 0) { // Flip start point\n                start = start + length;\n                if (start < 0) {\n                    start = input.byteLength + start;\n                    length = start - length;\n                } else {\n                    length = -length;\n                }\n            }\n\n            return input.slice(start, start+length);\n        }\n\n        // Split input into lines\n        const data = new Uint8Array(input);\n        const lines = [];\n        let line = [],\n            i;\n\n        for (i = 0; i < data.length; i++) {\n            if (data[i] === 0x0a) {\n                lines.push(line);\n                line = [];\n            } else {\n                line.push(data[i]);\n            }\n        }\n        lines.push(line);\n\n        let output = [];\n        let s = start,\n            l = length;\n        for (i = 0; i < lines.length; i++) {\n            if (s < 0) { // Take from the end\n                s = lines[i].length + s;\n            }\n\n            if (l < 0) { // Flip start point\n                s = s + l;\n                if (s < 0) {\n                    s = lines[i].length + s;\n                    l = s - l;\n                } else {\n                    l = -l;\n                }\n            }\n            output = output.concat(lines[i].slice(s, s+l));\n            output.push(0x0a);\n            s = start;\n            l = length;\n        }\n        return new Uint8Array(output.slice(0, output.length-1)).buffer;\n    }\n\n}\n\nexport default TakeBytes;\n"
  },
  {
    "path": "src/core/operations/TakeNthBytes.mjs",
    "content": "/**\n * @author Oshawk [oshawk@protonmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Take nth bytes operation\n */\nclass TakeNthBytes extends Operation {\n\n    /**\n     * TakeNthBytes constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Take nth bytes\";\n        this.module = \"Default\";\n        this.description = \"Takes every nth byte starting with a given byte.\";\n        this.infoURL = \"\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Take every\",\n                type: \"number\",\n                value: 4\n            },\n            {\n                name: \"Starting at\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Apply to each line\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const n = args[0];\n        const start = args[1];\n        const eachLine = args[2];\n\n        if (parseInt(n, 10) !== n || n <= 0) {\n            throw new OperationError(\"'Take every' must be a positive integer.\");\n        }\n        if (parseInt(start, 10) !== start || start < 0) {\n            throw new OperationError(\"'Starting at' must be a positive or zero integer.\");\n        }\n\n        let offset = 0;\n        const output = [];\n        for (let i = 0; i < input.length; i++) {\n            if (eachLine && input[i] === 0x0a) {\n                output.push(0x0a);\n                offset = i + 1;\n            } else if (i - offset >= start && (i - (start + offset)) % n === 0) {\n                output.push(input[i]);\n            }\n        }\n\n        return output;\n    }\n\n}\n\nexport default TakeNthBytes;\n"
  },
  {
    "path": "src/core/operations/Tar.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Tar operation\n */\nclass Tar extends Operation {\n\n    /**\n     * Tar constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Tar\";\n        this.module = \"Compression\";\n        this.description = \"Packs the input into a tarball.<br><br>No support for multiple files at this time.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Tar_(computing)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"File\";\n        this.args = [\n            {\n                \"name\": \"Filename\",\n                \"type\": \"string\",\n                \"value\": \"file.txt\"\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n\n        const Tarball = function() {\n            this.bytes = new Array(512);\n            this.position = 0;\n        };\n\n        Tarball.prototype.addEmptyBlock = function() {\n            const filler = new Array(512);\n            filler.fill(0);\n            this.bytes = this.bytes.concat(filler);\n        };\n\n        Tarball.prototype.writeBytes = function(bytes) {\n            const self = this;\n\n            if (this.position + bytes.length > this.bytes.length) {\n                this.addEmptyBlock();\n            }\n\n            Array.prototype.forEach.call(bytes, function(b, i) {\n                if (typeof b.charCodeAt !== \"undefined\") {\n                    b = b.charCodeAt();\n                }\n\n                self.bytes[self.position] = b;\n                self.position += 1;\n            });\n        };\n\n        Tarball.prototype.writeEndBlocks = function() {\n            const numEmptyBlocks = 2;\n            for (let i = 0; i < numEmptyBlocks; i++) {\n                this.addEmptyBlock();\n            }\n        };\n\n        const fileSize = input.length.toString(8).padStart(11, \"0\");\n        const currentUnixTimestamp = Math.floor(Date.now() / 1000);\n        const lastModTime = currentUnixTimestamp.toString(8).padStart(11, \"0\");\n\n        const file = {\n            fileName: Utils.padBytesRight(args[0], 100),\n            fileMode: Utils.padBytesRight(\"0000664\", 8),\n            ownerUID: Utils.padBytesRight(\"0\", 8),\n            ownerGID: Utils.padBytesRight(\"0\", 8),\n            size: Utils.padBytesRight(fileSize, 12),\n            lastModTime: Utils.padBytesRight(lastModTime, 12),\n            checksum: \"        \",\n            type: \"0\",\n            linkedFileName: Utils.padBytesRight(\"\", 100),\n            USTARFormat: Utils.padBytesRight(\"ustar\", 6),\n            version: \"00\",\n            ownerUserName: Utils.padBytesRight(\"\", 32),\n            ownerGroupName: Utils.padBytesRight(\"\", 32),\n            deviceMajor: Utils.padBytesRight(\"\", 8),\n            deviceMinor: Utils.padBytesRight(\"\", 8),\n            fileNamePrefix: Utils.padBytesRight(\"\", 155),\n        };\n\n        let checksum = 0;\n        for (const key in file) {\n            const bytes = file[key];\n            Array.prototype.forEach.call(bytes, function(b) {\n                if (typeof b.charCodeAt !== \"undefined\") {\n                    checksum += b.charCodeAt();\n                } else {\n                    checksum += b;\n                }\n            });\n        }\n        checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, \"0\"), 8);\n        file.checksum = checksum;\n\n        const tarball = new Tarball();\n        tarball.writeBytes(file.fileName);\n        tarball.writeBytes(file.fileMode);\n        tarball.writeBytes(file.ownerUID);\n        tarball.writeBytes(file.ownerGID);\n        tarball.writeBytes(file.size);\n        tarball.writeBytes(file.lastModTime);\n        tarball.writeBytes(file.checksum);\n        tarball.writeBytes(file.type);\n        tarball.writeBytes(file.linkedFileName);\n        tarball.writeBytes(file.USTARFormat);\n        tarball.writeBytes(file.version);\n        tarball.writeBytes(file.ownerUserName);\n        tarball.writeBytes(file.ownerGroupName);\n        tarball.writeBytes(file.deviceMajor);\n        tarball.writeBytes(file.deviceMinor);\n        tarball.writeBytes(file.fileNamePrefix);\n        tarball.writeBytes(Utils.padBytesRight(\"\", 12));\n        tarball.writeBytes(input);\n        tarball.writeEndBlocks();\n\n        return new File([new Uint8Array(tarball.bytes)], args[0]);\n    }\n\n}\n\nexport default Tar;\n"
  },
  {
    "path": "src/core/operations/Template.mjs",
    "content": "/**\n * @author kendallgoto [k@kgo.to]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Handlebars from \"handlebars\";\n\n/**\n * Template operation\n */\nclass Template extends Operation {\n\n    /**\n     * Template constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Template\";\n        this.module = \"Handlebars\";\n        this.description = \"Render a template with Handlebars/Mustache substituting variables using JSON input. Templates will be rendered to plain-text only, to prevent XSS.\";\n        this.infoURL = \"https://handlebarsjs.com/\";\n        this.inputType = \"JSON\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Template definition (.handlebars)\",\n                type: \"text\",\n                value: \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [templateStr] = args;\n        try {\n            const template = Handlebars.compile(templateStr);\n            return template(input);\n        } catch (e) {\n            throw new OperationError(e);\n        }\n    }\n}\n\nexport default Template;\n"
  },
  {
    "path": "src/core/operations/TextEncodingBruteForce.mjs",
    "content": "/**\n * @author Cynser\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport cptable from \"codepage\";\nimport {CHR_ENC_CODE_PAGES} from \"../lib/ChrEnc.mjs\";\n\n/**\n * Text Encoding Brute Force operation\n */\nclass TextEncodingBruteForce extends Operation {\n\n    /**\n     * TextEncodingBruteForce constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Text Encoding Brute Force\";\n        this.module = \"Encodings\";\n        this.description = [\n            \"Enumerates all supported text encodings for the input, allowing you to quickly spot the correct one.\",\n            \"<br><br>\",\n            \"Supported charsets are:\",\n            \"<ul>\",\n            Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join(\"\\n\"),\n            \"</ul>\"\n        ].join(\"\\n\");\n        this.infoURL = \"https://wikipedia.org/wiki/Character_encoding\";\n        this.inputType = \"string\";\n        this.outputType = \"json\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Mode\",\n                type: \"option\",\n                value: [\"Encode\", \"Decode\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {json}\n     */\n    run(input, args) {\n        const output = {},\n            charsets = Object.keys(CHR_ENC_CODE_PAGES),\n            mode = args[0];\n\n        charsets.forEach(charset => {\n            try {\n                if (mode === \"Decode\") {\n                    output[charset] = cptable.utils.decode(CHR_ENC_CODE_PAGES[charset], input);\n                } else {\n                    output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(CHR_ENC_CODE_PAGES[charset], input));\n                }\n            } catch (err) {\n                output[charset] = \"Could not decode.\";\n            }\n        });\n\n        return output;\n    }\n\n    /**\n     * Displays the encodings in an HTML table for web apps.\n     *\n     * @param {Object[]} encodings\n     * @returns {html}\n     */\n    present(encodings) {\n        let table = \"<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Encoding</th><th>Value</th></tr>\";\n\n        for (const enc in encodings) {\n            const value = Utils.escapeHtml(Utils.escapeWhitespace(encodings[enc]));\n            table += `<tr><td>${enc}</td><td>${value}</td></tr>`;\n        }\n\n        table += \"<table>\";\n        return table;\n    }\n\n}\n\nexport default TextEncodingBruteForce;\n"
  },
  {
    "path": "src/core/operations/TextIntegerConverter.mjs",
    "content": "/**\n * @author p-leriche [philip.leriche@cantab.net]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/* ---------- helper functions ---------- */\n\n/**\n * Convert text to BigInt (big-endian byte interpretation)\n */\nfunction textToBigInt(text) {\n    if (text.length === 0) return 0n;\n\n    let result = 0n;\n    for (let i = 0; i < text.length; i++) {\n        const charCode = BigInt(text.charCodeAt(i));\n        if (charCode > 255n) {\n            throw new OperationError(\n                `Character at position ${i} exceeds Latin-1 range (0-255).\\n` +\n                \"Only ASCII and Latin-1 characters are supported.\");\n        }\n        result = (result << 8n) | charCode;\n    }\n    return result;\n}\n\n/**\n * Convert BigInt to text (big-endian byte interpretation)\n */\nfunction bigIntToText(value) {\n    if (value === 0n) return \"\";\n\n    const bytes = [];\n    let num = value;\n\n    while (num > 0n) {\n        bytes.unshift(Number(num & 0xFFn));\n        num >>= 8n;\n    }\n\n    return String.fromCharCode(...bytes);\n}\n\n/* ---------- operation class ---------- */\n\n/**\n * Text/Integer Converter operation\n */\nclass TextIntegerConverter extends Operation {\n    /**\n     * TextIntegerConverter constructor\n     */\n    constructor() {\n        super();\n\n        this.description =\n            \"Converts between text strings and large integers (decimal or hexadecimal).<br><br>\" +\n            \"Text is interpreted as a big-endian sequence of character codes. For example:<br>\" +\n            \"ABC is 0x414243 (hex) is 4276803 (decimal)<br>\" +\n            \"<b>Input format detection:</b><br>\" +\n            \"Decimal: digits 0-9 only<br>\" +\n            \"Hexadecimal: 0x... prefix<br>\" +\n            \"Quoted or unquoted text: treated as string<br><br>\" +\n            \"<b>Character limitations:</b><br>\" +\n            \"Text input may only contain ASCII and Latin-1 characters (code point < 256).<br>\" +\n            \"Multi-byte Unicode characters will generate an error.<br><br>.\" ;\n        this.infoURL = \"https://wikipedia.org/wiki/Endianness\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Output format\",\n                type: \"option\",\n                value: [\"String\", \"Decimal\", \"Hexadecimal\"]\n            }\n        ];\n        this.name = \"Text-Integer Conversion\";\n        this.module = \"Default\";\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const outputFormat = args[0];\n        const trimmed = input.trim();\n\n        let bigIntValue;\n\n        if (!trimmed) {\n            // Null input - treat as zero\n            bigIntValue = 0;\n        } else if (/^0x[0-9a-f]+$/i.test(trimmed) ||\n            /^[+-]?[0-9]+$/.test(trimmed)) {\n            // Hex or decimal integer\n            bigIntValue = BigInt(trimmed);\n        } else if (/^[\"'].*[\"']$/.test(trimmed)) {\n            // Quoted string: Remove quotes and convert text to BigInt\n            const text = trimmed.slice(1, -1);\n            bigIntValue = textToBigInt(text);\n        } else {\n            // Assume it's unquoted text\n            bigIntValue = textToBigInt(trimmed);\n        }\n\n        // Convert to output format\n        if (outputFormat === \"String\") {\n            return bigIntToText(bigIntValue);\n        } else if (outputFormat === \"Decimal\") {\n            return bigIntValue.toString();\n        } else { // Hexadecimal\n            return \"0x\" + bigIntValue.toString(16);\n        }\n    }\n}\n\nexport default TextIntegerConverter;\n"
  },
  {
    "path": "src/core/operations/ToBCD.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from \"../lib/BCD.mjs\";\nimport BigNumber from \"bignumber.js\";\n\n/**\n * To BCD operation\n */\nclass ToBCD extends Operation {\n\n    /**\n     * ToBCD constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To BCD\";\n        this.module = \"Default\";\n        this.description = \"Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign\";\n        this.infoURL = \"https://wikipedia.org/wiki/Binary-coded_decimal\";\n        this.inputType = \"BigNumber\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Scheme\",\n                \"type\": \"option\",\n                \"value\": ENCODING_SCHEME\n            },\n            {\n                \"name\": \"Packed\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Signed\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Output format\",\n                \"type\": \"option\",\n                \"value\": FORMAT\n            }\n        ];\n    }\n\n    /**\n     * @param {BigNumber} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (input.isNaN())\n            throw new OperationError(\"Invalid input\");\n        if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input))\n            throw new OperationError(\"Fractional values are not supported by BCD\");\n\n        const encoding = ENCODING_LOOKUP[args[0]],\n            packed = args[1],\n            signed = args[2],\n            outputFormat = args[3];\n\n        // Split input number up into separate digits\n        const digits = input.toFixed().split(\"\");\n\n        if (digits[0] === \"-\" || digits[0] === \"+\") {\n            digits.shift();\n        }\n\n        let nibbles = [];\n\n        digits.forEach(d => {\n            const n = parseInt(d, 10);\n            nibbles.push(encoding[n]);\n        });\n\n        if (signed) {\n            if (packed && digits.length % 2 === 0) {\n                // If there are an even number of digits, we add a leading 0 so\n                // that the sign nibble doesn't sit in its own byte, leading to\n                // ambiguity around whether the number ends with a 0 or not.\n                nibbles.unshift(encoding[0]);\n            }\n\n            nibbles.push(input > 0 ? 12 : 13);\n            // 12 (\"C\") for + (credit)\n            // 13 (\"D\") for - (debit)\n        }\n\n        let bytes = [];\n\n        if (packed) {\n            let encoded = 0,\n                little = false;\n\n            nibbles.forEach(n => {\n                encoded ^= little ? n : (n << 4);\n                if (little) {\n                    bytes.push(encoded);\n                    encoded = 0;\n                }\n                little = !little;\n            });\n\n            if (little) bytes.push(encoded);\n        } else {\n            bytes = nibbles;\n\n            // Add null high nibbles\n            nibbles = nibbles.map(n => {\n                return [0, n];\n            }).reduce((a, b) => {\n                return a.concat(b);\n            });\n        }\n\n        // Output\n        switch (outputFormat) {\n            case \"Nibbles\":\n                return nibbles.map(n => {\n                    return n.toString(2).padStart(4, \"0\");\n                }).join(\" \");\n            case \"Bytes\":\n                return bytes.map(b => {\n                    return b.toString(2).padStart(8, \"0\");\n                }).join(\" \");\n            case \"Raw\":\n            default:\n                return Utils.byteArrayToChars(bytes);\n        }\n    }\n\n}\n\nexport default ToBCD;\n"
  },
  {
    "path": "src/core/operations/ToBase.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * To Base operation\n */\nclass ToBase extends Operation {\n\n    /**\n     * ToBase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base\";\n        this.module = \"Default\";\n        this.description = \"Converts a decimal number to a given numerical base.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Radix\";\n        this.inputType = \"BigNumber\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Radix\",\n                \"type\": \"number\",\n                \"value\": 36\n            }\n        ];\n    }\n\n    /**\n     * @param {BigNumber} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input) {\n            throw new OperationError(\"Error: Input must be a number\");\n        }\n        const radix = args[0];\n        if (radix < 2 || radix > 36) {\n            throw new OperationError(\"Error: Radix argument must be between 2 and 36\");\n        }\n        return input.toString(radix);\n    }\n\n}\n\nexport default ToBase;\n"
  },
  {
    "path": "src/core/operations/ToBase32.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {ALPHABET_OPTIONS} from \"../lib/Base32.mjs\";\n\n/**\n * To Base32 operation\n */\nclass ToBase32 extends Operation {\n\n    /**\n     * ToBase32 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base32\";\n        this.module = \"Default\";\n        this.description = \"Base32 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. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Base32\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"editableOption\",\n                value: ALPHABET_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input) return \"\";\n        input = new Uint8Array(input);\n\n        const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join(\"\") : \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=\";\n        let output = \"\",\n            chr1, chr2, chr3, chr4, chr5,\n            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,\n            i = 0;\n        while (i < input.length) {\n            chr1 = input[i++];\n            chr2 = input[i++];\n            chr3 = input[i++];\n            chr4 = input[i++];\n            chr5 = input[i++];\n\n            enc1 = chr1 >> 3;\n            enc2 = ((chr1 & 7) << 2) | (chr2 >> 6);\n            enc3 = (chr2 >> 1) & 31;\n            enc4 = ((chr2 & 1) << 4) | (chr3 >> 4);\n            enc5 = ((chr3 & 15) << 1) | (chr4 >> 7);\n            enc6 = (chr4 >> 2) & 31;\n            enc7 = ((chr4 & 3) << 3) | (chr5 >> 5);\n            enc8 = chr5 & 31;\n\n            if (isNaN(chr2)) {\n                enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32;\n            } else if (isNaN(chr3)) {\n                enc5 = enc6 = enc7 = enc8 = 32;\n            } else if (isNaN(chr4)) {\n                enc6 = enc7 = enc8 = 32;\n            } else if (isNaN(chr5)) {\n                enc8 = 32;\n            }\n\n            output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +\n                alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +\n                alphabet.charAt(enc7) + alphabet.charAt(enc8);\n        }\n        return output;\n    }\n\n}\n\nexport default ToBase32;\n\n"
  },
  {
    "path": "src/core/operations/ToBase45.mjs",
    "content": "/**\n * @author Thomas Weißschuh [thomas@t-8ch.de]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport {ALPHABET, highlightToBase45, highlightFromBase45} from \"../lib/Base45.mjs\";\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * To Base45 operation\n */\nclass ToBase45 extends Operation {\n\n    /**\n     * ToBase45 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base45\";\n        this.module = \"Default\";\n        this.description = \"Base45 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. The high number base results in shorter strings than with the decimal or hexadecimal system. Base45 is optimized for usage with QR codes.\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_numeral_systems\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"string\",\n                value: ALPHABET\n            }\n        ];\n\n        this.highlight = highlightToBase45;\n        this.highlightReverse = highlightFromBase45;\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!input) return \"\";\n        input = new Uint8Array(input);\n        const alphabet = Utils.expandAlphRange(args[0]);\n\n        const res = [];\n\n        for (const pair of Utils.chunked(input, 2)) {\n            let b = 0;\n            for (const e of pair) {\n                b *= 256;\n                b += e;\n            }\n\n            let chars = 0;\n            do {\n                res.push(alphabet[b % 45]);\n                chars++;\n                b = Math.floor(b / 45);\n            } while (b > 0);\n\n            if (chars < 2) {\n                res.push(\"0\");\n                chars++;\n            }\n            if (pair.length > 1 && chars < 3) {\n                res.push(\"0\");\n            }\n        }\n\n\n        return res.join(\"\");\n\n    }\n\n}\n\nexport default ToBase45;\n"
  },
  {
    "path": "src/core/operations/ToBase58.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {ALPHABET_OPTIONS} from \"../lib/Base58.mjs\";\n\n/**\n * To Base58 operation\n */\nclass ToBase58 extends Operation {\n\n    /**\n     * ToBase58 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base58\";\n        this.module = \"Default\";\n        this.description = \"Base58 (similar to Base64) is a notation for encoding arbitrary byte data. It differs from Base64 by removing easily misread characters (i.e. l, I, 0 and O) to improve human readability.<br><br>This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>hello world</code> becomes <code>StV1DL6CwTryKyV</code><br><br>Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Base58\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Alphabet\",\n                \"type\": \"editableOption\",\n                \"value\": ALPHABET_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        let alphabet = args[0] || ALPHABET_OPTIONS[0].value,\n            result = [];\n\n        alphabet = Utils.expandAlphRange(alphabet).join(\"\");\n\n        if (alphabet.length !== 58 ||\n            [].unique.call(alphabet).length !== 58) {\n            throw new OperationError(\"Error: alphabet must be of length 58\");\n        }\n\n        if (input.length === 0) return \"\";\n\n        let zeroPrefix = 0;\n        for (let i = 0; i < input.length && input[i] === 0; i++) {\n            zeroPrefix++;\n        }\n\n        input.forEach(function(b) {\n            let carry = b;\n\n            for (let i = 0; i < result.length; i++) {\n                carry += result[i] << 8;\n                result[i] = carry % 58;\n                carry = (carry / 58) | 0;\n            }\n\n            while (carry > 0) {\n                result.push(carry % 58);\n                carry = (carry / 58) | 0;\n            }\n        });\n\n        result = result.map(function(b) {\n            return alphabet[b];\n        }).reverse().join(\"\");\n\n        while (zeroPrefix--) {\n            result = alphabet[0] + result;\n        }\n\n        return result;\n    }\n\n}\n\nexport default ToBase58;\n"
  },
  {
    "path": "src/core/operations/ToBase62.mjs",
    "content": "/**\n * @author tcode2k16 [tcode2k16@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport BigNumber from \"bignumber.js\";\nimport Utils from \"../Utils.mjs\";\nimport {toHexFast} from \"../lib/Hex.mjs\";\n\n/**\n * To Base62 operation\n */\nclass ToBase62 extends Operation {\n\n    /**\n     * ToBase62 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base62\";\n        this.module = \"Default\";\n        this.description = \"Base62 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. The high number base results in shorter strings than with the decimal or hexadecimal system.\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_numeral_systems\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"string\",\n                value: \"0-9A-Za-z\"\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        if (input.length < 1) return \"\";\n\n        const alphabet = Utils.expandAlphRange(args[0]).join(\"\");\n        const BN62 = BigNumber.clone({ ALPHABET: alphabet });\n\n        input = toHexFast(input).toUpperCase();\n\n        // Read number in as hex using normal alphabet\n        const normalized = new BigNumber(input, 16);\n        // Copy to BigNumber clone that uses the specified Base62 alphabet\n        const number = new BN62(normalized);\n\n        return number.toString(62);\n    }\n\n}\n\nexport default ToBase62;\n"
  },
  {
    "path": "src/core/operations/ToBase64.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {toBase64, ALPHABET_OPTIONS} from \"../lib/Base64.mjs\";\n\n/**\n * To Base64 operation\n */\nclass ToBase64 extends Operation {\n\n    /**\n     * ToBase64 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base64\";\n        this.module = \"Default\";\n        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>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Base64\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"editableOption\",\n                value: ALPHABET_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const alphabet = args[0];\n        return toBase64(input, alphabet);\n    }\n\n    /**\n     * Highlight to Base64\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        pos[0].start = Math.floor(pos[0].start / 3 * 4);\n        pos[0].end = Math.ceil(pos[0].end / 3 * 4);\n        return pos;\n    }\n\n    /**\n     * Highlight from Base64\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        pos[0].start = Math.ceil(pos[0].start / 4 * 3);\n        pos[0].end = Math.floor(pos[0].end / 4 * 3);\n        return pos;\n    }\n}\n\nexport default ToBase64;\n"
  },
  {
    "path": "src/core/operations/ToBase85.mjs",
    "content": "/**\n * @author PenguinGeorge [george@penguingeorge.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {alphabetName, ALPHABET_OPTIONS} from \"../lib/Base85.mjs\";\n\n/**\n * To Base85 operation\n */\nclass ToBase85 extends Operation {\n\n    /**\n     * To Base85 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base85\";\n        this.module = \"Default\";\n        this.description = \"Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.<br><br>This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>hello world</code> becomes <code>BOu!rD]j7BEbo7</code><br><br>Base85 is commonly used in Adobe's PostScript and PDF file formats.<br><br><strong>Options</strong><br><u>Alphabet</u><ul><li>Standard - The standard alphabet, referred to as Ascii85</li><li>Z85 (ZeroMQ) - A string-safe variant of Base85, which avoids quote marks and backslash characters</li><li>IPv6 - A variant of Base85 suitable for encoding IPv6 addresses (RFC 1924)</li></ul><u>Include delimiter</u><br>Adds a '<~' and '~>' delimiter to the start and end of the data. This is standard for Adobe's implementation of Base85.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Ascii85\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Alphabet\",\n                type: \"editableOption\",\n                value: ALPHABET_OPTIONS\n            },\n            {\n                name: \"Include delimiter\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n    */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const alphabet = Utils.expandAlphRange(args[0]).join(\"\"),\n            encoding = alphabetName(alphabet),\n            includeDelim = args[1];\n        let result = \"\";\n\n        if (alphabet.length !== 85 ||\n            [].unique.call(alphabet).length !== 85) {\n            throw new OperationError(\"Error: Alphabet must be of length 85\");\n        }\n\n        if (input.length === 0) return \"\";\n\n        let block;\n        for (let i = 0; i < input.length; i += 4) {\n            block = (\n                ((input[i])          << 24) +\n                ((input[i + 1] || 0) << 16) +\n                ((input[i + 2] || 0) << 8)  +\n                ((input[i + 3] || 0))\n            ) >>> 0;\n\n            if (encoding !== \"Standard\" || block > 0) {\n                let digits = [];\n                for (let j = 0; j < 5; j++) {\n                    digits.push(block % 85);\n                    block = Math.floor(block / 85);\n                }\n\n                digits = digits.reverse();\n\n                if (input.length < i + 4) {\n                    digits.splice(input.length - (i + 4), 4);\n                }\n\n                result += digits.map(digit => alphabet[digit]).join(\"\");\n            } else {\n                result += (encoding === \"Standard\") ? \"z\" : null;\n            }\n        }\n\n        return includeDelim ? `<~${result}~>` : result;\n    }\n}\n\nexport default ToBase85;\n"
  },
  {
    "path": "src/core/operations/ToBase92.mjs",
    "content": "/**\n * @author sg5506844 [sg5506844@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport { base92Chr } from \"../lib/Base92.mjs\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * To Base92 operation\n */\nclass ToBase92 extends Operation {\n    /**\n     * ToBase92 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Base92\";\n        this.module = \"Default\";\n        this.description = \"Base92 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.\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_numeral_systems\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const res = [];\n        let bitString = \"\";\n\n        while (input.length > 0) {\n            while (bitString.length < 13 && input.length > 0) {\n                bitString += input[0].charCodeAt(0).toString(2).padStart(8, \"0\");\n                input = input.slice(1);\n            }\n            if (bitString.length < 13)\n                break;\n            const i = parseInt(bitString.slice(0, 13), 2);\n            res.push(base92Chr(Math.floor(i / 91)));\n            res.push(base92Chr(i % 91));\n            bitString = bitString.slice(13);\n        }\n\n        if (bitString.length > 0) {\n            if (bitString.length < 7) {\n                bitString = bitString.padEnd(6, \"0\");\n                res.push(base92Chr(parseInt(bitString, 2)));\n            } else {\n                bitString = bitString.padEnd(13, \"0\");\n                const i = parseInt(bitString.slice(0, 13), 2);\n                res.push(base92Chr(Math.floor(i / 91)));\n                res.push(base92Chr(i % 91));\n            }\n        }\n\n        return res;\n\n    }\n}\n\nexport default ToBase92;\n"
  },
  {
    "path": "src/core/operations/ToBech32.mjs",
    "content": "/**\n * @author Medjedtxm\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { encode } from \"../lib/Bech32.mjs\";\nimport { fromHex } from \"../lib/Hex.mjs\";\n\n/**\n * To Bech32 operation\n */\nclass ToBech32 extends Operation {\n\n    /**\n     * ToBech32 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Bech32\";\n        this.module = \"Default\";\n        this.description = \"Bech32 is an encoding scheme primarily used for Bitcoin SegWit addresses (BIP-0173). It uses a 32-character alphabet that excludes easily confused characters (1, b, i, o) and includes a checksum for error detection.<br><br>Bech32m (BIP-0350) is an updated version that fixes a weakness in the original Bech32 checksum and is used for Bitcoin Taproot addresses.<br><br>The Human-Readable Part (HRP) identifies the network or purpose (e.g., 'bc' for Bitcoin mainnet, 'tb' for testnet, 'age' for AGE encryption keys).<br><br>Maximum output length is 90 characters as per specification.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bech32\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Human-Readable Part (HRP)\",\n                \"type\": \"string\",\n                \"value\": \"bc\"\n            },\n            {\n                \"name\": \"Encoding\",\n                \"type\": \"option\",\n                \"value\": [\"Bech32\", \"Bech32m\"]\n            },\n            {\n                \"name\": \"Input Format\",\n                \"type\": \"option\",\n                \"value\": [\"Raw bytes\", \"Hex\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"Generic\", \"Bitcoin SegWit\"]\n            },\n            {\n                \"name\": \"Witness Version\",\n                \"type\": \"number\",\n                \"value\": 0,\n                \"hint\": \"SegWit witness version (0-16). Only used in Bitcoin SegWit mode.\"\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const hrp = args[0];\n        const encoding = args[1];\n        const inputFormat = args[2];\n        const mode = args[3];\n        const witnessVersion = args[4];\n\n        let inputArray;\n        if (inputFormat === \"Hex\") {\n            // Convert hex string to bytes\n            const hexStr = new TextDecoder().decode(new Uint8Array(input)).replace(/\\s/g, \"\");\n            inputArray = fromHex(hexStr);\n        } else {\n            inputArray = new Uint8Array(input);\n        }\n\n        if (mode === \"Bitcoin SegWit\") {\n            // Prepend witness version to the input data\n            const withVersion = new Uint8Array(inputArray.length + 1);\n            withVersion[0] = witnessVersion;\n            withVersion.set(inputArray, 1);\n            return encode(hrp, withVersion, encoding, true);\n        }\n\n        return encode(hrp, inputArray, encoding, false);\n    }\n\n}\n\nexport default ToBech32;\n"
  },
  {
    "path": "src/core/operations/ToBinary.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {BIN_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\nimport {toBinary} from \"../lib/Binary.mjs\";\n\n/**\n * To Binary operation\n */\nclass ToBinary extends Operation {\n\n    /**\n     * ToBinary constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Binary\";\n        this.module = \"Default\";\n        this.description = \"Displays the input data as a binary string.<br><br>e.g. <code>Hi</code> becomes <code>01001000 01101001</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Binary_code\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": BIN_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Byte Length\",\n                \"type\": \"number\",\n                \"value\": 8\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const padding = args[1] ? args[1] : 8;\n        return toBinary(input, args[0], padding);\n    }\n\n    /**\n     * Highlight To Binary\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n        pos[0].start = pos[0].start * (8 + delim.length);\n        pos[0].end = pos[0].end * (8 + delim.length) - delim.length;\n        return pos;\n    }\n\n    /**\n     * Highlight To Binary in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n        pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length));\n        pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length));\n        return pos;\n    }\n\n}\n\nexport default ToBinary;\n"
  },
  {
    "path": "src/core/operations/ToBraille.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {BRAILLE_LOOKUP} from \"../lib/Braille.mjs\";\n\n/**\n * To Braille operation\n */\nclass ToBraille extends Operation {\n\n    /**\n     * ToBraille constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Braille\";\n        this.module = \"Default\";\n        this.description = \"Converts text to six-dot braille symbols.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Braille\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return input.split(\"\").map(c => {\n            const idx = BRAILLE_LOOKUP.ascii.indexOf(c.toUpperCase());\n            return idx < 0 ? c : BRAILLE_LOOKUP.dot6[idx];\n        }).join(\"\");\n    }\n\n    /**\n     * Highlight To Braille\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight To Braille in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default ToBraille;\n"
  },
  {
    "path": "src/core/operations/ToCamelCase.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport camelCase from \"lodash/camelCase.js\";\nimport Operation from \"../Operation.mjs\";\nimport { replaceVariableNames } from \"../lib/Code.mjs\";\n\n/**\n * To Camel case operation\n */\nclass ToCamelCase extends Operation {\n\n    /**\n     * ToCamelCase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Camel case\";\n        this.module = \"Code\";\n        this.description = \"Converts the input string to camel case.\\n<br><br>\\nCamel case is all lower case except letters after word boundaries which are uppercase.\\n<br><br>\\ne.g. thisIsCamelCase\\n<br><br>\\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Camel_case\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Attempt to be context aware\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const smart = args[0];\n\n        if (smart) {\n            return replaceVariableNames(input, camelCase);\n        } else {\n            return camelCase(input);\n        }\n    }\n\n}\n\nexport default ToCamelCase;\n"
  },
  {
    "path": "src/core/operations/ToCaseInsensitiveRegex.mjs",
    "content": "/**\n * @author masq [github.cyberchef@masq.cc]\n * @author n1073645\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * To Case Insensitive Regex operation\n */\nclass ToCaseInsensitiveRegex extends Operation {\n\n    /**\n     * ToCaseInsensitiveRegex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Case Insensitive Regex\";\n        this.module = \"Default\";\n        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>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Regular_expression\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n\n        /**\n         * Simulates look behind behaviour since javascript doesn't support it.\n         *\n         * @param {string} input\n         * @returns {string}\n         */\n        function preProcess(input) {\n            let result = \"\";\n            for (let i = 0; i < input.length; i++) {\n                const temp = input.charAt(i);\n                if (temp.match(/[a-zA-Z]/g) && (input.charAt(i-1) !== \"-\") && (input.charAt(i+1) !== \"-\"))\n                    result += \"[\" + temp.toLowerCase() + temp.toUpperCase() + \"]\";\n                else\n                    result += temp;\n            }\n            return result;\n        }\n\n        try {\n            RegExp(input);\n        } catch (error) {\n            throw new OperationError(\"Invalid Regular Expression (Please note this version of node does not support look behinds).\");\n        }\n\n        // Example: [test] -> [[tT][eE][sS][tT]]\n        return preProcess(input)\n\n            // Example: [A-Z] -> [A-Za-z]\n            .replace(/([A-Z]-[A-Z]|[a-z]-[a-z])/g, m => `${m[0].toUpperCase()}-${m[2].toUpperCase()}${m[0].toLowerCase()}-${m[2].toLowerCase()}`)\n\n            // Example: [H-d] -> [A-DH-dh-z]\n            .replace(/[A-Z]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}${m[0].toLowerCase()}-z`)\n\n            // Example: [!-D] -> [!-Da-d]\n            .replace(/\\\\?[ -@]-[A-Z]/g, m => `${m}a-${m[2].toLowerCase()}`)\n\n            // Example: [%-^] -> [%-^a-z]\n            .replace(/\\\\?[ -@]-\\\\?[[-`]/g, m => `${m}a-z`)\n\n            // Example: [K-`] -> [K-`k-z]\n            .replace(/[A-Z]-\\\\?[[-`]/g, m => `${m}${m[0].toLowerCase()}-z`)\n\n            // Example: [[-}] -> [[-}A-Z]\n            .replace(/\\\\?[[-`]-\\\\?[{-~]/g, m => `${m}A-Z`)\n\n            // Example: [b-}] -> [b-}B-Z]\n            .replace(/[a-z]-\\\\?[{-~]/g, m => `${m}${m[0].toUpperCase()}-Z`)\n\n            // Example: [<-j] -> [<-z]\n            .replace(/\\\\?[ -@]-[a-z]/g, m => `${m[0]}-z`)\n\n            // Example: [^-j] -> [A-J^-j]\n            .replace(/\\\\?[[-`]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}`);\n\n    }\n}\n\nexport default ToCaseInsensitiveRegex;\n"
  },
  {
    "path": "src/core/operations/ToCharcode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { DELIM_OPTIONS } from \"../lib/Delim.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * To Charcode operation\n */\nclass ToCharcode extends Operation {\n\n    /**\n     * ToCharcode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Charcode\";\n        this.module = \"Default\";\n        this.description = \"Converts text to its unicode character code equivalent.<br><br>e.g. <code>Γειά σου</code> becomes <code>0393 03b5 03b9 03ac 20 03c3 03bf 03c5</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Plane_(Unicode)\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Base\",\n                \"type\": \"number\",\n                \"value\": 16\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if base argument out of range\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0] || \"Space\"),\n            base = args[1];\n        let output = \"\",\n            padding,\n            ordinal;\n\n        if (base < 2 || base > 36) {\n            throw new OperationError(\"Error: Base argument must be between 2 and 36\");\n        }\n\n        const charcode = Utils.strToCharcode(input);\n        for (let i = 0; i < charcode.length; i++) {\n            ordinal = charcode[i];\n\n            if (base === 16) {\n                if (ordinal < 256) padding = 2;\n                else if (ordinal < 65536) padding = 4;\n                else if (ordinal < 16777216) padding = 6;\n                else if (ordinal < 4294967296) padding = 8;\n                else padding = 2;\n\n                if (padding > 2 && isWorkerEnvironment()) self.setOption(\"attemptHighlight\", false);\n\n                output += Utils.hex(ordinal, padding) + delim;\n            } else {\n                if (isWorkerEnvironment()) self.setOption(\"attemptHighlight\", false);\n                output += ordinal.toString(base) + delim;\n            }\n        }\n\n        return output.slice(0, -delim.length);\n    }\n\n}\n\nexport default ToCharcode;\n"
  },
  {
    "path": "src/core/operations/ToDecimal.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n\n/**\n * To Decimal operation\n */\nclass ToDecimal extends Operation {\n\n    /**\n     * ToDecimal constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Decimal\";\n        this.module = \"Default\";\n        this.description = \"Converts the input data to an ordinal integer array.<br><br>e.g. <code>Hello</code> becomes <code>72 101 108 108 111</code>\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Support signed values\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0]),\n            signed = args[1];\n        if (signed) {\n            input = new Int8Array(input);\n        } else {\n            input = new Uint8Array(input);\n        }\n        return input.join(delim);\n    }\n\n}\n\nexport default ToDecimal;\n"
  },
  {
    "path": "src/core/operations/ToFloat.mjs",
    "content": "/**\n * @author tcode2k16 [tcode2k16@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport ieee754 from \"ieee754\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * To Float operation\n */\nclass ToFloat extends Operation {\n\n    /**\n     * ToFloat constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Float\";\n        this.module = \"Default\";\n        this.description = \"Convert to IEEE754 Floating Point Numbers\";\n        this.infoURL = \"https://wikipedia.org/wiki/IEEE_754\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Endianness\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Big Endian\",\n                    \"Little Endian\"\n                ]\n            },\n            {\n                \"name\": \"Size\",\n                \"type\": \"option\",\n                \"value\": [\n                    \"Float (4 bytes)\",\n                    \"Double (8 bytes)\"\n                ]\n            },\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [endianness, size, delimiterName] = args;\n        const delim = Utils.charRep(delimiterName || \"Space\");\n        const byteSize = size === \"Double (8 bytes)\" ? 8 : 4;\n        const isLE = endianness === \"Little Endian\";\n        const mLen = byteSize === 4 ? 23 : 52;\n\n        if (input.length % byteSize !== 0) {\n            throw new OperationError(`Input is not a multiple of ${byteSize}`);\n        }\n\n        const output = [];\n        for (let i = 0; i < input.length; i+=byteSize) {\n            output.push(ieee754.read(input, i, isLE, mLen, byteSize));\n        }\n        return output.join(delim);\n    }\n\n}\n\nexport default ToFloat;\n"
  },
  {
    "path": "src/core/operations/ToHTMLEntity.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * To HTML Entity operation\n */\nclass ToHTMLEntity extends Operation {\n\n    /**\n     * ToHTMLEntity constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To HTML Entity\";\n        this.module = \"Encodings\";\n        this.description = \"Converts characters to HTML entities<br><br>e.g. <code>&amp;</code> becomes <code>&amp;<span>amp;</span></code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Convert all characters\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Convert to\",\n                \"type\": \"option\",\n                \"value\": [\"Named entities\", \"Numeric entities\", \"Hex entities\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const convertAll = args[0],\n            numeric = args[1] === \"Numeric entities\",\n            hexa = args[1] === \"Hex entities\";\n\n        const charcodes = Utils.strToCharcode(input);\n        let output = \"\";\n\n        for (let i = 0; i < charcodes.length; i++) {\n            if (convertAll && numeric) {\n                output += \"&#\" + charcodes[i] + \";\";\n            } else if (convertAll && hexa) {\n                output += \"&#x\" + Utils.hex(charcodes[i]) + \";\";\n            } else if (convertAll) {\n                output += byteToEntity[charcodes[i]] || \"&#\" + charcodes[i] + \";\";\n            } else if (numeric) {\n                if (charcodes[i] > 255 || charcodes[i] in byteToEntity) {\n                    output += \"&#\" + charcodes[i] + \";\";\n                } else {\n                    output += Utils.chr(charcodes[i]);\n                }\n            } else if (hexa) {\n                if (charcodes[i] > 255 || charcodes[i] in byteToEntity) {\n                    output += \"&#x\" + Utils.hex(charcodes[i]) + \";\";\n                } else {\n                    output += Utils.chr(charcodes[i]);\n                }\n            } else {\n                output += byteToEntity[charcodes[i]] || (\n                    charcodes[i] > 255 ?\n                        \"&#\" + charcodes[i] + \";\" :\n                        Utils.chr(charcodes[i])\n                );\n            }\n        }\n        return output;\n    }\n\n}\n\n/**\n * Lookup table to translate byte values to their HTML entity codes.\n */\nconst byteToEntity = {\n    9: \"&Tab;\",\n    10: \"&NewLine;\",\n    33: \"&excl;\",\n    34: \"&quot;\",\n    35: \"&num;\",\n    36: \"&dollar;\",\n    37: \"&percnt;\",\n    38: \"&amp;\",\n    39: \"&apos;\",\n    40: \"&lpar;\",\n    41: \"&rpar;\",\n    42: \"&ast;\",\n    43: \"&plus;\",\n    44: \"&comma;\",\n    46: \"&period;\",\n    47: \"&sol;\",\n    58: \"&colon;\",\n    59: \"&semi;\",\n    60: \"&lt;\",\n    61: \"&equals;\",\n    62: \"&gt;\",\n    63: \"&quest;\",\n    64: \"&commat;\",\n    91: \"&lsqb;\",\n    92: \"&bsol;\",\n    93: \"&rsqb;\",\n    94: \"&Hat;\",\n    95: \"&lowbar;\",\n    96: \"&grave;\",\n    123: \"&lcub;\",\n    124: \"&verbar;\",\n    125: \"&rcub;\",\n    160: \"&nbsp;\",\n    161: \"&iexcl;\",\n    162: \"&cent;\",\n    163: \"&pound;\",\n    164: \"&curren;\",\n    165: \"&yen;\",\n    166: \"&brvbar;\",\n    167: \"&sect;\",\n    168: \"&uml;\",\n    169: \"&copy;\",\n    170: \"&ordf;\",\n    171: \"&laquo;\",\n    172: \"&not;\",\n    173: \"&shy;\",\n    174: \"&reg;\",\n    175: \"&macr;\",\n    176: \"&deg;\",\n    177: \"&plusmn;\",\n    178: \"&sup2;\",\n    179: \"&sup3;\",\n    180: \"&acute;\",\n    181: \"&micro;\",\n    182: \"&para;\",\n    183: \"&middot;\",\n    184: \"&cedil;\",\n    185: \"&sup1;\",\n    186: \"&ordm;\",\n    187: \"&raquo;\",\n    188: \"&frac14;\",\n    189: \"&frac12;\",\n    190: \"&frac34;\",\n    191: \"&iquest;\",\n    192: \"&Agrave;\",\n    193: \"&Aacute;\",\n    194: \"&Acirc;\",\n    195: \"&Atilde;\",\n    196: \"&Auml;\",\n    197: \"&Aring;\",\n    198: \"&AElig;\",\n    199: \"&Ccedil;\",\n    200: \"&Egrave;\",\n    201: \"&Eacute;\",\n    202: \"&Ecirc;\",\n    203: \"&Euml;\",\n    204: \"&Igrave;\",\n    205: \"&Iacute;\",\n    206: \"&Icirc;\",\n    207: \"&Iuml;\",\n    208: \"&ETH;\",\n    209: \"&Ntilde;\",\n    210: \"&Ograve;\",\n    211: \"&Oacute;\",\n    212: \"&Ocirc;\",\n    213: \"&Otilde;\",\n    214: \"&Ouml;\",\n    215: \"&times;\",\n    216: \"&Oslash;\",\n    217: \"&Ugrave;\",\n    218: \"&Uacute;\",\n    219: \"&Ucirc;\",\n    220: \"&Uuml;\",\n    221: \"&Yacute;\",\n    222: \"&THORN;\",\n    223: \"&szlig;\",\n    224: \"&agrave;\",\n    225: \"&aacute;\",\n    226: \"&acirc;\",\n    227: \"&atilde;\",\n    228: \"&auml;\",\n    229: \"&aring;\",\n    230: \"&aelig;\",\n    231: \"&ccedil;\",\n    232: \"&egrave;\",\n    233: \"&eacute;\",\n    234: \"&ecirc;\",\n    235: \"&euml;\",\n    236: \"&igrave;\",\n    237: \"&iacute;\",\n    238: \"&icirc;\",\n    239: \"&iuml;\",\n    240: \"&eth;\",\n    241: \"&ntilde;\",\n    242: \"&ograve;\",\n    243: \"&oacute;\",\n    244: \"&ocirc;\",\n    245: \"&otilde;\",\n    246: \"&ouml;\",\n    247: \"&divide;\",\n    248: \"&oslash;\",\n    249: \"&ugrave;\",\n    250: \"&uacute;\",\n    251: \"&ucirc;\",\n    252: \"&uuml;\",\n    253: \"&yacute;\",\n    254: \"&thorn;\",\n    255: \"&yuml;\",\n    256: \"&Amacr;\",\n    257: \"&amacr;\",\n    258: \"&Abreve;\",\n    259: \"&abreve;\",\n    260: \"&Aogon;\",\n    261: \"&aogon;\",\n    262: \"&Cacute;\",\n    263: \"&cacute;\",\n    264: \"&Ccirc;\",\n    265: \"&ccirc;\",\n    266: \"&Cdot;\",\n    267: \"&cdot;\",\n    268: \"&Ccaron;\",\n    269: \"&ccaron;\",\n    270: \"&Dcaron;\",\n    271: \"&dcaron;\",\n    272: \"&Dstrok;\",\n    273: \"&dstrok;\",\n    274: \"&Emacr;\",\n    275: \"&emacr;\",\n    278: \"&Edot;\",\n    279: \"&edot;\",\n    280: \"&Eogon;\",\n    281: \"&eogon;\",\n    282: \"&Ecaron;\",\n    283: \"&ecaron;\",\n    284: \"&Gcirc;\",\n    285: \"&gcirc;\",\n    286: \"&Gbreve;\",\n    287: \"&gbreve;\",\n    288: \"&Gdot;\",\n    289: \"&gdot;\",\n    290: \"&Gcedil;\",\n    292: \"&Hcirc;\",\n    293: \"&hcirc;\",\n    294: \"&Hstrok;\",\n    295: \"&hstrok;\",\n    296: \"&Itilde;\",\n    297: \"&itilde;\",\n    298: \"&Imacr;\",\n    299: \"&imacr;\",\n    302: \"&Iogon;\",\n    303: \"&iogon;\",\n    304: \"&Idot;\",\n    305: \"&imath;\",\n    306: \"&IJlig;\",\n    307: \"&ijlig;\",\n    308: \"&Jcirc;\",\n    309: \"&jcirc;\",\n    310: \"&Kcedil;\",\n    311: \"&kcedil;\",\n    312: \"&kgreen;\",\n    313: \"&Lacute;\",\n    314: \"&lacute;\",\n    315: \"&Lcedil;\",\n    316: \"&lcedil;\",\n    317: \"&Lcaron;\",\n    318: \"&lcaron;\",\n    319: \"&Lmidot;\",\n    320: \"&lmidot;\",\n    321: \"&Lstrok;\",\n    322: \"&lstrok;\",\n    323: \"&Nacute;\",\n    324: \"&nacute;\",\n    325: \"&Ncedil;\",\n    326: \"&ncedil;\",\n    327: \"&Ncaron;\",\n    328: \"&ncaron;\",\n    329: \"&napos;\",\n    330: \"&ENG;\",\n    331: \"&eng;\",\n    332: \"&Omacr;\",\n    333: \"&omacr;\",\n    336: \"&Odblac;\",\n    337: \"&odblac;\",\n    338: \"&OElig;\",\n    339: \"&oelig;\",\n    340: \"&Racute;\",\n    341: \"&racute;\",\n    342: \"&Rcedil;\",\n    343: \"&rcedil;\",\n    344: \"&Rcaron;\",\n    345: \"&rcaron;\",\n    346: \"&Sacute;\",\n    347: \"&sacute;\",\n    348: \"&Scirc;\",\n    349: \"&scirc;\",\n    350: \"&Scedil;\",\n    351: \"&scedil;\",\n    352: \"&Scaron;\",\n    353: \"&scaron;\",\n    354: \"&Tcedil;\",\n    355: \"&tcedil;\",\n    356: \"&Tcaron;\",\n    357: \"&tcaron;\",\n    358: \"&Tstrok;\",\n    359: \"&tstrok;\",\n    360: \"&Utilde;\",\n    361: \"&utilde;\",\n    362: \"&Umacr;\",\n    363: \"&umacr;\",\n    364: \"&Ubreve;\",\n    365: \"&ubreve;\",\n    366: \"&Uring;\",\n    367: \"&uring;\",\n    368: \"&Udblac;\",\n    369: \"&udblac;\",\n    370: \"&Uogon;\",\n    371: \"&uogon;\",\n    372: \"&Wcirc;\",\n    373: \"&wcirc;\",\n    374: \"&Ycirc;\",\n    375: \"&ycirc;\",\n    376: \"&Yuml;\",\n    377: \"&Zacute;\",\n    378: \"&zacute;\",\n    379: \"&Zdot;\",\n    380: \"&zdot;\",\n    381: \"&Zcaron;\",\n    382: \"&zcaron;\",\n    402: \"&fnof;\",\n    437: \"&imped;\",\n    501: \"&gacute;\",\n    567: \"&jmath;\",\n    710: \"&circ;\",\n    711: \"&caron;\",\n    728: \"&breve;\",\n    729: \"&dot;\",\n    730: \"&ring;\",\n    731: \"&ogon;\",\n    732: \"&tilde;\",\n    785: \"&DownBreve;\",\n    818: \"&UnderBar;\",\n    913: \"&Alpha;\",\n    914: \"&Beta;\",\n    915: \"&Gamma;\",\n    916: \"&Delta;\",\n    917: \"&Epsilon;\",\n    918: \"&Zeta;\",\n    919: \"&Eta;\",\n    920: \"&Theta;\",\n    921: \"&Iota;\",\n    922: \"&Kappa;\",\n    923: \"&Lambda;\",\n    924: \"&Mu;\",\n    925: \"&Nu;\",\n    926: \"&Xi;\",\n    927: \"&Omicron;\",\n    928: \"&Pi;\",\n    929: \"&Rho;\",\n    931: \"&Sigma;\",\n    932: \"&Tau;\",\n    933: \"&Upsilon;\",\n    934: \"&Phi;\",\n    935: \"&Chi;\",\n    936: \"&Psi;\",\n    937: \"&Omega;\",\n    945: \"&alpha;\",\n    946: \"&beta;\",\n    947: \"&gamma;\",\n    948: \"&delta;\",\n    949: \"&epsilon;\",\n    950: \"&zeta;\",\n    951: \"&eta;\",\n    952: \"&theta;\",\n    953: \"&iota;\",\n    954: \"&kappa;\",\n    955: \"&lambda;\",\n    956: \"&mu;\",\n    957: \"&nu;\",\n    958: \"&xi;\",\n    959: \"&omicron;\",\n    960: \"&pi;\",\n    961: \"&rho;\",\n    962: \"&sigmaf;\",\n    963: \"&sigma;\",\n    964: \"&tau;\",\n    965: \"&upsilon;\",\n    966: \"&phi;\",\n    967: \"&chi;\",\n    968: \"&psi;\",\n    969: \"&omega;\",\n    977: \"&thetasym;\",\n    978: \"&upsih;\",\n    981: \"&straightphi;\",\n    982: \"&piv;\",\n    988: \"&Gammad;\",\n    989: \"&gammad;\",\n    1008: \"&kappav;\",\n    1009: \"&rhov;\",\n    1013: \"&epsi;,\",\n    1014: \"&bepsi;\",\n    1025: \"&IOcy;\",\n    1026: \"&DJcy;\",\n    1027: \"&GJcy;\",\n    1028: \"&Jukcy;\",\n    1029: \"&DScy;\",\n    1030: \"&Iukcy;\",\n    1031: \"&YIcy;\",\n    1032: \"&Jsercy;\",\n    1033: \"&LJcy;\",\n    1034: \"&NJcy;\",\n    1035: \"&TSHcy;\",\n    1036: \"&KJcy;\",\n    1038: \"&Ubrcy;\",\n    1039: \"&DZcy;\",\n    1040: \"&Acy;\",\n    1041: \"&Bcy;\",\n    1042: \"&Vcy;\",\n    1043: \"&Gcy;\",\n    1044: \"&Dcy;\",\n    1045: \"&IEcy;\",\n    1046: \"&ZHcy;\",\n    1047: \"&Zcy;\",\n    1048: \"&Icy;\",\n    1049: \"&Jcy;\",\n    1050: \"&Kcy;\",\n    1051: \"&Lcy;\",\n    1052: \"&Mcy;\",\n    1053: \"&Ncy;\",\n    1054: \"&Ocy;\",\n    1055: \"&Pcy;\",\n    1056: \"&Rcy;\",\n    1057: \"&Scy;\",\n    1058: \"&Tcy;\",\n    1059: \"&Ucy;\",\n    1060: \"&Fcy;\",\n    1061: \"&KHcy;\",\n    1062: \"&TScy;\",\n    1063: \"&CHcy;\",\n    1064: \"&SHcy;\",\n    1065: \"&SHCHcy;\",\n    1066: \"&HARDcy;\",\n    1067: \"&Ycy;\",\n    1068: \"&SOFTcy;\",\n    1069: \"&Ecy;\",\n    1070: \"&YUcy;\",\n    1071: \"&YAcy;\",\n    1072: \"&acy;\",\n    1073: \"&bcy;\",\n    1074: \"&vcy;\",\n    1075: \"&gcy;\",\n    1076: \"&dcy;\",\n    1077: \"&iecy;\",\n    1078: \"&zhcy;\",\n    1079: \"&zcy;\",\n    1080: \"&icy;\",\n    1081: \"&jcy;\",\n    1082: \"&kcy;\",\n    1083: \"&lcy;\",\n    1084: \"&mcy;\",\n    1085: \"&ncy;\",\n    1086: \"&ocy;\",\n    1087: \"&pcy;\",\n    1088: \"&rcy;\",\n    1089: \"&scy;\",\n    1090: \"&tcy;\",\n    1091: \"&ucy;\",\n    1092: \"&fcy;\",\n    1093: \"&khcy;\",\n    1094: \"&tscy;\",\n    1095: \"&chcy;\",\n    1096: \"&shcy;\",\n    1097: \"&shchcy;\",\n    1098: \"&hardcy;\",\n    1099: \"&ycy;\",\n    1100: \"&softcy;\",\n    1101: \"&ecy;\",\n    1102: \"&yucy;\",\n    1103: \"&yacy;\",\n    1105: \"&iocy;\",\n    1106: \"&djcy;\",\n    1107: \"&gjcy;\",\n    1108: \"&jukcy;\",\n    1109: \"&dscy;\",\n    1110: \"&iukcy;\",\n    1111: \"&yicy;\",\n    1112: \"&jsercy;\",\n    1113: \"&ljcy;\",\n    1114: \"&njcy;\",\n    1115: \"&tshcy;\",\n    1116: \"&kjcy;\",\n    1118: \"&ubrcy;\",\n    1119: \"&dzcy;\",\n    8194: \"&ensp;\",\n    8195: \"&emsp;\",\n    8196: \"&emsp13;\",\n    8197: \"&emsp14;\",\n    8199: \"&numsp;\",\n    8200: \"&puncsp;\",\n    8201: \"&thinsp;\",\n    8202: \"&hairsp;\",\n    8203: \"&ZeroWidthSpace;\",\n    8204: \"&zwnj;\",\n    8205: \"&zwj;\",\n    8206: \"&lrm;\",\n    8207: \"&rlm;\",\n    8208: \"&hyphen;\",\n    8211: \"&ndash;\",\n    8212: \"&mdash;\",\n    8213: \"&horbar;\",\n    8214: \"&Verbar;\",\n    8216: \"&lsquo;\",\n    8217: \"&rsquo;\",\n    8218: \"&sbquo;\",\n    8220: \"&ldquo;\",\n    8221: \"&rdquo;\",\n    8222: \"&bdquo;\",\n    8224: \"&dagger;\",\n    8225: \"&Dagger;\",\n    8226: \"&bull;\",\n    8229: \"&nldr;\",\n    8230: \"&hellip;\",\n    8240: \"&permil;\",\n    8241: \"&pertenk;\",\n    8242: \"&prime;\",\n    8243: \"&Prime;\",\n    8244: \"&tprime;\",\n    8245: \"&bprime;\",\n    8249: \"&lsaquo;\",\n    8250: \"&rsaquo;\",\n    8254: \"&oline;\",\n    8257: \"&caret;\",\n    8259: \"&hybull;\",\n    8260: \"&frasl;\",\n    8271: \"&bsemi;\",\n    8279: \"&qprime;\",\n    8287: \"&MediumSpace;\",\n    8288: \"&NoBreak;\",\n    8289: \"&ApplyFunction;\",\n    8290: \"&InvisibleTimes;\",\n    8291: \"&InvisibleComma;\",\n    8364: \"&euro;\",\n    8411: \"&tdot;\",\n    8412: \"&DotDot;\",\n    8450: \"&Copf;\",\n    8453: \"&incare;\",\n    8458: \"&gscr;\",\n    8459: \"&hamilt;\",\n    8460: \"&Hfr;\",\n    8461: \"&quaternions;\",\n    8462: \"&planckh;\",\n    8463: \"&planck;\",\n    8464: \"&Iscr;\",\n    8465: \"&image;\",\n    8466: \"&Lscr;\",\n    8467: \"&ell;\",\n    8469: \"&Nopf;\",\n    8470: \"&numero;\",\n    8471: \"&copysr;\",\n    8472: \"&weierp;\",\n    8473: \"&Popf;\",\n    8474: \"&rationals;\",\n    8475: \"&Rscr;\",\n    8476: \"&real;\",\n    8477: \"&reals;\",\n    8478: \"&rx;\",\n    8482: \"&trade;\",\n    8484: \"&integers;\",\n    8486: \"&ohm;\",\n    8487: \"&mho;\",\n    8488: \"&Zfr;\",\n    8489: \"&iiota;\",\n    8491: \"&angst;\",\n    8492: \"&bernou;\",\n    8493: \"&Cfr;\",\n    8495: \"&escr;\",\n    8496: \"&Escr;\",\n    8497: \"&Fscr;\",\n    8499: \"&phmmat;\",\n    8500: \"&order;\",\n    8501: \"&alefsym;\",\n    8502: \"&beth;\",\n    8503: \"&gimel;\",\n    8504: \"&daleth;\",\n    8517: \"&CapitalDifferentialD;\",\n    8518: \"&DifferentialD;\",\n    8519: \"&ExponentialE;\",\n    8520: \"&ImaginaryI;\",\n    8531: \"&frac13;\",\n    8532: \"&frac23;\",\n    8533: \"&frac15;\",\n    8534: \"&frac25;\",\n    8535: \"&frac35;\",\n    8536: \"&frac45;\",\n    8537: \"&frac16;\",\n    8538: \"&frac56;\",\n    8539: \"&frac18;\",\n    8540: \"&frac38;\",\n    8541: \"&frac58;\",\n    8542: \"&frac78;\",\n    8592: \"&larr;\",\n    8593: \"&uarr;\",\n    8594: \"&rarr;\",\n    8595: \"&darr;\",\n    8596: \"&harr;\",\n    8597: \"&varr;\",\n    8598: \"&nwarr;\",\n    8599: \"&nearr;\",\n    8600: \"&searr;\",\n    8601: \"&swarr;\",\n    8602: \"&nlarr;\",\n    8603: \"&nrarr;\",\n    8605: \"&rarrw;\",\n    8606: \"&Larr;\",\n    8607: \"&Uarr;\",\n    8608: \"&Rarr;\",\n    8609: \"&Darr;\",\n    8610: \"&larrtl;\",\n    8611: \"&rarrtl;\",\n    8612: \"&LeftTeeArrow;\",\n    8613: \"&UpTeeArrow;\",\n    8614: \"&map;\",\n    8615: \"&DownTeeArrow;\",\n    8617: \"&larrhk;\",\n    8618: \"&rarrhk;\",\n    8619: \"&larrlp;\",\n    8620: \"&rarrlp;\",\n    8621: \"&harrw;\",\n    8622: \"&nharr;\",\n    8624: \"&lsh;\",\n    8625: \"&rsh;\",\n    8626: \"&ldsh;\",\n    8627: \"&rdsh;\",\n    8629: \"&crarr;\",\n    8630: \"&cularr;\",\n    8631: \"&curarr;\",\n    8634: \"&olarr;\",\n    8635: \"&orarr;\",\n    8636: \"&lharu;\",\n    8637: \"&lhard;\",\n    8638: \"&uharr;\",\n    8639: \"&uharl;\",\n    8640: \"&rharu;\",\n    8641: \"&rhard;\",\n    8642: \"&dharr;\",\n    8643: \"&dharl;\",\n    8644: \"&rlarr;\",\n    8645: \"&udarr;\",\n    8646: \"&lrarr;\",\n    8647: \"&llarr;\",\n    8648: \"&uuarr;\",\n    8649: \"&rrarr;\",\n    8650: \"&ddarr;\",\n    8651: \"&lrhar;\",\n    8652: \"&rlhar;;\",\n    8653: \"&nlArr;\",\n    8654: \"&nhArr;\",\n    8655: \"&nrArr;\",\n    8656: \"&lArr;\",\n    8657: \"&uArr;\",\n    8658: \"&rArr;\",\n    8659: \"&dArr;\",\n    8660: \"&hArr;\",\n    8661: \"&vArr;\",\n    8662: \"&nwArr;\",\n    8663: \"&neArr;\",\n    8664: \"&seArr;\",\n    8665: \"&swArr;\",\n    8666: \"&lAarr;\",\n    8667: \"&rAarr;\",\n    8669: \"&zigrarr;\",\n    8676: \"&larrb;\",\n    8677: \"&rarrb;\",\n    8693: \"&duarr;\",\n    8701: \"&loarr;\",\n    8702: \"&roarr;\",\n    8703: \"&hoarr;\",\n    8704: \"&forall;\",\n    8705: \"&comp;\",\n    8706: \"&part;\",\n    8707: \"&exist;\",\n    8708: \"&nexist;\",\n    8709: \"&empty;\",\n    8711: \"&nabla;\",\n    8712: \"&isin;\",\n    8713: \"&notin;\",\n    8715: \"&ni;\",\n    8716: \"&notni;\",\n    8719: \"&prod;\",\n    8720: \"&coprod;\",\n    8721: \"&sum;\",\n    8722: \"&minus;\",\n    8723: \"&mnplus;\",\n    8724: \"&plusdo;\",\n    8726: \"&setmn;\",\n    8727: \"&lowast;\",\n    8728: \"&compfn;\",\n    8730: \"&radic;\",\n    8733: \"&prop;\",\n    8734: \"&infin;\",\n    8735: \"&angrt;\",\n    8736: \"&ang;\",\n    8737: \"&angmsd;\",\n    8738: \"&angsph;\",\n    8739: \"&mid;\",\n    8740: \"&nmid;\",\n    8741: \"&par;\",\n    8742: \"&npar;\",\n    8743: \"&and;\",\n    8744: \"&or;\",\n    8745: \"&cap;\",\n    8746: \"&cup;\",\n    8747: \"&int;\",\n    8748: \"&Int;\",\n    8749: \"&tint;\",\n    8750: \"&conint;\",\n    8751: \"&Conint;\",\n    8752: \"&Cconint;\",\n    8753: \"&cwint;\",\n    8754: \"&cwconint;\",\n    8755: \"&awconint;\",\n    8756: \"&there4;\",\n    8757: \"&becaus;\",\n    8758: \"&ratio;\",\n    8759: \"&Colon;\",\n    8760: \"&minusd;\",\n    8762: \"&mDDot;\",\n    8763: \"&homtht;\",\n    8764: \"&sim;\",\n    8765: \"&bsim;\",\n    8766: \"&ac;\",\n    8767: \"&acd;\",\n    8768: \"&wreath;\",\n    8769: \"&nsim;\",\n    8770: \"&esim;\",\n    8771: \"&sime;\",\n    8772: \"&nsime;\",\n    8773: \"&cong;\",\n    8774: \"&simne;\",\n    8775: \"&ncong;\",\n    8776: \"&asymp;\",\n    8777: \"&nap;\",\n    8778: \"&ape;\",\n    8779: \"&apid;\",\n    8780: \"&bcong;\",\n    8781: \"&asympeq;\",\n    8782: \"&bump;\",\n    8783: \"&bumpe;\",\n    8784: \"&esdot;\",\n    8785: \"&eDot;\",\n    8786: \"&efDot;\",\n    8787: \"&erDot;\",\n    8788: \"&colone;\",\n    8789: \"&ecolon;\",\n    8790: \"&ecir;\",\n    8791: \"&cire;\",\n    8793: \"&wedgeq;\",\n    8794: \"&veeeq;\",\n    8796: \"&trie;\",\n    8799: \"&equest;\",\n    8800: \"&ne;\",\n    8801: \"&equiv;\",\n    8802: \"&nequiv;\",\n    8804: \"&le;\",\n    8805: \"&ge;\",\n    8806: \"&lE;\",\n    8807: \"&gE;\",\n    8808: \"&lnE;\",\n    8809: \"&gnE;\",\n    8810: \"&Lt;\",\n    8811: \"&Gt;\",\n    8812: \"&twixt;\",\n    8813: \"&NotCupCap;\",\n    8814: \"&nlt;\",\n    8815: \"&ngt;\",\n    8816: \"&nle;\",\n    8817: \"&nge;;\",\n    8818: \"&lsim;\",\n    8819: \"&gsim;\",\n    8820: \"&nlsim;\",\n    8821: \"&ngsim;\",\n    8822: \"&lg;\",\n    8823: \"&gl;\",\n    8824: \"&ntlg;\",\n    8825: \"&ntgl;\",\n    8826: \"&pr;\",\n    8827: \"&sc;\",\n    8828: \"&prcue;\",\n    8829: \"&sccue;\",\n    8830: \"&prsim;\",\n    8831: \"&scsim;\",\n    8832: \"&npr;\",\n    8833: \"&nsc;\",\n    8834: \"&sub;\",\n    8835: \"&sup;\",\n    8836: \"&nsub;\",\n    8837: \"&nsup;\",\n    8838: \"&sube;\",\n    8839: \"&supe;\",\n    8840: \"&nsube;\",\n    8841: \"&nsupe;\",\n    8842: \"&subne;\",\n    8843: \"&supne;\",\n    8845: \"&cupdot;\",\n    8846: \"&uplus;\",\n    8847: \"&sqsub;\",\n    8848: \"&sqsup;\",\n    8849: \"&sqsube;\",\n    8850: \"&sqsupe;\",\n    8851: \"&sqcap;\",\n    8852: \"&sqcup;\",\n    8853: \"&oplus;\",\n    8854: \"&ominus;\",\n    8855: \"&otimes;\",\n    8856: \"&osol;\",\n    8857: \"&odot;\",\n    8858: \"&ocir;\",\n    8859: \"&oast;\",\n    8861: \"&odash;\",\n    8862: \"&plusb;\",\n    8863: \"&minusb;\",\n    8864: \"&timesb;\",\n    8865: \"&sdotb;\",\n    8866: \"&vdash;\",\n    8867: \"&dashv;\",\n    8868: \"&top;\",\n    8869: \"&perp;\",\n    8871: \"&models;\",\n    8872: \"&vDash;\",\n    8873: \"&Vdash;\",\n    8874: \"&Vvdash;\",\n    8875: \"&VDash;\",\n    8876: \"&nvdash;\",\n    8877: \"&nvDash;\",\n    8878: \"&nVdash;\",\n    8879: \"&nVDash;\",\n    8880: \"&prurel;\",\n    8882: \"&vltri;\",\n    8883: \"&vrtri;\",\n    8884: \"&ltrie;\",\n    8885: \"&rtrie;\",\n    8886: \"&origof;\",\n    8887: \"&imof;\",\n    8888: \"&mumap;\",\n    8889: \"&hercon;\",\n    8890: \"&intcal;\",\n    8891: \"&veebar;\",\n    8893: \"&barvee;\",\n    8894: \"&angrtvb;\",\n    8895: \"&lrtri;\",\n    8896: \"&xwedge;\",\n    8897: \"&xvee;\",\n    8898: \"&xcap;\",\n    8899: \"&xcup;\",\n    8900: \"&diam;\",\n    8901: \"&sdot;\",\n    8902: \"&sstarf;\",\n    8903: \"&divonx;\",\n    8904: \"&bowtie;\",\n    8905: \"&ltimes;\",\n    8906: \"&rtimes;\",\n    8907: \"&lthree;\",\n    8908: \"&rthree;\",\n    8909: \"&bsime;\",\n    8910: \"&cuvee;\",\n    8911: \"&cuwed;\",\n    8912: \"&Sub;\",\n    8913: \"&Sup;\",\n    8914: \"&Cap;\",\n    8915: \"&Cup;\",\n    8916: \"&fork;\",\n    8917: \"&epar;\",\n    8918: \"&ltdot;\",\n    8919: \"&gtdot;\",\n    8920: \"&Ll;\",\n    8921: \"&Gg;\",\n    8922: \"&leg;\",\n    8923: \"&gel;\",\n    8926: \"&cuepr;\",\n    8927: \"&cuesc;\",\n    8928: \"&nprcue;\",\n    8929: \"&nsccue;\",\n    8930: \"&nsqsube;\",\n    8931: \"&nsqsupe;\",\n    8934: \"&lnsim;\",\n    8935: \"&gnsim;\",\n    8936: \"&prnsim;\",\n    8937: \"&scnsim;\",\n    8938: \"&nltri;\",\n    8939: \"&nrtri;\",\n    8940: \"&nltrie;\",\n    8941: \"&nrtrie;\",\n    8942: \"&vellip;\",\n    8943: \"&ctdot;\",\n    8944: \"&utdot;\",\n    8945: \"&dtdot;\",\n    8946: \"&disin;\",\n    8947: \"&isinsv;\",\n    8948: \"&isins;\",\n    8949: \"&isindot;\",\n    8950: \"&notinvc;\",\n    8951: \"&notinvb;\",\n    8953: \"&isinE;\",\n    8954: \"&nisd;\",\n    8955: \"&xnis;\",\n    8956: \"&nis;\",\n    8957: \"&notnivc;\",\n    8958: \"&notnivb;\",\n    8965: \"&barwed;\",\n    8966: \"&Barwed;\",\n    8968: \"&lceil;\",\n    8969: \"&rceil;\",\n    8970: \"&lfloor;\",\n    8971: \"&rfloor;\",\n    8972: \"&drcrop;\",\n    8973: \"&dlcrop;\",\n    8974: \"&urcrop;\",\n    8975: \"&ulcrop;\",\n    8976: \"&bnot;\",\n    8978: \"&profline;\",\n    8979: \"&profsurf;\",\n    8981: \"&telrec;\",\n    8982: \"&target;\",\n    8988: \"&ulcorn;\",\n    8989: \"&urcorn;\",\n    8990: \"&dlcorn;\",\n    8991: \"&drcorn;\",\n    8994: \"&frown;\",\n    8995: \"&smile;\",\n    9001: \"&lang;\",\n    9002: \"&rang;\",\n    9005: \"&cylcty;\",\n    9006: \"&profalar;\",\n    9014: \"&topbot;\",\n    9021: \"&ovbar;\",\n    9023: \"&solbar;\",\n    9084: \"&angzarr;\",\n    9136: \"&lmoust;\",\n    9137: \"&rmoust;\",\n    9140: \"&tbrk;\",\n    9141: \"&bbrk;\",\n    9142: \"&bbrktbrk;\",\n    9180: \"&OverParenthesis;\",\n    9181: \"&UnderParenthesis;\",\n    9182: \"&OverBrace;\",\n    9183: \"&UnderBrace;\",\n    9186: \"&trpezium;\",\n    9191: \"&elinters;\",\n    9251: \"&blank;\",\n    9416: \"&oS;\",\n    9472: \"&boxh;\",\n    9474: \"&boxv;\",\n    9484: \"&boxdr;\",\n    9488: \"&boxdl;\",\n    9492: \"&boxur;\",\n    9496: \"&boxul;\",\n    9500: \"&boxvr;\",\n    9508: \"&boxvl;\",\n    9516: \"&boxhd;\",\n    9524: \"&boxhu;\",\n    9532: \"&boxvh;\",\n    9552: \"&boxH;\",\n    9553: \"&boxV;\",\n    9554: \"&boxdR;\",\n    9555: \"&boxDr;\",\n    9556: \"&boxDR;\",\n    9557: \"&boxdL;\",\n    9558: \"&boxDl;\",\n    9559: \"&boxDL;\",\n    9560: \"&boxuR;\",\n    9561: \"&boxUr;\",\n    9562: \"&boxUR;\",\n    9563: \"&boxuL;\",\n    9564: \"&boxUl;\",\n    9565: \"&boxUL;\",\n    9566: \"&boxvR;\",\n    9567: \"&boxVr;\",\n    9568: \"&boxVR;\",\n    9569: \"&boxvL;\",\n    9570: \"&boxVl;\",\n    9571: \"&boxVL;\",\n    9572: \"&boxHd;\",\n    9573: \"&boxhD;\",\n    9674: \"&loz;\",\n    9675: \"&cir;\",\n    9708: \"&tridot;\",\n    9711: \"&xcirc;\",\n    9720: \"&ultri;\",\n    9721: \"&urtri;\",\n    9722: \"&lltri;\",\n    9723: \"&EmptySmallSquare;\",\n    9724: \"&FilledSmallSquare;\",\n    9733: \"&starf;\",\n    9734: \"&star;\",\n    9742: \"&phone;\",\n    9792: \"&female;\",\n    9794: \"&male;\",\n    9824: \"&spades;\",\n    9827: \"&clubs;\",\n    9829: \"&hearts;\",\n    9830: \"&diams;\",\n    9834: \"&sung;\",\n    9837: \"&flat;\",\n    9838: \"&natur;\",\n    9839: \"&sharp;\",\n    10003: \"&check;\",\n    10007: \"&cross;\",\n    10016: \"&malt;\",\n    10038: \"&sext;\",\n    10072: \"&VerticalSeparator;\",\n    10098: \"&lbbrk;\",\n    10099: \"&rbbrk;\",\n    10214: \"&lobrk;\",\n    10215: \"&robrk;\",\n    10216: \"&lang;\",\n    10217: \"&rang;\",\n    10218: \"&Lang;\",\n    10219: \"&Rang;\",\n    10220: \"&loang;\",\n    10221: \"&roang;\",\n    10229: \"&xlarr;\",\n    10230: \"&xrarr;\",\n    10231: \"&xharr;\",\n    10232: \"&xlArr;\",\n    10233: \"&xrArr;\",\n    10234: \"&xhArr;\",\n    10236: \"&xmap;\",\n    10239: \"&dzigrarr;\",\n    10498: \"&nvlArr;\",\n    10499: \"&nvrArr;\",\n    10500: \"&nvHarr;\",\n    10501: \"&Map;\",\n    10508: \"&lbarr;\",\n    10509: \"&rbarr;\",\n    10510: \"&lBarr;\",\n    10511: \"&rBarr;\",\n    10512: \"&RBarr;\",\n    10513: \"&DDotrahd;\",\n    10514: \"&UpArrowBar;\",\n    10515: \"&DownArrowBar;\",\n    10518: \"&Rarrtl;\",\n    10521: \"&latail;\",\n    10522: \"&ratail;\",\n    10523: \"&lAtail;\",\n    10524: \"&rAtail;\",\n    10525: \"&larrfs;\",\n    10526: \"&rarrfs;\",\n    10527: \"&larrbfs;\",\n    10528: \"&rarrbfs;\",\n    10531: \"&nwarhk;\",\n    10532: \"&nearhk;\",\n    10533: \"&searhk;\",\n    10534: \"&swarhk;\",\n    10535: \"&nwnear;\",\n    10536: \"&nesear;\",\n    10537: \"&seswar;\",\n    10538: \"&swnwar;\",\n    10547: \"&rarrc;\",\n    10549: \"&cudarrr;\",\n    10550: \"&ldca;\",\n    10551: \"&rdca;\",\n    10552: \"&cudarrl;\",\n    10553: \"&larrpl;\",\n    10556: \"&curarrm;\",\n    10557: \"&cularrp;\",\n    10565: \"&rarrpl;\",\n    10568: \"&harrcir;\",\n    10569: \"&Uarrocir;\",\n    10570: \"&lurdshar;\",\n    10571: \"&ldrushar;\",\n    10574: \"&LeftRightVector;\",\n    10575: \"&RightUpDownVector;\",\n    10576: \"&DownLeftRightVector;\",\n    10577: \"&LeftUpDownVector;\",\n    10578: \"&LeftVectorBar;\",\n    10579: \"&RightVectorBar;\",\n    10580: \"&RightUpVectorBar;\",\n    10581: \"&RightDownVectorBar;\",\n    10582: \"&DownLeftVectorBar;\",\n    10583: \"&DownRightVectorBar;\",\n    10584: \"&LeftUpVectorBar;\",\n    10585: \"&LeftDownVectorBar;\",\n    10586: \"&LeftTeeVector;\",\n    10587: \"&RightTeeVector;\",\n    10588: \"&RightUpTeeVector;\",\n    10589: \"&RightDownTeeVector;\",\n    10590: \"&DownLeftTeeVector;\",\n    10591: \"&DownRightTeeVector;\",\n    10592: \"&LeftUpTeeVector;\",\n    10593: \"&LeftDownTeeVector;\",\n    10594: \"&lHar;\",\n    10595: \"&uHar;\",\n    10596: \"&rHar;\",\n    10597: \"&dHar;\",\n    10598: \"&luruhar;\",\n    10599: \"&ldrdhar;\",\n    10600: \"&ruluhar;\",\n    10601: \"&rdldhar;\",\n    10602: \"&lharul;\",\n    10603: \"&llhard;\",\n    10604: \"&rharul;\",\n    10605: \"&lrhard;\",\n    10606: \"&udhar;\",\n    10607: \"&duhar;\",\n    10608: \"&RoundImplies;\",\n    10609: \"&erarr;\",\n    10610: \"&simrarr;\",\n    10611: \"&larrsim;\",\n    10612: \"&rarrsim;\",\n    10613: \"&rarrap;\",\n    10614: \"&ltlarr;\",\n    10616: \"&gtrarr;\",\n    10617: \"&subrarr;\",\n    10619: \"&suplarr;\",\n    10620: \"&lfisht;\",\n    10621: \"&rfisht;\",\n    10622: \"&ufisht;\",\n    10623: \"&dfisht;\",\n    10629: \"&lopar;\",\n    10630: \"&ropar;\",\n    10635: \"&lbrke;\",\n    10636: \"&rbrke;\",\n    10637: \"&lbrkslu;\",\n    10638: \"&rbrksld;\",\n    10639: \"&lbrksld;\",\n    10640: \"&rbrkslu;\",\n    10641: \"&langd;\",\n    10642: \"&rangd;\",\n    10643: \"&lparlt;\",\n    10644: \"&rpargt;\",\n    10645: \"&gtlPar;\",\n    10646: \"&ltrPar;\",\n    10650: \"&vzigzag;\",\n    10652: \"&vangrt;\",\n    10653: \"&angrtvbd;\",\n    10660: \"&ange;\",\n    10661: \"&range;\",\n    10662: \"&dwangle;\",\n    10663: \"&uwangle;\",\n    10664: \"&angmsdaa;\",\n    10665: \"&angmsdab;\",\n    10666: \"&angmsdac;\",\n    10667: \"&angmsdad;\",\n    10668: \"&angmsdae;\",\n    10669: \"&angmsdaf;\",\n    10670: \"&angmsdag;\",\n    10671: \"&angmsdah;\",\n    10672: \"&bemptyv;\",\n    10673: \"&demptyv;\",\n    10674: \"&cemptyv;\",\n    10675: \"&raemptyv;\",\n    10676: \"&laemptyv;\",\n    10677: \"&ohbar;\",\n    10678: \"&omid;\",\n    10679: \"&opar;\",\n    10681: \"&operp;\",\n    10683: \"&olcross;\",\n    10684: \"&odsold;\",\n    10686: \"&olcir;\",\n    10687: \"&ofcir;\",\n    10688: \"&olt;\",\n    10689: \"&ogt;\",\n    10690: \"&cirscir;\",\n    10691: \"&cirE;\",\n    10692: \"&solb;\",\n    10693: \"&bsolb;\",\n    10697: \"&boxbox;\",\n    10701: \"&trisb;\",\n    10702: \"&rtriltri;\",\n    10703: \"&LeftTriangleBar;\",\n    10704: \"&RightTriangleBar;\",\n    10714: \"&race;\",\n    10716: \"&iinfin;\",\n    10717: \"&infintie;\",\n    10718: \"&nvinfin;\",\n    10723: \"&eparsl;\",\n    10724: \"&smeparsl;\",\n    10725: \"&eqvparsl;\",\n    10731: \"&lozf;\",\n    10740: \"&RuleDelayed;\",\n    10742: \"&dsol;\",\n    10752: \"&xodot;\",\n    10753: \"&xoplus;\",\n    10754: \"&xotime;\",\n    10756: \"&xuplus;\",\n    10758: \"&xsqcup;\",\n    10764: \"&qint;\",\n    10765: \"&fpartint;\",\n    10768: \"&cirfnint;\",\n    10769: \"&awint;\",\n    10770: \"&rppolint;\",\n    10771: \"&scpolint;\",\n    10772: \"&npolint;\",\n    10773: \"&pointint;\",\n    10774: \"&quatint;\",\n    10775: \"&intlarhk;\",\n    10786: \"&pluscir;\",\n    10787: \"&plusacir;\",\n    10788: \"&simplus;\",\n    10789: \"&plusdu;\",\n    10790: \"&plussim;\",\n    10791: \"&plustwo;\",\n    10793: \"&mcomma;\",\n    10794: \"&minusdu;\",\n    10797: \"&loplus;\",\n    10798: \"&roplus;\",\n    10799: \"&Cross;\",\n    10800: \"&timesd;\",\n    10801: \"&timesbar;\",\n    10803: \"&smashp;\",\n    10804: \"&lotimes;\",\n    10805: \"&rotimes;\",\n    10806: \"&otimesas;\",\n    10807: \"&Otimes;\",\n    10808: \"&odiv;\",\n    10809: \"&triplus;\",\n    10810: \"&triminus;\",\n    10811: \"&tritime;\",\n    10812: \"&iprod;\",\n    10815: \"&amalg;\",\n    10816: \"&capdot;\",\n    10818: \"&ncup;\",\n    10819: \"&ncap;\",\n    10820: \"&capand;\",\n    10821: \"&cupor;\",\n    10822: \"&cupcap;\",\n    10823: \"&capcup;\",\n    10824: \"&cupbrcap;\",\n    10825: \"&capbrcup;\",\n    10826: \"&cupcup;\",\n    10827: \"&capcap;\",\n    10828: \"&ccups;\",\n    10829: \"&ccaps;\",\n    10832: \"&ccupssm;\",\n    10835: \"&And;\",\n    10836: \"&Or;\",\n    10837: \"&andand;\",\n    10838: \"&oror;\",\n    10839: \"&orslope;\",\n    10840: \"&andslope;\",\n    10842: \"&andv;\",\n    10843: \"&orv;\",\n    10844: \"&andd;\",\n    10845: \"&ord;\",\n    10847: \"&wedbar;\",\n    10854: \"&sdote;\",\n    10858: \"&simdot;\",\n    10861: \"&congdot;\",\n    10862: \"&easter;\",\n    10863: \"&apacir;\",\n    10864: \"&apE;\",\n    10865: \"&eplus;\",\n    10866: \"&pluse;\",\n    10867: \"&Esim;\",\n    10868: \"&Colone;\",\n    10869: \"&Equal;\",\n    10871: \"&eDDot;\",\n    10872: \"&equivDD;\",\n    10873: \"&ltcir;\",\n    10874: \"&gtcir;\",\n    10875: \"&ltquest;\",\n    10876: \"&gtquest;\",\n    10877: \"&les;\",\n    10878: \"&ges;\",\n    10879: \"&lesdot;\",\n    10880: \"&gesdot;\",\n    10881: \"&lesdoto;\",\n    10882: \"&gesdoto;\",\n    10883: \"&lesdotor;\",\n    10884: \"&gesdotol;\",\n    10885: \"&lap;\",\n    10886: \"&gap;\",\n    10887: \"&lne;\",\n    10888: \"&gne;\",\n    10889: \"&lnap;\",\n    10890: \"&gnap;\",\n    10891: \"&lEg;\",\n    10892: \"&gEl;\",\n    10893: \"&lsime;\",\n    10894: \"&gsime;\",\n    10895: \"&lsimg;\",\n    10896: \"&gsiml;\",\n    10897: \"&lgE;\",\n    10898: \"&glE;\",\n    10899: \"&lesges;\",\n    10900: \"&gesles;\",\n    10901: \"&els;\",\n    10902: \"&egs;\",\n    10903: \"&elsdot;\",\n    10904: \"&egsdot;\",\n    10905: \"&el;\",\n    10906: \"&eg;\",\n    10909: \"&siml;\",\n    10910: \"&simg;\",\n    10911: \"&simlE;\",\n    10912: \"&simgE;\",\n    10913: \"&LessLess;\",\n    10914: \"&GreaterGreater;\",\n    10916: \"&glj;\",\n    10917: \"&gla;\",\n    10918: \"&ltcc;\",\n    10919: \"&gtcc;\",\n    10920: \"&lescc;\",\n    10921: \"&gescc;\",\n    10922: \"&smt;\",\n    10923: \"&lat;\",\n    10924: \"&smte;\",\n    10925: \"&late;\",\n    10926: \"&bumpE;\",\n    10927: \"&pre;\",\n    10928: \"&sce;\",\n    10931: \"&prE;\",\n    10932: \"&scE;\",\n    10933: \"&prnE;\",\n    10934: \"&scnE;\",\n    10935: \"&prap;\",\n    10936: \"&scap;\",\n    10937: \"&prnap;\",\n    10938: \"&scnap;\",\n    10939: \"&Pr;\",\n    10940: \"&Sc;\",\n    10941: \"&subdot;\",\n    10942: \"&supdot;\",\n    10943: \"&subplus;\",\n    10944: \"&supplus;\",\n    10945: \"&submult;\",\n    10946: \"&supmult;\",\n    10947: \"&subedot;\",\n    10948: \"&supedot;\",\n    10949: \"&subE;\",\n    10950: \"&supE;\",\n    10951: \"&subsim;\",\n    10952: \"&supsim;\",\n    10955: \"&subnE;\",\n    10956: \"&supnE;\",\n    10959: \"&csub;\",\n    10960: \"&csup;\",\n    10961: \"&csube;\",\n    10962: \"&csupe;\",\n    10963: \"&subsup;\",\n    10964: \"&supsub;\",\n    10965: \"&subsub;\",\n    10966: \"&supsup;\",\n    10967: \"&suphsub;\",\n    10968: \"&supdsub;\",\n    10969: \"&forkv;\",\n    10970: \"&topfork;\",\n    10971: \"&mlcp;\",\n    10980: \"&Dashv;\",\n    10982: \"&Vdashl;\",\n    10983: \"&Barv;\",\n    10984: \"&vBar;\",\n    10985: \"&vBarv;\",\n    10987: \"&Vbar;\",\n    10988: \"&Not;\",\n    10989: \"&bNot;\",\n    10990: \"&rnmid;\",\n    10991: \"&cirmid;\",\n    10992: \"&midcir;\",\n    10993: \"&topcir;\",\n    10994: \"&nhpar;\",\n    10995: \"&parsim;\",\n    11005: \"&parsl;\",\n    64256: \"&fflig;\",\n    64257: \"&filig;\",\n    64258: \"&fllig;\",\n    64259: \"&ffilig;\",\n    64260: \"&ffllig;\",\n    119964: \"&Ascr;\",\n    119966: \"&Cscr;\",\n    119967: \"&Dscr;\",\n    119970: \"&Gscr;\",\n    119973: \"&Jscr;\",\n    119974: \"&Kscr;\",\n    119977: \"&Nscr;\",\n    119978: \"&Oscr;\",\n    119979: \"&Pscr;\",\n    119980: \"&Qscr;\",\n    119982: \"&Sscr;\",\n    119983: \"&Tscr;\",\n    119984: \"&Uscr;\",\n    119985: \"&Vscr;\",\n    119986: \"&Wscr;\",\n    119987: \"&Xscr;\",\n    119988: \"&Yscr;\",\n    119989: \"&Zscr;\",\n    119990: \"&ascr;\",\n    119991: \"&bscr;\",\n    119992: \"&cscr;\",\n    119993: \"&dscr;\",\n    119995: \"&fscr;\",\n    119997: \"&hscr;\",\n    119998: \"&iscr;\",\n    119999: \"&jscr;\",\n    120000: \"&kscr;\",\n    120001: \"&lscr;\",\n    120002: \"&mscr;\",\n    120003: \"&nscr;\",\n    120005: \"&pscr;\",\n    120006: \"&qscr;\",\n    120007: \"&rscr;\",\n    120008: \"&sscr;\",\n    120009: \"&tscr;\",\n    120010: \"&uscr;\",\n    120011: \"&vscr;\",\n    120012: \"&wscr;\",\n    120013: \"&xscr;\",\n    120014: \"&yscr;\",\n    120015: \"&zscr;\",\n    120068: \"&Afr;\",\n    120069: \"&Bfr;\",\n    120071: \"&Dfr;\",\n    120072: \"&Efr;\",\n    120073: \"&Ffr;\",\n    120074: \"&Gfr;\",\n    120077: \"&Jfr;\",\n    120078: \"&Kfr;\",\n    120079: \"&Lfr;\",\n    120080: \"&Mfr;\",\n    120081: \"&Nfr;\",\n    120082: \"&Ofr;\",\n    120083: \"&Pfr;\",\n    120084: \"&Qfr;\",\n    120086: \"&Sfr;\",\n    120087: \"&Tfr;\",\n    120088: \"&Ufr;\",\n    120089: \"&Vfr;\",\n    120090: \"&Wfr;\",\n    120091: \"&Xfr;\",\n    120092: \"&Yfr;\",\n    120094: \"&afr;\",\n    120095: \"&bfr;\",\n    120096: \"&cfr;\",\n    120097: \"&dfr;\",\n    120098: \"&efr;\",\n    120099: \"&ffr;\",\n    120100: \"&gfr;\",\n    120101: \"&hfr;\",\n    120102: \"&ifr;\",\n    120103: \"&jfr;\",\n    120104: \"&kfr;\",\n    120105: \"&lfr;\",\n    120106: \"&mfr;\",\n    120107: \"&nfr;\",\n    120108: \"&ofr;\",\n    120109: \"&pfr;\",\n    120110: \"&qfr;\",\n    120111: \"&rfr;\",\n    120112: \"&sfr;\",\n    120113: \"&tfr;\",\n    120114: \"&ufr;\",\n    120115: \"&vfr;\",\n    120116: \"&wfr;\",\n    120117: \"&xfr;\",\n    120118: \"&yfr;\",\n    120119: \"&zfr;\",\n    120120: \"&Aopf;\",\n    120121: \"&Bopf;\",\n    120123: \"&Dopf;\",\n    120124: \"&Eopf;\",\n    120125: \"&Fopf;\",\n    120126: \"&Gopf;\",\n    120128: \"&Iopf;\",\n    120129: \"&Jopf;\",\n    120130: \"&Kopf;\",\n    120131: \"&Lopf;\",\n    120132: \"&Mopf;\",\n    120134: \"&Oopf;\",\n    120138: \"&Sopf;\",\n    120139: \"&Topf;\",\n    120140: \"&Uopf;\",\n    120141: \"&Vopf;\",\n    120142: \"&Wopf;\",\n    120143: \"&Xopf;\",\n    120144: \"&Yopf;\",\n    120146: \"&aopf;\",\n    120147: \"&bopf;\",\n    120148: \"&copf;\",\n    120149: \"&dopf;\",\n    120150: \"&eopf;\",\n    120151: \"&fopf;\",\n    120152: \"&gopf;\",\n    120153: \"&hopf;\",\n    120154: \"&iopf;\",\n    120155: \"&jopf;\",\n    120156: \"&kopf;\",\n    120157: \"&lopf;\",\n    120158: \"&mopf;\",\n    120159: \"&nopf;\",\n    120160: \"&oopf;\",\n    120161: \"&popf;\",\n    120162: \"&qopf;\",\n    120163: \"&ropf;\",\n    120164: \"&sopf;\",\n    120165: \"&topf;\",\n    120166: \"&uopf;\",\n    120167: \"&vopf;\",\n    120168: \"&wopf;\",\n    120169: \"&xopf;\",\n    120170: \"&yopf;\",\n    120171: \"&zopf;\"\n};\n\nexport default ToHTMLEntity;\n"
  },
  {
    "path": "src/core/operations/ToHex.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {toHex, TO_HEX_DELIM_OPTIONS} from \"../lib/Hex.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * To Hex operation\n */\nclass ToHex extends Operation {\n\n    /**\n     * ToHex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Hex\";\n        this.module = \"Default\";\n        this.description = \"Converts the input string to hexadecimal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Hexadecimal\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: TO_HEX_DELIM_OPTIONS\n            },\n            {\n                name: \"Bytes per line\",\n                type: \"number\",\n                value: 0\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        let delim, comma;\n        if (args[0] === \"0x with comma\") {\n            delim = \"0x\";\n            comma = \",\";\n        } else {\n            delim = Utils.charRep(args[0] || \"Space\");\n        }\n        const lineSize = args[1];\n\n        return toHex(new Uint8Array(input), delim, 2, comma, lineSize);\n    }\n\n    /**\n     * Highlight to Hex\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        let delim, commaLen = 0;\n        if (args[0] === \"0x with comma\") {\n            delim = \"0x\";\n            commaLen = 1;\n        } else {\n            delim = Utils.charRep(args[0] || \"Space\");\n        }\n\n        const lineSize = args[1],\n            len = delim.length + commaLen;\n\n        const countLF = function(p) {\n            // Count the number of LFs from 0 upto p\n            return (p / lineSize | 0) - (p >= lineSize && p % lineSize === 0);\n        };\n\n        pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start);\n        pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end);\n\n        // if the delimiters are not prepended, trim the trailing delimiter\n        if (!(delim === \"0x\" || delim === \"\\\\x\")) {\n            pos[0].end -= delim.length;\n        }\n        // if there is comma, trim the trailing comma\n        pos[0].end -= commaLen;\n        return pos;\n    }\n\n    /**\n     * Highlight from Hex\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        let delim, commaLen = 0;\n        if (args[0] === \"0x with comma\") {\n            delim = \"0x\";\n            commaLen = 1;\n        } else {\n            delim = Utils.charRep(args[0] || \"Space\");\n        }\n\n        const lineSize = args[1],\n            len = delim.length + commaLen,\n            width = len + 2;\n\n        const countLF = function(p) {\n            // Count the number of LFs from 0 up to p\n            const lineLength = width * lineSize;\n            return (p / lineLength | 0) - (p >= lineLength && p % lineLength === 0);\n        };\n\n        pos[0].start = pos[0].start === 0 ? 0 : Math.round((pos[0].start - countLF(pos[0].start)) / width);\n        pos[0].end = pos[0].end === 0 ? 0 : Math.ceil((pos[0].end - countLF(pos[0].end)) / width);\n        return pos;\n    }\n}\n\nexport default ToHex;\n"
  },
  {
    "path": "src/core/operations/ToHexContent.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {toHex} from \"../lib/Hex.mjs\";\n\n/**\n * To Hex Content operation\n */\nclass ToHexContent extends Operation {\n\n    /**\n     * ToHexContent constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Hex Content\";\n        this.module = \"Default\";\n        this.description = \"Converts special characters in a string to hexadecimal. This format is used by SNORT for representing hex within ASCII text.<br><br>e.g. <code>foo=bar</code> becomes <code>foo|3d|bar</code>.\";\n        this.infoURL = \"http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Convert\",\n                \"type\": \"option\",\n                \"value\": [\"Only special chars\", \"Only special chars including spaces\", \"All chars\"]\n            },\n            {\n                \"name\": \"Print spaces between bytes\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const convert = args[0];\n        const spaces = args[1];\n        if (convert === \"All chars\") {\n            let result = \"|\" + toHex(input) + \"|\";\n            if (!spaces) result = result.replace(/ /g, \"\");\n            return result;\n        }\n\n        let output = \"\",\n            inHex = false,\n            b;\n        const convertSpaces = convert === \"Only special chars including spaces\";\n        for (let i = 0; i < input.length; i++) {\n            b = input[i];\n            if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) {\n                if (!inHex) {\n                    output += \"|\";\n                    inHex = true;\n                } else if (spaces) output += \" \";\n                output += toHex([b]);\n            } else {\n                if (inHex) {\n                    output += \"|\";\n                    inHex = false;\n                }\n                output += Utils.chr(input[i]);\n            }\n        }\n        if (inHex) output += \"|\";\n        return output;\n    }\n\n}\n\nexport default ToHexContent;\n"
  },
  {
    "path": "src/core/operations/ToHexdump.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * To Hexdump operation\n */\nclass ToHexdump extends Operation {\n\n    /**\n     * ToHexdump constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Hexdump\";\n        this.module = \"Default\";\n        this.description = \"Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.<br><br>The 'UNIX format' argument defines which subset of printable characters are displayed in the preview column.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Hex_dump\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Width\",\n                \"type\": \"number\",\n                \"value\": 16,\n                \"min\": 1\n            },\n            {\n                \"name\": \"Upper case hex\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Include final length\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"UNIX format\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const data = new Uint8Array(input);\n        const [length, upperCase, includeFinalLength, unixFormat] = args;\n        const padding = 2;\n\n        if (length < 1 || Math.round(length) !== length)\n            throw new OperationError(\"Width must be a positive integer\");\n\n        const lines = [];\n        for (let i = 0; i < data.length; i += length) {\n            let lineNo = Utils.hex(i, 8);\n\n            const buff = data.slice(i, i+length);\n            const hex = [];\n            buff.forEach(b => hex.push(Utils.hex(b, padding)));\n            let hexStr = hex.join(\" \").padEnd(length*(padding+1), \" \");\n\n            const ascii = Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat);\n            const asciiStr = ascii.padEnd(buff.length, \" \");\n\n            if (upperCase) {\n                hexStr = hexStr.toUpperCase();\n                lineNo = lineNo.toUpperCase();\n            }\n\n            lines.push(`${lineNo}  ${hexStr} |${asciiStr}|`);\n\n\n            if (includeFinalLength && i+buff.length === data.length) {\n                lines.push(Utils.hex(i+buff.length, 8));\n            }\n        }\n\n        return lines.join(\"\\n\");\n    }\n\n    /**\n     * Highlight To Hexdump\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        // Calculate overall selection\n        const w = args[0] || 16,\n            width = 14 + (w*4);\n        let line = Math.floor(pos[0].start / w),\n            offset = pos[0].start % w,\n            start = 0,\n            end = 0;\n\n        pos[0].start = line*width + 10 + offset*3;\n\n        line = Math.floor(pos[0].end / w);\n        offset = pos[0].end % w;\n        if (offset === 0) {\n            line--;\n            offset = w;\n        }\n        pos[0].end = line*width + 10 + offset*3 - 1;\n\n        // Set up multiple selections for bytes\n        let startLineNum = Math.floor(pos[0].start / width);\n        const endLineNum = Math.floor(pos[0].end / width);\n\n        if (startLineNum === endLineNum) {\n            pos.push(pos[0]);\n        } else {\n            start = pos[0].start;\n            end = (startLineNum+1) * width - w - 5;\n            pos.push({ start: start, end: end });\n            while (end < pos[0].end) {\n                startLineNum++;\n                start = startLineNum * width + 10;\n                end = (startLineNum+1) * width - w - 5;\n                if (end > pos[0].end) end = pos[0].end;\n                pos.push({ start: start, end: end });\n            }\n        }\n\n        // Set up multiple selections for ASCII\n        const len = pos.length;\n        let lineNum = 0;\n        start = 0;\n        end = 0;\n        for (let i = 1; i < len; i++) {\n            lineNum = Math.floor(pos[i].start / width);\n            start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width);\n            end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width);\n            pos.push({ start: start, end: end });\n        }\n        return pos;\n    }\n\n    /**\n     * Highlight To Hexdump in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        const w = args[0] || 16;\n        const width = 14 + (w*4);\n\n        let line = Math.floor(pos[0].start / width);\n        let offset = pos[0].start % width;\n\n        if (offset < 10) { // In line number section\n            pos[0].start = line*w;\n        } else if (offset > 10+(w*3)) { // In ASCII section\n            pos[0].start = (line+1)*w;\n        } else { // In byte section\n            pos[0].start = line*w + Math.floor((offset-10)/3);\n        }\n\n        line = Math.floor(pos[0].end / width);\n        offset = pos[0].end % width;\n\n        if (offset < 10) { // In line number section\n            pos[0].end = line*w;\n        } else if (offset > 10+(w*3)) { // In ASCII section\n            pos[0].end = (line+1)*w;\n        } else { // In byte section\n            pos[0].end = line*w + Math.ceil((offset-10)/3);\n        }\n\n        return pos;\n    }\n\n}\n\nexport default ToHexdump;\n"
  },
  {
    "path": "src/core/operations/ToKebabCase.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport kebabCase from \"lodash/kebabCase.js\";\nimport Operation from \"../Operation.mjs\";\nimport { replaceVariableNames } from \"../lib/Code.mjs\";\n\n/**\n * To Kebab case operation\n */\nclass ToKebabCase extends Operation {\n\n    /**\n     * ToKebabCase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Kebab case\";\n        this.module = \"Code\";\n        this.description = \"Converts the input string to kebab case.\\n<br><br>\\nKebab case is all lower case with dashes as word boundaries.\\n<br><br>\\ne.g. this-is-kebab-case\\n<br><br>\\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Kebab_case\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Attempt to be context aware\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const smart = args[0];\n\n        if (smart) {\n            return replaceVariableNames(input, kebabCase);\n        } else {\n            return kebabCase(input);\n        }\n    }\n\n}\n\nexport default ToKebabCase;\n"
  },
  {
    "path": "src/core/operations/ToLowerCase.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * To Lower case operation\n */\nclass ToLowerCase extends Operation {\n\n    /**\n     * ToLowerCase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Lower case\";\n        this.module = \"Default\";\n        this.description = \"Converts every character in the input to lower case.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return input.toLowerCase();\n    }\n\n    /**\n     * Highlight To Lower case\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight To Lower case in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default ToLowerCase;\n"
  },
  {
    "path": "src/core/operations/ToMessagePack.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport notepack from \"notepack.io\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * To MessagePack operation\n */\nclass ToMessagePack extends Operation {\n\n    /**\n     * ToMessagePack constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To MessagePack\";\n        this.module = \"Code\";\n        this.description = \"Converts JSON to MessagePack encoded byte buffer. MessagePack is a computer data interchange format. It is a binary form for representing simple data structures like arrays and associative arrays.\";\n        this.infoURL = \"https://wikipedia.org/wiki/MessagePack\";\n        this.inputType = \"JSON\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [];\n    }\n\n    /**\n     * @param {JSON} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        try {\n            if (isWorkerEnvironment()) {\n                return notepack.encode(input);\n            } else {\n                const res = notepack.encode(input);\n                // Safely convert from Node Buffer to ArrayBuffer using the correct view of the data\n                return (new Uint8Array(res)).buffer;\n            }\n        } catch (err) {\n            throw new OperationError(`Could not encode JSON to MessagePack: ${err}`);\n        }\n    }\n\n}\n\nexport default ToMessagePack;\n"
  },
  {
    "path": "src/core/operations/ToModhex.mjs",
    "content": "/**\n * @author linuxgemini [ilteris@asenkron.com.tr]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport { TO_MODHEX_DELIM_OPTIONS, toModhex } from \"../lib/Modhex.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * To Modhex operation\n */\nclass ToModhex extends Operation {\n\n    /**\n     * ToModhex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Modhex\";\n        this.module = \"Default\";\n        this.description = \"Converts the input string to modhex bytes separated by the specified delimiter.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/YubiKey#ModHex\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: TO_MODHEX_DELIM_OPTIONS\n            },\n            {\n                name: \"Bytes per line\",\n                type: \"number\",\n                value: 0\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0]);\n        const lineSize = args[1];\n\n        return toModhex(new Uint8Array(input), delim, 2, \"\", lineSize);\n    }\n}\n\nexport default ToModhex;\n"
  },
  {
    "path": "src/core/operations/ToMorseCode.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * To Morse Code operation\n */\nclass ToMorseCode extends Operation {\n\n    /**\n     * ToMorseCode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Morse Code\";\n        this.module = \"Default\";\n        this.description = \"Translates alphanumeric characters into International Morse Code.<br><br>Ignores non-Morse characters.<br><br>e.g. <code>SOS</code> becomes <code>... --- ...</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Morse_code\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Format options\",\n                \"type\": \"option\",\n                \"value\": [\"-/.\", \"_/.\", \"Dash/Dot\", \"DASH/DOT\", \"dash/dot\"]\n            },\n            {\n                \"name\": \"Letter delimiter\",\n                \"type\": \"option\",\n                \"value\": LETTER_DELIM_OPTIONS\n            },\n            {\n                \"name\": \"Word delimiter\",\n                \"type\": \"option\",\n                \"value\": WORD_DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const format = args[0].split(\"/\");\n        const dash = format[0];\n        const dot = format[1];\n\n        const letterDelim = Utils.charRep(args[1]);\n        const wordDelim = Utils.charRep(args[2]);\n\n        input = input.split(/\\r?\\n/);\n        input = Array.prototype.map.call(input, function(line) {\n            let words = line.split(/ +/);\n            words = Array.prototype.map.call(words, function(word) {\n                const letters = Array.prototype.map.call(word, function(character) {\n                    const letter = character.toUpperCase();\n                    if (typeof MORSE_TABLE[letter] == \"undefined\") {\n                        return \"\";\n                    }\n\n                    return MORSE_TABLE[letter];\n                });\n\n                return letters.join(\"<ld>\");\n            });\n            line = words.join(\"<wd>\");\n            return line;\n        });\n        input = input.join(\"\\n\");\n\n        input = input.replace(\n            /<dash>|<dot>|<ld>|<wd>/g,\n            function(match) {\n                switch (match) {\n                    case \"<dash>\": return dash;\n                    case \"<dot>\": return dot;\n                    case \"<ld>\": return letterDelim;\n                    case \"<wd>\": return wordDelim;\n                }\n            }\n        );\n\n        return input;\n    }\n\n}\n\nconst MORSE_TABLE = {\n    \"A\": \"<dot><dash>\",\n    \"B\": \"<dash><dot><dot><dot>\",\n    \"C\": \"<dash><dot><dash><dot>\",\n    \"D\": \"<dash><dot><dot>\",\n    \"E\": \"<dot>\",\n    \"F\": \"<dot><dot><dash><dot>\",\n    \"G\": \"<dash><dash><dot>\",\n    \"H\": \"<dot><dot><dot><dot>\",\n    \"I\": \"<dot><dot>\",\n    \"J\": \"<dot><dash><dash><dash>\",\n    \"K\": \"<dash><dot><dash>\",\n    \"L\": \"<dot><dash><dot><dot>\",\n    \"M\": \"<dash><dash>\",\n    \"N\": \"<dash><dot>\",\n    \"O\": \"<dash><dash><dash>\",\n    \"P\": \"<dot><dash><dash><dot>\",\n    \"Q\": \"<dash><dash><dot><dash>\",\n    \"R\": \"<dot><dash><dot>\",\n    \"S\": \"<dot><dot><dot>\",\n    \"T\": \"<dash>\",\n    \"U\": \"<dot><dot><dash>\",\n    \"V\": \"<dot><dot><dot><dash>\",\n    \"W\": \"<dot><dash><dash>\",\n    \"X\": \"<dash><dot><dot><dash>\",\n    \"Y\": \"<dash><dot><dash><dash>\",\n    \"Z\": \"<dash><dash><dot><dot>\",\n    \"1\": \"<dot><dash><dash><dash><dash>\",\n    \"2\": \"<dot><dot><dash><dash><dash>\",\n    \"3\": \"<dot><dot><dot><dash><dash>\",\n    \"4\": \"<dot><dot><dot><dot><dash>\",\n    \"5\": \"<dot><dot><dot><dot><dot>\",\n    \"6\": \"<dash><dot><dot><dot><dot>\",\n    \"7\": \"<dash><dash><dot><dot><dot>\",\n    \"8\": \"<dash><dash><dash><dot><dot>\",\n    \"9\": \"<dash><dash><dash><dash><dot>\",\n    \"0\": \"<dash><dash><dash><dash><dash>\",\n    \".\": \"<dot><dash><dot><dash><dot><dash>\",\n    \",\": \"<dash><dash><dot><dot><dash><dash>\",\n    \":\": \"<dash><dash><dash><dot><dot><dot>\",\n    \";\": \"<dash><dot><dash><dot><dash><dot>\",\n    \"!\": \"<dash><dot><dash><dot><dash><dash>\",\n    \"?\": \"<dot><dot><dash><dash><dot><dot>\",\n    \"'\": \"<dot><dash><dash><dash><dash><dot>\",\n    \"\\\"\": \"<dot><dash><dot><dot><dash><dot>\",\n    \"/\": \"<dash><dot><dot><dash><dot>\",\n    \"-\": \"<dash><dot><dot><dot><dot><dash>\",\n    \"+\": \"<dot><dash><dot><dash><dot>\",\n    \"(\": \"<dash><dot><dash><dash><dot>\",\n    \")\": \"<dash><dot><dash><dash><dot><dash>\",\n    \"@\": \"<dot><dash><dash><dot><dash><dot>\",\n    \"=\": \"<dash><dot><dot><dot><dash>\",\n    \"&\": \"<dot><dash><dot><dot><dot>\",\n    \"_\": \"<dot><dot><dash><dash><dot><dash>\",\n    \"$\": \"<dot><dot><dot><dash><dot><dot><dash>\",\n    \" \": \"<dot><dot><dot><dot><dot><dot><dot>\"\n};\n\nexport default ToMorseCode;\n"
  },
  {
    "path": "src/core/operations/ToOctal.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n\n/**\n * To Octal operation\n */\nclass ToOctal extends Operation {\n\n    /**\n     * ToOctal constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Octal\";\n        this.module = \"Default\";\n        this.description = \"Converts the input string to octal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>316 223 316 265 316 271 316 254 40 317 203 316 277 317 205</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Octal\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Delimiter\",\n                \"type\": \"option\",\n                \"value\": DELIM_OPTIONS\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0] || \"Space\");\n        return input.map(val => val.toString(8)).join(delim);\n    }\n\n}\n\nexport default ToOctal;\n"
  },
  {
    "path": "src/core/operations/ToPunycode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport punycode from \"punycode\";\n\n/**\n * To Punycode operation\n */\nclass ToPunycode extends Operation {\n\n    /**\n     * ToPunycode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Punycode\";\n        this.module = \"Encodings\";\n        this.description = \"Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.<br><br>e.g. <code>m\\xfcnchen</code> encodes to <code>mnchen-3ya</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Punycode\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Internationalised domain name\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const idn = args[0];\n\n        if (idn) {\n            return punycode.toASCII(input);\n        } else {\n            return punycode.encode(input);\n        }\n    }\n\n}\n\nexport default ToPunycode;\n"
  },
  {
    "path": "src/core/operations/ToQuotedPrintable.mjs",
    "content": "/**\n * Some parts taken from mimelib (http://github.com/andris9/mimelib)\n * @author Andris Reinman\n * @license MIT\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * To Quoted Printable operation\n */\nclass ToQuotedPrintable extends Operation {\n\n    /**\n     * ToQuotedPrintable constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Quoted Printable\";\n        this.module = \"Default\";\n        this.description = \"Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in email.<br><br>QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Quoted-printable\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        let mimeEncodedStr = this.mimeEncode(input);\n\n        // fix line breaks\n        mimeEncodedStr = mimeEncodedStr.replace(/\\r?\\n|\\r/g, function() {\n            return \"\\r\\n\";\n        }).replace(/[\\t ]+$/gm, function(spaces) {\n            return spaces.replace(/ /g, \"=20\").replace(/\\t/g, \"=09\");\n        });\n\n        return this._addSoftLinebreaks(mimeEncodedStr, \"qp\");\n    }\n\n\n    /** @license\n    ========================================================================\n      mimelib: http://github.com/andris9/mimelib\n      Copyright (c) 2011-2012 Andris Reinman\n\n      Permission is hereby granted, free of charge, to any person obtaining a copy\n      of this software and associated documentation files (the \"Software\"), to deal\n      in the Software without restriction, including without limitation the rights\n      to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n      copies of the Software, and to permit persons to whom the Software is\n      furnished to do so, subject to the following conditions:\n\n      THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n      OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n      SOFTWARE.\n    */\n\n    /**\n     * Encodes mime data.\n     *\n     * @param {byteArray|Uint8Array} buffer\n     * @returns {string}\n     */\n    mimeEncode(buffer) {\n        const ranges = [\n            [0x09],\n            [0x0A],\n            [0x0D],\n            [0x20],\n            [0x21],\n            [0x23, 0x3C],\n            [0x3E],\n            [0x40, 0x5E],\n            [0x60, 0x7E]\n        ];\n        let result = \"\";\n\n        for (let i = 0, len = buffer.length; i < len; i++) {\n            if (this._checkRanges(buffer[i], ranges)) {\n                result += String.fromCharCode(buffer[i]);\n                continue;\n            }\n            result += \"=\" + (buffer[i] < 0x10 ? \"0\" : \"\") + buffer[i].toString(16).toUpperCase();\n        }\n\n        return result;\n    }\n\n    /**\n     * Checks if a given number falls within a given set of ranges.\n     *\n     * @private\n     * @param {number} nr\n     * @param {byteArray[]} ranges\n     * @returns {boolean}\n     */\n    _checkRanges(nr, ranges) {\n        for (let i = ranges.length - 1; i >= 0; i--) {\n            if (!ranges[i].length)\n                continue;\n            if (ranges[i].length === 1 && nr === ranges[i][0])\n                return true;\n            if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1])\n                return true;\n        }\n        return false;\n    }\n\n    /**\n     * Adds soft line breaks to a string.\n     * Lines can't be longer that 76 + <CR><LF> = 78 bytes\n     * http://tools.ietf.org/html/rfc2045#section-6.7\n     *\n     * @private\n     * @param {string} str\n     * @param {string} encoding\n     * @returns {string}\n     */\n    _addSoftLinebreaks(str, encoding) {\n        const lineLengthMax = 76;\n\n        encoding = (encoding || \"base64\").toString().toLowerCase().trim();\n\n        if (encoding === \"qp\") {\n            return this._addQPSoftLinebreaks(str, lineLengthMax);\n        } else {\n            return this._addBase64SoftLinebreaks(str, lineLengthMax);\n        }\n    }\n\n    /**\n     * Adds soft line breaks to a base64 string.\n     *\n     * @private\n     * @param {string} base64EncodedStr\n     * @param {number} lineLengthMax\n     * @returns {string}\n     */\n    _addBase64SoftLinebreaks(base64EncodedStr, lineLengthMax) {\n        base64EncodedStr = (base64EncodedStr || \"\").toString().trim();\n        return base64EncodedStr.replace(new RegExp(\".{\" + lineLengthMax + \"}\", \"g\"), \"$&\\r\\n\").trim();\n    }\n\n    /**\n     * Adds soft line breaks to a quoted printable string.\n     *\n     * @private\n     * @param {string} mimeEncodedStr\n     * @param {number} lineLengthMax\n     * @returns {string}\n     */\n    _addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) {\n        const len = mimeEncodedStr.length,\n            lineMargin = Math.floor(lineLengthMax / 3);\n        let pos = 0,\n            match, code, line,\n            result = \"\";\n\n        // insert soft linebreaks where needed\n        while (pos < len) {\n            line = mimeEncodedStr.substr(pos, lineLengthMax);\n            if ((match = line.match(/\\r\\n/))) {\n                line = line.substr(0, match.index + match[0].length);\n                result += line;\n                pos += line.length;\n                continue;\n            }\n\n            if (line.substr(-1) === \"\\n\") {\n                // nothing to change here\n                result += line;\n                pos += line.length;\n                continue;\n            } else if ((match = line.substr(-lineMargin).match(/\\n.*?$/))) {\n                // truncate to nearest line break\n                line = line.substr(0, line.length - (match[0].length - 1));\n                result += line;\n                pos += line.length;\n                continue;\n            } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \\t.,!?][^ \\t.,!?]*$/))) {\n                // truncate to nearest space\n                line = line.substr(0, line.length - (match[0].length - 1));\n            } else if (line.substr(-1) === \"\\r\") {\n                line = line.substr(0, line.length - 1);\n            } else {\n                if (line.match(/=[\\da-f]{0,2}$/i)) {\n\n                    // push incomplete encoding sequences to the next line\n                    if ((match = line.match(/=[\\da-f]{0,1}$/i))) {\n                        line = line.substr(0, line.length - match[0].length);\n                    }\n\n                    // ensure that utf-8 sequences are not split\n                    while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\\da-f]{2}){1,4}$/i) && (match = line.match(/=[\\da-f]{2}$/ig))) {\n                        code = parseInt(match[0].substr(1, 2), 16);\n                        if (code < 128) {\n                            break;\n                        }\n\n                        line = line.substr(0, line.length - 3);\n\n                        if (code >= 0xC0) {\n                            break;\n                        }\n                    }\n\n                }\n            }\n\n            if (pos + line.length < len && line.substr(-1) !== \"\\n\") {\n                if (line.length === 76 && line.match(/=[\\da-f]{2}$/i)) {\n                    line = line.substr(0, line.length - 3);\n                } else if (line.length === 76) {\n                    line = line.substr(0, line.length - 1);\n                }\n                pos += line.length;\n                line += \"=\\r\\n\";\n            } else {\n                pos += line.length;\n            }\n\n            result += line;\n        }\n\n        return result;\n    }\n\n}\n\nexport default ToQuotedPrintable;\n"
  },
  {
    "path": "src/core/operations/ToSnakeCase.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport snakeCase from \"lodash/snakeCase.js\";\nimport Operation from \"../Operation.mjs\";\nimport { replaceVariableNames } from \"../lib/Code.mjs\";\n\n/**\n * To Snake case operation\n */\nclass ToSnakeCase extends Operation {\n\n    /**\n     * ToSnakeCase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Snake case\";\n        this.module = \"Code\";\n        this.description = \"Converts the input string to snake case.\\n<br><br>\\nSnake case is all lower case with underscores as word boundaries.\\n<br><br>\\ne.g. this_is_snake_case\\n<br><br>\\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Snake_case\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Attempt to be context aware\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const smart = args[0];\n\n        if (smart) {\n            return replaceVariableNames(input, snakeCase);\n        } else {\n            return snakeCase(input);\n        }\n    }\n}\n\nexport default ToSnakeCase;\n"
  },
  {
    "path": "src/core/operations/ToTable.mjs",
    "content": "/**\n * @author Mark Jones [github.com/justanothermark]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * To Table operation\n */\nclass ToTable extends Operation {\n\n    /**\n     * ToTable constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Table\";\n        this.module = \"Default\";\n        this.description = \"Data can be split on different characters and rendered as an HTML, ASCII or Markdown table with an optional header row.<br><br>Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to <code>\\\\t</code> to support TSV (Tab Separated Values) or <code>|</code> for PSV (Pipe Separated Values).<br><br>You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Comma-separated_values\";\n        this.inputType = \"string\";\n        this.outputType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Cell delimiters\",\n                \"type\": \"binaryShortString\",\n                \"value\": \",\"\n            },\n            {\n                \"name\": \"Row delimiters\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\r\\\\n\"\n            },\n            {\n                \"name\": \"Make first row header\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Format\",\n                \"type\": \"option\",\n                \"value\": [\"ASCII\", \"HTML\", \"Markdown\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {html}\n     */\n    run(input, args) {\n        const [cellDelims, rowDelims, firstRowHeader, format] = args;\n\n        // Process the input into a nested array of elements.\n        const tableData = Utils.parseCSV(Utils.escapeHtml(input), cellDelims.split(\"\"), rowDelims.split(\"\"));\n\n        if (!tableData.length) return \"\";\n\n        // Render the data in the requested format.\n        switch (format) {\n            case \"ASCII\":\n                return asciiOutput(tableData);\n            case \"HTML\":\n                return htmlOutput(tableData);\n            case \"Markdown\":\n                return markdownOutput(tableData);\n            default:\n                return htmlOutput(tableData);\n        }\n\n        /**\n         * Outputs an array of data as an ASCII table.\n         *\n         * @param {string[][]} tableData\n         * @returns {string}\n         */\n        function asciiOutput(tableData) {\n            const horizontalBorder = \"-\";\n            const verticalBorder = \"|\";\n            const crossBorder = \"+\";\n\n            let output = \"\";\n            const longestCells = [];\n\n            // Find longestCells value per column to pad cells equally.\n            tableData.forEach(function(row, index) {\n                row.forEach(function(cell, cellIndex) {\n                    if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) {\n                        longestCells[cellIndex] = cell.length;\n                    }\n                });\n            });\n\n            // Add the top border of the table to the output.\n            output += outputHorizontalBorder(longestCells);\n\n            // If the first row is a header, remove the row from the data and\n            // add it to the output with another horizontal border.\n            if (firstRowHeader) {\n                const row = tableData.shift();\n                output += outputRow(row, longestCells);\n                output += outputHorizontalBorder(longestCells);\n            }\n\n            // Add the rest of the table rows.\n            tableData.forEach(function(row, index) {\n                output += outputRow(row, longestCells);\n            });\n\n            // Close the table with a final horizontal border.\n            output += outputHorizontalBorder(longestCells);\n\n            return output;\n\n            /**\n             * Outputs a row of correctly padded cells.\n             */\n            function outputRow(row, longestCells) {\n                let rowOutput = verticalBorder;\n                row.forEach(function(cell, index) {\n                    rowOutput += \" \" + cell + \" \".repeat(longestCells[index] - cell.length) + \" \" + verticalBorder;\n                });\n                rowOutput += \"\\n\";\n                return rowOutput;\n            }\n\n            /**\n             * Outputs a horizontal border with a different character where\n             * the horizontal border meets a vertical border.\n             */\n            function outputHorizontalBorder(longestCells) {\n                let rowOutput = crossBorder;\n                longestCells.forEach(function(cellLength) {\n                    rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder;\n                });\n                rowOutput += \"\\n\";\n                return rowOutput;\n            }\n        }\n\n        /**\n         * Outputs a table of data as a HTML table.\n         *\n         * @param {string[][]} tableData\n         * @returns {string}\n         */\n        function htmlOutput(tableData) {\n            // Start the HTML output with suitable classes for styling.\n            let output = \"<table class='table table-hover table-sm table-bordered table-nonfluid'>\";\n\n            // If the first row is a header then put it in <thead> with <th> cells.\n            if (firstRowHeader) {\n                const row = tableData.shift();\n                output += \"<thead class='thead-light'>\";\n                output += outputRow(row, \"th\");\n                output += \"</thead>\";\n            }\n\n            // Output the rest of the rows in the <tbody>.\n            output += \"<tbody>\";\n            tableData.forEach(function(row, index) {\n                output += outputRow(row, \"td\");\n            });\n\n            // Close the body and table elements.\n            output += \"</tbody></table>\";\n            return output;\n\n          /**\n           * Outputs a table row.\n           *\n           * @param {string[]} row\n           * @param {string} cellType\n           */\n            function outputRow(row, cellType) {\n                let output = \"<tr>\";\n                row.forEach(function(cell) {\n                    output += \"<\" + cellType + \">\" + cell + \"</\" + cellType + \">\";\n                });\n                output += \"</tr>\";\n                return output;\n            }\n        }\n\n        /**\n         * Outputs an array of data as a Markdown table.\n         *\n         * @param {string[][]} tableData\n         * @returns {string}\n         */\n        function markdownOutput(tableData) {\n            const headerDivider = \"-\";\n            const verticalBorder = \"|\";\n\n            let output = \"\";\n            const longestCells = [];\n\n            // Find longestCells value per column to pad cells equally.\n            tableData.forEach(function(row, index) {\n                row.forEach(function(cell, cellIndex) {\n                    if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) {\n                        longestCells[cellIndex] = cell.length;\n                    }\n                });\n            });\n\n            // Ignoring the checkbox, as current Mardown renderer in CF doesn't handle table without headers\n            const row = tableData.shift();\n            output += outputRow(row, longestCells);\n            let rowOutput = verticalBorder;\n            row.forEach(function(cell, index) {\n                rowOutput += \" \" +  headerDivider.repeat(longestCells[index]) + \" \" + verticalBorder;\n            });\n            output += rowOutput += \"\\n\";\n\n            // Add the rest of the table rows.\n            tableData.forEach(function(row, index) {\n                output += outputRow(row, longestCells);\n            });\n\n            return output;\n\n            /**\n             * Outputs a row of correctly padded cells.\n             */\n            function outputRow(row, longestCells) {\n                let rowOutput = verticalBorder;\n                row.forEach(function(cell, index) {\n                    rowOutput += \" \" + cell + \" \".repeat(longestCells[index] - cell.length) + \" \" + verticalBorder;\n                });\n                rowOutput += \"\\n\";\n                return rowOutput;\n            }\n\n        }\n\n    }\n\n}\n\nexport default ToTable;\n"
  },
  {
    "path": "src/core/operations/ToUNIXTimestamp.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport moment from \"moment-timezone\";\nimport {UNITS} from \"../lib/DateTime.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * To UNIX Timestamp operation\n */\nclass ToUNIXTimestamp extends Operation {\n\n    /**\n     * ToUNIXTimestamp constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To UNIX Timestamp\";\n        this.module = \"Default\";\n        this.description = \"Parses a datetime string in UTC and returns the corresponding UNIX timestamp.<br><br>e.g. <code>Mon 1 January 2001 11:00:00</code> becomes <code>978346800</code><br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Unix_time\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Units\",\n                \"type\": \"option\",\n                \"value\": UNITS\n            },\n            {\n                \"name\": \"Treat as UTC\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Show parsed datetime\",\n                \"type\": \"boolean\",\n                \"value\": true\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     *\n     * @throws {OperationError} if unit unrecognised\n     */\n    run(input, args) {\n        const [units, treatAsUTC, showDateTime] = args,\n            d = treatAsUTC ? moment.utc(input) : moment(input);\n\n        let result = \"\";\n\n        if (units === \"Seconds (s)\") {\n            result = d.unix();\n        } else if (units === \"Milliseconds (ms)\") {\n            result = d.valueOf();\n        } else if (units === \"Microseconds (μs)\") {\n            result = d.valueOf() * 1000;\n        } else if (units === \"Nanoseconds (ns)\") {\n            result = d.valueOf() * 1000000;\n        } else {\n            throw new OperationError(\"Unrecognised unit\");\n        }\n\n        return showDateTime ? `${result} (${d.tz(\"UTC\").format(\"ddd D MMMM YYYY HH:mm:ss\")} UTC)` : result.toString();\n    }\n\n}\n\nexport default ToUNIXTimestamp;\n"
  },
  {
    "path": "src/core/operations/ToUpperCase.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * To Upper case operation\n */\nclass ToUpperCase extends Operation {\n\n    /**\n     * ToUpperCase constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"To Upper case\";\n        this.module = \"Default\";\n        this.description = \"Converts the input string to upper case, optionally limiting scope to only the first character in each word, sentence or paragraph.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Scope\",\n                \"type\": \"option\",\n                \"value\": [\"All\", \"Word\", \"Sentence\", \"Paragraph\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        if (!args || args.length === 0) {\n            throw new OperationError(\"No capitalization scope was provided.\");\n        }\n\n        const scope = args[0];\n\n        if (scope === \"All\") {\n            return input.toUpperCase();\n        }\n\n        const scopeRegex = {\n            \"Word\": /(\\b\\w)/gi,\n            \"Sentence\": /(?:\\.|^)\\s*(\\b\\w)/gi,\n            \"Paragraph\": /(?:\\n|^)\\s*(\\b\\w)/gi\n        }[scope];\n\n        if (scopeRegex === undefined) {\n            throw new OperationError(\"Unrecognized capitalization scope\");\n        }\n\n        // Use the regex to capitalize the input\n        return input.replace(scopeRegex, function(m) {\n            return m.toUpperCase();\n        });\n    }\n\n    /**\n     * Highlight To Upper case\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight To Upper case in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default ToUpperCase;\n"
  },
  {
    "path": "src/core/operations/TranslateDateTimeFormat.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport moment from \"moment-timezone\";\nimport {DATETIME_FORMATS, FORMAT_EXAMPLES} from \"../lib/DateTime.mjs\";\n\n/**\n * Translate DateTime Format operation\n */\nclass TranslateDateTimeFormat extends Operation {\n\n    /**\n     * TranslateDateTimeFormat constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Translate DateTime Format\";\n        this.module = \"Default\";\n        this.description = \"Parses a datetime string in one format and re-writes it in another.<br><br>Run with no input to see the relevant format string examples.\";\n        this.infoURL = \"https://momentjs.com/docs/#/parsing/string-format/\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                \"name\": \"Built in formats\",\n                \"type\": \"populateOption\",\n                \"value\": DATETIME_FORMATS,\n                \"target\": 1\n            },\n            {\n                \"name\": \"Input format string\",\n                \"type\": \"binaryString\",\n                \"value\": \"DD/MM/YYYY HH:mm:ss\"\n            },\n            {\n                \"name\": \"Input timezone\",\n                \"type\": \"option\",\n                \"value\": [\"UTC\"].concat(moment.tz.names())\n            },\n            {\n                \"name\": \"Output format string\",\n                \"type\": \"binaryString\",\n                \"value\": \"dddd Do MMMM YYYY HH:mm:ss Z z\"\n            },\n            {\n                \"name\": \"Output timezone\",\n                \"type\": \"option\",\n                \"value\": [\"UTC\"].concat(moment.tz.names())\n            }\n        ];\n\n        this.invalidFormatMessage = \"Invalid format.\";\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [inputFormat, inputTimezone, outputFormat, outputTimezone] = args.slice(1);\n        let date;\n\n        try {\n            date = moment.tz(input, inputFormat, inputTimezone);\n            if (!date || date.format() === \"Invalid date\") throw Error;\n        } catch (err) {\n            return this.invalidFormatMessage;\n        }\n\n        return date.tz(outputTimezone).format(outputFormat.replace(/[<>]/g, \"\"));\n    }\n\n    /**\n     * @param {string} data\n     * @returns {html}\n     */\n    present(data) {\n        if (data === this.invalidFormatMessage) {\n            return `${data}\\n\\n${FORMAT_EXAMPLES}`;\n        }\n        return Utils.escapeHtml(data);\n    }\n}\n\nexport default TranslateDateTimeFormat;\n"
  },
  {
    "path": "src/core/operations/TripleDESDecrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\n\n/**\n * Triple DES Decrypt operation\n */\nclass TripleDESDecrypt extends Operation {\n\n    /**\n     * TripleDESDecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Triple DES Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Triple_DES\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\", \"CBC/NoPadding\", \"ECB/NoPadding\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            mode = args[2].substring(0, 3),\n            noPadding = args[2].endsWith(\"NoPadding\"),\n            inputType = args[3],\n            outputType = args[4];\n\n        if (key.length !== 24 && key.length !== 16) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nTriple DES uses a key length of 24 bytes (192 bits).`);\n        }\n        if (iv.length !== 8 && mode !== \"ECB\") {\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nTriple DES uses an IV length of 8 bytes (64 bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        const decipher = forge.cipher.createDecipher(\"3DES-\" + mode,\n            key.length === 16 ? key + key.substring(0, 8) : key);\n\n        /* Allow for a \"no padding\" mode */\n        if (noPadding) {\n            decipher.mode.unpad = function(output, options) {\n                return true;\n            };\n        }\n\n        decipher.start({iv: iv});\n        decipher.update(forge.util.createBuffer(input));\n        const result = decipher.finish();\n\n        if (result) {\n            return outputType === \"Hex\" ? decipher.output.toHex() : decipher.output.getBytes();\n        } else {\n            throw new OperationError(\"Unable to decrypt input with these parameters.\");\n        }\n    }\n\n}\n\nexport default TripleDESDecrypt;\n"
  },
  {
    "path": "src/core/operations/TripleDESEncrypt.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport forge from \"node-forge\";\n\n/**\n * Triple DES Encrypt operation\n */\nclass TripleDESEncrypt extends Operation {\n\n    /**\n     * TripleDESEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Triple DES Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Triple_DES\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"IV\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Mode\",\n                \"type\": \"option\",\n                \"value\": [\"CBC\", \"CFB\", \"OFB\", \"CTR\", \"ECB\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteString(args[0].string, args[0].option),\n            iv = Utils.convertToByteArray(args[1].string, args[1].option),\n            mode = args[2],\n            inputType = args[3],\n            outputType = args[4];\n\n        if (key.length !== 24 && key.length !== 16) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes\n\nTriple DES uses a key length of 24 bytes (192 bits).`);\n        }\n        if (iv.length !== 8 && mode !== \"ECB\") {\n            throw new OperationError(`Invalid IV length: ${iv.length} bytes\n\nTriple DES uses an IV length of 8 bytes (64 bits).\nMake sure you have specified the type correctly (e.g. Hex vs UTF8).`);\n        }\n\n        input = Utils.convertToByteString(input, inputType);\n\n        const cipher = forge.cipher.createCipher(\"3DES-\" + mode,\n            key.length === 16 ? key + key.substring(0, 8) : key);\n        cipher.start({iv: iv});\n        cipher.update(forge.util.createBuffer(input));\n        cipher.finish();\n\n        return outputType === \"Hex\" ? cipher.output.toHex() : cipher.output.getBytes();\n    }\n\n}\n\nexport default TripleDESEncrypt;\n"
  },
  {
    "path": "src/core/operations/Typex.mjs",
    "content": "/**\n * Emulation of the Typex machine.\n *\n * Tested against a genuine Typex machine using a variety of inputs\n * and settings to confirm correctness.\n *\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {LETTERS, Reflector} from \"../lib/Enigma.mjs\";\nimport {ROTORS, REFLECTORS, TypexMachine, Plugboard, Rotor} from \"../lib/Typex.mjs\";\n\n/**\n * Typex operation\n */\nclass Typex extends Operation {\n    /**\n     * Typex constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Typex\";\n        this.module = \"Bletchley\";\n        this.description = \"Encipher/decipher with the WW2 Typex machine.<br><br>Typex was originally built by the British Royal Air Force prior to WW2, and is based on the Enigma machine with some improvements made, including using five rotors with more stepping points and interchangeable wiring cores. It was used across the British and Commonwealth militaries. A number of later variants were produced; here we simulate a WW2 era Mark 22 Typex with plugboards for the reflector and input. Typex rotors were changed regularly and none are public: a random example set are provided.<br><br>To configure the reflector plugboard, enter a string of connected pairs of letters in the reflector box, e.g. <code>AB CD EF</code> connects A to B, C to D, and E to F (you'll need to connect every letter). There is also an input plugboard: unlike Enigma's plugboard, it's not restricted to pairs, so it's entered like a rotor (without stepping). To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by <code>&lt;</code> then a list of stepping points.<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Typex\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"1st (left-hand) rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 0\n            },\n            {\n                name: \"1st rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"1st rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"1st rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"2nd rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 1\n            },\n            {\n                name: \"2nd rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"2nd rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"2nd rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"3rd (middle) rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 2\n            },\n            {\n                name: \"3rd rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"3rd rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"3rd rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"4th (static) rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 3\n            },\n            {\n                name: \"4th rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"4th rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"4th rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"5th (right-hand, static) rotor\",\n                type: \"editableOption\",\n                value: ROTORS,\n                defaultIndex: 4\n            },\n            {\n                name: \"5th rotor reversed\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"5th rotor ring setting\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"5th rotor initial value\",\n                type: \"option\",\n                value: LETTERS\n            },\n            {\n                name: \"Reflector\",\n                type: \"editableOption\",\n                value: REFLECTORS\n            },\n            {\n                name: \"Plugboard\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Typex keyboard emulation\",\n                type: \"option\",\n                value: [\"None\", \"Encrypt\", \"Decrypt\"]\n            },\n            {\n                name: \"Strict output\",\n                hint: \"Remove non-alphabet letters and group output\",\n                type: \"boolean\",\n                value: true\n            },\n        ];\n    }\n\n    /**\n     * Helper - for ease of use rotors are specified as a single string; this\n     * method breaks the spec string into wiring and steps parts.\n     *\n     * @param {string} rotor - Rotor specification string.\n     * @param {number} i - For error messages, the number of this rotor.\n     * @returns {string[]}\n     */\n    parseRotorStr(rotor, i) {\n        if (rotor === \"\") {\n            throw new OperationError(`Rotor ${i} must be provided.`);\n        }\n        if (!rotor.includes(\"<\")) {\n            return [rotor, \"\"];\n        }\n        return rotor.split(\"<\", 2);\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const reflectorstr = args[20];\n        const plugboardstr = args[21];\n        const typexKeyboard = args[22];\n        const removeOther = args[23];\n        const rotors = [];\n        for (let i=0; i<5; i++) {\n            const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*4]);\n            rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*4 + 1], args[i*4+2], args[i*4+3]));\n        }\n        // Rotors are handled in reverse\n        rotors.reverse();\n        const reflector = new Reflector(reflectorstr);\n        let plugboardstrMod = plugboardstr;\n        if (plugboardstrMod === \"\") {\n            plugboardstrMod = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n        }\n        const plugboard = new Plugboard(plugboardstrMod);\n        if (removeOther) {\n            if (typexKeyboard === \"Encrypt\") {\n                input = input.replace(/[^A-Za-z0-9 /%£()',.-]/g, \"\");\n            } else {\n                input = input.replace(/[^A-Za-z]/g, \"\");\n            }\n        }\n        const typex = new TypexMachine(rotors, reflector, plugboard, typexKeyboard);\n        let result = typex.crypt(input);\n        if (removeOther && typexKeyboard !== \"Decrypt\") {\n            // Five character cipher groups is traditional\n            result = result.replace(/([A-Z]{5})(?!$)/g, \"$1 \");\n        }\n        return result;\n    }\n\n    /**\n     * Highlight Typex\n     * This is only possible if we're passing through non-alphabet characters.\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        if (args[18] === false) {\n            return pos;\n        }\n    }\n\n    /**\n     * Highlight Typex in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        if (args[18] === false) {\n            return pos;\n        }\n    }\n\n}\n\nexport default Typex;\n"
  },
  {
    "path": "src/core/operations/UNIXTimestampToWindowsFiletime.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport BigNumber from \"bignumber.js\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * UNIX Timestamp to Windows Filetime operation\n */\nclass UNIXTimestampToWindowsFiletime extends Operation {\n\n    /**\n     * UNIXTimestampToWindowsFiletime constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"UNIX Timestamp to Windows Filetime\";\n        this.module = \"Default\";\n        this.description = \"Converts a UNIX timestamp to a Windows Filetime value.<br><br>A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.<br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).<br><br>This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds.\";\n        this.infoURL = \"https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Input units\",\n                \"type\": \"option\",\n                \"value\": [\"Seconds (s)\", \"Milliseconds (ms)\", \"Microseconds (μs)\", \"Nanoseconds (ns)\"]\n            },\n            {\n                \"name\": \"Output format\",\n                \"type\": \"option\",\n                \"value\": [\"Decimal\", \"Hex (big endian)\", \"Hex (little endian)\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [units, format] = args;\n\n        if (!input) return \"\";\n\n        input = new BigNumber(input);\n\n        if (units === \"Seconds (s)\") {\n            input = input.multipliedBy(new BigNumber(\"10000000\"));\n        } else if (units === \"Milliseconds (ms)\") {\n            input = input.multipliedBy(new BigNumber(\"10000\"));\n        } else if (units === \"Microseconds (μs)\") {\n            input = input.multipliedBy(new BigNumber(\"10\"));\n        } else if (units === \"Nanoseconds (ns)\") {\n            input = input.dividedBy(new BigNumber(\"100\"));\n        } else {\n            throw new OperationError(\"Unrecognised unit\");\n        }\n\n        input = input.plus(new BigNumber(\"116444736000000000\"));\n\n        let result;\n        if (format.startsWith(\"Hex\")) {\n            result = input.toString(16);\n        } else {\n            result = input.toFixed();\n        }\n\n        if (format === \"Hex (little endian)\") {\n            // Swap endianness\n            let flipped = \"\";\n            for (let i = result.length - 2; i >= 0; i -= 2) {\n                flipped += result.charAt(i);\n                flipped += result.charAt(i + 1);\n            }\n            if (result.length % 2 !== 0) {\n                flipped += \"0\" + result.charAt(0);\n            }\n            result = flipped;\n        }\n\n        return result;\n    }\n\n}\n\nexport default UNIXTimestampToWindowsFiletime;\n"
  },
  {
    "path": "src/core/operations/URLDecode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * URL Decode operation\n */\nclass URLDecode extends Operation {\n\n    /**\n     * URLDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"URL Decode\";\n        this.module = \"URL\";\n        this.description = \"Converts URI/URL percent-encoded characters back to their raw values.<br><br>e.g. <code>%3d</code> becomes <code>=</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Percent-encoding\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Treat \\\"+\\\" as space\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n        ];\n        this.checks = [\n            {\n                pattern: \".*(?:%[\\\\da-f]{2}.*){4}\",\n                flags: \"i\",\n                args: []\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const plusIsSpace = args[0];\n        const data = plusIsSpace ? input.replace(/\\+/g, \"%20\") : input;\n        try {\n            return decodeURIComponent(data);\n        } catch (err) {\n            return unescape(data);\n        }\n    }\n\n}\n\nexport default URLDecode;\n"
  },
  {
    "path": "src/core/operations/URLEncode.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * URL Encode operation\n */\nclass URLEncode extends Operation {\n\n    /**\n     * URLEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"URL Encode\";\n        this.module = \"URL\";\n        this.description = \"Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.<br><br>e.g. <code>=</code> becomes <code>%3d</code>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Percent-encoding\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Encode all special chars\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const encodeAll = args[0];\n        return encodeAll ? this.encodeAllChars(input) : encodeURI(input);\n    }\n\n    /**\n     * Encode characters in URL outside of encodeURI() function spec\n     *\n     * @param {string} str\n     * @returns {string}\n     */\n    encodeAllChars (str) {\n        // TODO Do this programmatically\n        return encodeURIComponent(str)\n            .replace(/!/g, \"%21\")\n            .replace(/#/g, \"%23\")\n            .replace(/'/g, \"%27\")\n            .replace(/\\(/g, \"%28\")\n            .replace(/\\)/g, \"%29\")\n            .replace(/\\*/g, \"%2A\")\n            .replace(/-/g, \"%2D\")\n            .replace(/\\./g, \"%2E\")\n            .replace(/_/g, \"%5F\")\n            .replace(/~/g, \"%7E\");\n    }\n\n}\n\n\nexport default URLEncode;\n"
  },
  {
    "path": "src/core/operations/UnescapeString.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Unescape string operation\n */\nclass UnescapeString extends Operation {\n\n    /**\n     * UnescapeString constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Unescape string\";\n        this.module = \"Default\";\n        this.description = \"Unescapes characters in a string that have been escaped. For example, <code>Don\\\\'t stop me now</code> becomes <code>Don't stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\\\n</code> (Line feed/newline)</li><li><code>\\\\r</code> (Carriage return)</li><li><code>\\\\t</code> (Horizontal tab)</li><li><code>\\\\b</code> (Backspace)</li><li><code>\\\\f</code> (Form feed)</li><li><code>\\\\nnn</code> (Octal, where n is 0-7)</li><li><code>\\\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\\\\\</code> (Backslash)</li><li><code>\\\\'</code> (Single quote)</li><li><code>\\\\&quot;</code> (Double quote)</li><li><code>\\\\unnnn</code> (Unicode character)</li><li><code>\\\\u{nnnnnn}</code> (Unicode code point)</li></ul>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Escape_sequence\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        return Utils.parseEscapedChars(input);\n    }\n\n}\n\nexport default UnescapeString;\n"
  },
  {
    "path": "src/core/operations/UnescapeUnicodeCharacters.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Unescape Unicode Characters operation\n */\nclass UnescapeUnicodeCharacters extends Operation {\n\n    /**\n     * UnescapeUnicodeCharacters constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Unescape Unicode Characters\";\n        this.module = \"Default\";\n        this.description = \"Converts unicode-escaped character notation back into raw characters.<br><br>Supports the prefixes:<ul><li><code>\\\\u</code></li><li><code>%u</code></li><li><code>U+</code></li></ul>e.g. <code>\\\\u03c3\\\\u03bf\\\\u03c5</code> becomes <code>σου</code>\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Prefix\",\n                \"type\": \"option\",\n                \"value\": [\"\\\\u\", \"%u\", \"U+\"]\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"\\\\\\\\u(?:[\\\\da-f]{4,6})\",\n                flags: \"i\",\n                args: [\"\\\\u\"]\n            },\n            {\n                pattern: \"%u(?:[\\\\da-f]{4,6})\",\n                flags: \"i\",\n                args: [\"%u\"]\n            },\n            {\n                pattern: \"U\\\\+(?:[\\\\da-f]{4,6})\",\n                flags: \"i\",\n                args: [\"U+\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const prefix = prefixToRegex[args[0]],\n            regex = new RegExp(prefix+\"([a-f\\\\d]{4})\", \"ig\");\n        let output = \"\",\n            m,\n            i = 0;\n\n        while ((m = regex.exec(input))) {\n            // Add up to match\n            output += input.slice(i, m.index);\n\n            // Add match\n            output += Utils.chr(parseInt(m[1], 16));\n\n            i = regex.lastIndex;\n        }\n\n        // Add all after final match\n        output += input.slice(i, input.length);\n\n        return output;\n    }\n\n}\n\n/**\n * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex.\n */\nconst prefixToRegex = {\n    \"\\\\u\": \"\\\\\\\\u\",\n    \"%u\": \"%u\",\n    \"U+\": \"U\\\\+\"\n};\n\nexport default UnescapeUnicodeCharacters;\n"
  },
  {
    "path": "src/core/operations/UnicodeTextFormat.mjs",
    "content": "/**\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\n\n/**\n * Unicode Text Format operation\n */\nclass UnicodeTextFormat extends Operation {\n\n    /**\n     * UnicodeTextFormat constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Unicode Text Format\";\n        this.module = \"Default\";\n        this.description = \"Adds Unicode combining characters to change formatting of plaintext.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Combining_character\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                name: \"Underline\",\n                type: \"boolean\",\n                value: \"false\"\n            },\n            {\n                name: \"Strikethrough\",\n                type: \"boolean\",\n                value: \"false\"\n            }\n        ];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        const [underline, strikethrough] = args;\n        let output = input.map(char => [char]);\n        if (strikethrough) {\n            output = output.map(charFormat => {\n                charFormat.push(...Utils.strToUtf8ByteArray(\"\\u0336\"));\n                return charFormat;\n            });\n        }\n        if (underline) {\n            output = output.map(charFormat => {\n                charFormat.push(...Utils.strToUtf8ByteArray(\"\\u0332\"));\n                return charFormat;\n            });\n        }\n        // return output.flat(); - Not supported in Node 10, polyfilled\n        return [].concat(...output);\n    }\n\n}\n\nexport default UnicodeTextFormat;\n"
  },
  {
    "path": "src/core/operations/Unique.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {INPUT_DELIM_OPTIONS} from \"../lib/Delim.mjs\";\n\n/**\n * Unique operation\n */\nclass Unique extends Operation {\n\n    /**\n     * Unique constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Unique\";\n        this.module = \"Default\";\n        this.description = \"Removes duplicate strings from the input.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Delimiter\",\n                type: \"option\",\n                value: INPUT_DELIM_OPTIONS\n            },\n            {\n                name: \"Display count\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const delim = Utils.charRep(args[0]),\n            count = args[1];\n\n        if (count) {\n            const valMap = input.split(delim).reduce((acc, curr) => {\n                if (Object.prototype.hasOwnProperty.call(acc, curr)) {\n                    acc[curr]++;\n                } else {\n                    acc[curr] = 1;\n                }\n                return acc;\n            }, {});\n\n            return Object.keys(valMap).map(val => `${valMap[val]} ${val}`).join(delim);\n        } else {\n            return input.split(delim).unique().join(delim);\n        }\n    }\n\n}\n\nexport default Unique;\n"
  },
  {
    "path": "src/core/operations/Untar.mjs",
    "content": "/**\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport Stream from \"../lib/Stream.mjs\";\n\n/**\n * Untar operation\n */\nclass Untar extends Operation {\n\n    /**\n     * Untar constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Untar\";\n        this.module = \"Compression\";\n        this.description = \"Unpacks a tarball and displays it per file.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Tar_(computing)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"List<File>\";\n        this.presentType = \"html\";\n        this.args = [];\n        this.checks = [\n            {\n                \"pattern\": \"^.{257}\\\\x75\\\\x73\\\\x74\\\\x61\\\\x72\",\n                \"flags\": \"\",\n                \"args\": []\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {List<File>}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const stream = new Stream(input),\n            files = [];\n\n        while (stream.hasMore()) {\n            const dataPosition = stream.position + 512;\n\n            const file = {\n                fileName: stream.readString(100),\n                fileMode: stream.readString(8),\n                ownerUID: stream.readString(8),\n                ownerGID: stream.readString(8),\n                size: parseInt(stream.readString(12), 8), // Octal\n                lastModTime: new Date(1000 * parseInt(stream.readString(12), 8)), // Octal\n                checksum: stream.readString(8),\n                type: stream.readString(1),\n                linkedFileName: stream.readString(100),\n                USTARFormat: stream.readString(6).indexOf(\"ustar\") >= 0,\n            };\n\n            if (file.USTARFormat) {\n                file.version = stream.readString(2);\n                file.ownerUserName = stream.readString(32);\n                file.ownerGroupName = stream.readString(32);\n                file.deviceMajor = stream.readString(8);\n                file.deviceMinor = stream.readString(8);\n                file.filenamePrefix = stream.readString(155);\n            }\n\n            stream.position = dataPosition;\n\n            if (file.type === \"0\") {\n                // File\n                let endPosition = stream.position + file.size;\n                if (file.size % 512 !== 0) {\n                    endPosition += 512 - (file.size % 512);\n                }\n\n                file.bytes = stream.getBytes(file.size);\n                files.push(new File([new Uint8Array(file.bytes)], file.fileName));\n                stream.position = endPosition;\n            } else if (file.type === \"5\") {\n                // Directory\n                files.push(new File([new Uint8Array(file.bytes)], file.fileName));\n            } else {\n                // Symlink or empty bytes\n            }\n        }\n\n        return files;\n    }\n\n    /**\n     * Displays the files in HTML for web apps.\n     *\n     * @param {File[]} files\n     * @returns {html}\n     */\n    async present(files) {\n        return await Utils.displayFilesAsHTML(files);\n    }\n\n}\n\nexport default Untar;\n"
  },
  {
    "path": "src/core/operations/Unzip.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport unzip from \"zlibjs/bin/unzip.min.js\";\n\nconst Zlib = unzip.Zlib;\n\n/**\n * Unzip operation\n */\nclass Unzip extends Operation {\n\n    /**\n     * Unzip constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Unzip\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data using the PKZIP algorithm and displays it per file, with support for passwords.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Zip_(file_format)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"List<File>\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Password\",\n                type: \"binaryString\",\n                value: \"\"\n            },\n            {\n                name: \"Verify result\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^\\\\x50\\\\x4b(?:\\\\x03|\\\\x05|\\\\x07)(?:\\\\x04|\\\\x06|\\\\x08)\",\n                flags: \"\",\n                args: [\"\", false]\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {File[]}\n     */\n    run(input, args) {\n        const options = {\n                password: Utils.strToByteArray(args[0]),\n                verify: args[1]\n            },\n            unzip = new Zlib.Unzip(new Uint8Array(input), options),\n            filenames = unzip.getFilenames();\n\n        return filenames.map(fileName => {\n            const bytes = unzip.decompress(fileName);\n            return new File([bytes], fileName);\n        });\n    }\n\n    /**\n     * Displays the files in HTML for web apps.\n     *\n     * @param {File[]} files\n     * @returns {html}\n     */\n    async present(files) {\n        return await Utils.displayFilesAsHTML(files);\n    }\n\n}\n\nexport default Unzip;\n"
  },
  {
    "path": "src/core/operations/VarIntDecode.mjs",
    "content": "/**\n * @author GCHQ Contributor [3]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Protobuf from \"../lib/Protobuf.mjs\";\n\n/**\n * VarInt Decode operation\n */\nclass VarIntDecode extends Operation {\n\n    /**\n     * VarIntDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"VarInt Decode\";\n        this.module = \"Default\";\n        this.description = \"Decodes a VarInt encoded integer. VarInt is an efficient way of encoding variable length integers and is commonly used with Protobuf.\";\n        this.infoURL = \"https://developers.google.com/protocol-buffers/docs/encoding#varints\";\n        this.inputType = \"byteArray\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {byteArray} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        try {\n            if (typeof BigInt === \"function\") {\n                let result = BigInt(0);\n                let offset = BigInt(0);\n                for (let i = 0; i < input.length; i++) {\n                    result |= BigInt(input[i] & 0x7f) << offset;\n                    if (!(input[i] & 0x80)) break;\n                    offset += BigInt(7);\n                }\n                return result.toString();\n            } else {\n                return Protobuf.varIntDecode(input).toString();\n            }\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default VarIntDecode;\n"
  },
  {
    "path": "src/core/operations/VarIntEncode.mjs",
    "content": "/**\n * @author GCHQ Contributor [3]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Protobuf from \"../lib/Protobuf.mjs\";\n\n/**\n * VarInt Encode operation\n */\nclass VarIntEncode extends Operation {\n\n    /**\n     * VarIntEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"VarInt Encode\";\n        this.module = \"Default\";\n        this.description = \"Encodes a Vn integer as a VarInt. VarInt is an efficient way of encoding variable length integers and is commonly used with Protobuf.\";\n        this.infoURL = \"https://developers.google.com/protocol-buffers/docs/encoding#varints\";\n        this.inputType = \"string\";\n        this.outputType = \"byteArray\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        try {\n            if (typeof BigInt === \"function\") {\n                let value = BigInt(input);\n                if (value < 0) throw new OperationError(\"Negative values cannot be represented as VarInt\");\n                const result = [];\n                while (value >= 0x80) {\n                    result.push(Number(value & BigInt(0x7f)) | 0x80);\n                    value >>= BigInt(7);\n                }\n                result.push(Number(value));\n                return result;\n            } else {\n                return Protobuf.varIntEncode(Number(input));\n            }\n        } catch (err) {\n            throw new OperationError(err);\n        }\n    }\n\n}\n\nexport default VarIntEncode;\n"
  },
  {
    "path": "src/core/operations/ViewBitPlane.mjs",
    "content": "/**\n * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { isImage } from \"../lib/FileType.mjs\";\nimport { toBase64 } from \"../lib/Base64.mjs\";\nimport { Jimp } from \"jimp\";\n\n/**\n * View Bit Plane operation\n */\nclass ViewBitPlane extends Operation {\n    /**\n     * ViewBitPlane constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"View Bit Plane\";\n        this.module = \"Image\";\n        this.description =\n            \"Extracts and displays a bit plane of any given image. These show only a single bit from each pixel, and can be used to hide messages in Steganography.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Bit_plane\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.presentType = \"html\";\n        this.args = [\n            {\n                name: \"Colour\",\n                type: \"option\",\n                value: COLOUR_OPTIONS,\n            },\n            {\n                name: \"Bit\",\n                type: \"number\",\n                value: 0,\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    async run(input, args) {\n        if (!isImage(input))\n            throw new OperationError(\"Please enter a valid image file.\");\n\n        const [colour, bit] = args,\n            parsedImage = await Jimp.read(input),\n            width = parsedImage.bitmap.width,\n            height = parsedImage.bitmap.height,\n            colourIndex = COLOUR_OPTIONS.indexOf(colour),\n            bitIndex = 7 - bit;\n\n        if (bit < 0 || bit > 7) {\n            throw new OperationError(\n                \"Error: Bit argument must be between 0 and 7\",\n            );\n        }\n\n        let pixel, bin, newPixelValue;\n\n        parsedImage.scan(0, 0, width, height, function (x, y, idx) {\n            pixel = this.bitmap.data[idx + colourIndex];\n            bin = Utils.bin(pixel);\n            newPixelValue = 255;\n\n            if (bin.charAt(bitIndex) === \"1\") newPixelValue = 0;\n\n            for (let i = 0; i < 3; i++) {\n                this.bitmap.data[idx + i] = newPixelValue;\n            }\n            this.bitmap.data[idx + 3] = 255;\n        });\n\n        const imageBuffer = await parsedImage.getBuffer(parsedImage.mime);\n\n        return new Uint8Array(imageBuffer).buffer;\n    }\n\n    /**\n     * Displays the extracted data as an image for web apps.\n     * @param {ArrayBuffer} data\n     * @returns {html}\n     */\n    present(data) {\n        if (!data.byteLength) return \"\";\n        const type = isImage(data);\n\n        return `<img src=\"data:${type};base64,${toBase64(data)}\">`;\n    }\n}\n\nconst COLOUR_OPTIONS = [\"Red\", \"Green\", \"Blue\", \"Alpha\"];\n\nexport default ViewBitPlane;\n"
  },
  {
    "path": "src/core/operations/VigenèreDecode.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n/**\n * Vigenère Decode operation\n */\nclass VigenèreDecode extends Operation {\n\n    /**\n     * VigenèreDecode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Vigenère Decode\";\n        this.module = \"Ciphers\";\n        this.description = \"The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Vigenère_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const alphabet = \"abcdefghijklmnopqrstuvwxyz\",\n            key = args[0].toLowerCase();\n        let output = \"\",\n            fail = 0,\n            keyIndex,\n            msgIndex,\n            chr;\n\n        if (!key) throw new OperationError(\"No key entered\");\n        if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError(\"The key must consist only of letters\");\n\n        for (let i = 0; i < input.length; i++) {\n            if (alphabet.indexOf(input[i]) >= 0) {\n                chr = key[(i - fail) % key.length];\n                keyIndex = alphabet.indexOf(chr);\n                msgIndex = alphabet.indexOf(input[i]);\n                // Subtract indexes from each other, add 26 just in case the value is negative,\n                // modulo to remove if necessary\n                output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26];\n            } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {\n                chr = key[(i - fail) % key.length].toLowerCase();\n                keyIndex = alphabet.indexOf(chr);\n                msgIndex = alphabet.indexOf(input[i].toLowerCase());\n                output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase();\n            } else {\n                output += input[i];\n                fail++;\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Highlight Vigenère Decode\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Vigenère Decode in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default VigenèreDecode;\n"
  },
  {
    "path": "src/core/operations/VigenèreEncode.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Vigenère Encode operation\n */\nclass VigenèreEncode extends Operation {\n\n    /**\n     * VigenèreEncode constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Vigenère Encode\";\n        this.module = \"Ciphers\";\n        this.description = \"The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Vigenère_cipher\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const alphabet = \"abcdefghijklmnopqrstuvwxyz\",\n            key = args[0].toLowerCase();\n        let output = \"\",\n            fail = 0,\n            keyIndex,\n            msgIndex,\n            chr;\n\n        if (!key) throw new OperationError(\"No key entered\");\n        if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError(\"The key must consist only of letters\");\n\n        for (let i = 0; i < input.length; i++) {\n            if (alphabet.indexOf(input[i]) >= 0) {\n                // Get the corresponding character of key for the current letter, accounting\n                // for chars not in alphabet\n                chr = key[(i - fail) % key.length];\n                // Get the location in the vigenere square of the key char\n                keyIndex = alphabet.indexOf(chr);\n                // Get the location in the vigenere square of the message char\n                msgIndex = alphabet.indexOf(input[i]);\n                // Get the encoded letter by finding the sum of indexes modulo 26 and finding\n                // the letter corresponding to that\n                output += alphabet[(keyIndex + msgIndex) % 26];\n            } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {\n                chr = key[(i - fail) % key.length].toLowerCase();\n                keyIndex = alphabet.indexOf(chr);\n                msgIndex = alphabet.indexOf(input[i].toLowerCase());\n                output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase();\n            } else {\n                output += input[i];\n                fail++;\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Highlight Vigenère Encode\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight Vigenère Encode in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default VigenèreEncode;\n"
  },
  {
    "path": "src/core/operations/Whirlpool.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {runHash} from \"../lib/Hash.mjs\";\n\n/**\n * Whirlpool operation\n */\nclass Whirlpool extends Operation {\n\n    /**\n     * Whirlpool constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Whirlpool\";\n        this.module = \"Crypto\";\n        this.description = \"Whirlpool is a cryptographic hash function designed by Vincent Rijmen (co-creator of AES) and Paulo S. L. M. Barreto, who first described it in 2000.<br><br>Several variants exist:<ul><li>Whirlpool-0 is the original version released in 2000.</li><li>Whirlpool-T is the first revision, released in 2001, improving the generation of the s-box.</li><li>Whirlpool is the latest revision, released in 2003, fixing a flaw in the diffusion matrix.</li></ul>\";\n        this.infoURL = \"https://wikipedia.org/wiki/Whirlpool_(cryptography)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Variant\",\n                type: \"option\",\n                value: [\"Whirlpool\", \"Whirlpool-T\", \"Whirlpool-0\"]\n            },\n            {\n                name: \"Rounds\",\n                type: \"number\",\n                value: 10,\n                min: 1,\n                max: 10\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const variant = args[0].toLowerCase();\n        return runHash(variant, input, {rounds: args[1]});\n    }\n\n}\n\nexport default Whirlpool;\n"
  },
  {
    "path": "src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport BigNumber from \"bignumber.js\";\nimport OperationError from \"../errors/OperationError.mjs\";\n\n/**\n * Windows Filetime to UNIX Timestamp operation\n */\nclass WindowsFiletimeToUNIXTimestamp extends Operation {\n\n    /**\n     * WindowsFiletimeToUNIXTimestamp constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Windows Filetime to UNIX Timestamp\";\n        this.module = \"Default\";\n        this.description = \"Converts a Windows Filetime value to a UNIX timestamp.<br><br>A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.<br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).<br><br>This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds.\";\n        this.infoURL = \"https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Output units\",\n                \"type\": \"option\",\n                \"value\": [\"Seconds (s)\", \"Milliseconds (ms)\", \"Microseconds (μs)\", \"Nanoseconds (ns)\"]\n            },\n            {\n                \"name\": \"Input format\",\n                \"type\": \"option\",\n                \"value\": [\"Decimal\", \"Hex (big endian)\", \"Hex (little endian)\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [units, format] = args;\n\n        if (!input) return \"\";\n\n        if (format === \"Hex (little endian)\") {\n            // Swap endianness\n            let result = \"\";\n            if (input.length % 2 !== 0) {\n                result += input.charAt(input.length - 1);\n            }\n            for (let i = input.length - input.length % 2 - 2; i >= 0; i -= 2) {\n                result += input.charAt(i);\n                result += input.charAt(i + 1);\n            }\n            input = result;\n        }\n\n        if (format.startsWith(\"Hex\")) {\n            input = new BigNumber(input, 16);\n        } else {\n            input = new BigNumber(input);\n        }\n\n        input = input.minus(new BigNumber(\"116444736000000000\"));\n\n        if (units === \"Seconds (s)\") {\n            input = input.dividedBy(new BigNumber(\"10000000\"));\n        } else if (units === \"Milliseconds (ms)\") {\n            input = input.dividedBy(new BigNumber(\"10000\"));\n        } else if (units === \"Microseconds (μs)\") {\n            input = input.dividedBy(new BigNumber(\"10\"));\n        } else if (units === \"Nanoseconds (ns)\") {\n            input = input.multipliedBy(new BigNumber(\"100\"));\n        } else {\n            throw new OperationError(\"Unrecognised unit\");\n        }\n\n        return input.toFixed();\n    }\n\n}\n\nexport default WindowsFiletimeToUNIXTimestamp;\n"
  },
  {
    "path": "src/core/operations/XKCDRandomNumber.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\n\n/**\n * XKCD Random Number operation\n */\nclass XKCDRandomNumber extends Operation {\n\n    /**\n     * XKCDRandomNumber constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XKCD Random Number\";\n        this.module = \"Default\";\n        this.description = \"RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.\";\n        this.infoURL = \"https://xkcd.com/221/\";\n        this.inputType = \"string\";\n        this.outputType = \"number\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        return 4; // chosen by fair dice roll.\n                  // guaranteed to be random.\n    }\n\n}\n\nexport default XKCDRandomNumber;\n"
  },
  {
    "path": "src/core/operations/XMLBeautify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport vkbeautify from \"vkbeautify\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * XML Beautify operation\n */\nclass XMLBeautify extends Operation {\n\n    /**\n     * XMLBeautify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XML Beautify\";\n        this.module = \"Code\";\n        this.description = \"Indents and prettifies eXtensible Markup Language (XML) code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Indent string\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\t\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const indentStr = args[0];\n        return vkbeautify.xml(input, indentStr);\n    }\n\n}\n\nexport default XMLBeautify;\n"
  },
  {
    "path": "src/core/operations/XMLMinify.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport vkbeautify from \"vkbeautify\";\nimport Operation from \"../Operation.mjs\";\n\n/**\n * XML Minify operation\n */\nclass XMLMinify extends Operation {\n\n    /**\n     * XMLMinify constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XML Minify\";\n        this.module = \"Code\";\n        this.description = \"Compresses eXtensible Markup Language (XML) code.\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Preserve comments\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const preserveComments = args[0];\n        return vkbeautify.xmlmin(input, preserveComments);\n    }\n\n}\n\nexport default XMLMinify;\n"
  },
  {
    "path": "src/core/operations/XOR.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { bitOp, xor, BITWISE_OP_DELIMS } from \"../lib/BitwiseOp.mjs\";\n\n/**\n * XOR operation\n */\nclass XOR extends Operation {\n\n    /**\n     * XOR constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XOR\";\n        this.module = \"Default\";\n        this.description = \"XOR the input with the given key.<br>e.g. <code>fe023da5</code><br><br><strong>Options</strong><br><u>Null preserving:</u> If the current byte is 0x00 or the same as the key, skip it.<br><br><u>Scheme:</u><ul><li>Standard - key is unchanged after each round</li><li>Input differential - key is set to the value of the previous unprocessed byte</li><li>Output differential - key is set to the value of the previous processed byte</li><li>Cascade - key is set to the input byte shifted by one</li></ul>\";\n        this.infoURL = \"https://wikipedia.org/wiki/XOR\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"byteArray\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": BITWISE_OP_DELIMS\n            },\n            {\n                \"name\": \"Scheme\",\n                \"type\": \"option\",\n                \"value\": [\"Standard\", \"Input differential\", \"Output differential\", \"Cascade\"]\n            },\n            {\n                \"name\": \"Null preserving\",\n                \"type\": \"boolean\",\n                \"value\": false\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {byteArray}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const key = Utils.convertToByteArray(args[0].string || \"\", args[0].option),\n            [, scheme, nullPreserving] = args;\n\n        return bitOp(input, key, xor, nullPreserving, scheme);\n    }\n\n    /**\n     * Highlight XOR\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        return pos;\n    }\n\n    /**\n     * Highlight XOR in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        return pos;\n    }\n\n}\n\nexport default XOR;\n"
  },
  {
    "path": "src/core/operations/XORBruteForce.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { bitOp, xor } from \"../lib/BitwiseOp.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * XOR Brute Force operation\n */\nclass XORBruteForce extends Operation {\n\n    /**\n     * XORBruteForce constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XOR Brute Force\";\n        this.module = \"Default\";\n        this.description = \"Enumerate all possible XOR solutions. Current maximum key length is 2 due to browser performance.<br><br>Optionally enter a string that you expect to find in the plaintext to filter results (crib).\";\n        this.infoURL = \"https://wikipedia.org/wiki/Exclusive_or\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key length\",\n                \"type\": \"number\",\n                \"value\": 1\n            },\n            {\n                \"name\": \"Sample length\",\n                \"type\": \"number\",\n                \"value\": 100\n            },\n            {\n                \"name\": \"Sample offset\",\n                \"type\": \"number\",\n                \"value\": 0\n            },\n            {\n                \"name\": \"Scheme\",\n                \"type\": \"option\",\n                \"value\": [\"Standard\", \"Input differential\", \"Output differential\"]\n            },\n            {\n                \"name\": \"Null preserving\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Print key\",\n                \"type\": \"boolean\",\n                \"value\": true\n            },\n            {\n                \"name\": \"Output as hex\",\n                \"type\": \"boolean\",\n                \"value\": false\n            },\n            {\n                \"name\": \"Crib (known plaintext string)\",\n                \"type\": \"binaryString\",\n                \"value\": \"\"\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        const [\n                keyLength,\n                sampleLength,\n                sampleOffset,\n                scheme,\n                nullPreserving,\n                printKey,\n                outputHex,\n                rawCrib\n            ] = args,\n            crib = rawCrib.toLowerCase(),\n            output = [];\n        let result,\n            resultUtf8,\n            record = \"\";\n\n        input = input.slice(sampleOffset, sampleOffset + sampleLength);\n\n        if (isWorkerEnvironment())\n            self.sendStatusMessage(\"Calculating \" + Math.pow(256, keyLength) + \" values...\");\n\n        /**\n         * Converts an integer to an array of bytes expressing that number.\n         *\n         * @param {number} int\n         * @param {number} len - Length of the resulting array\n         * @returns {array}\n         */\n        const intToByteArray = (int, len) => {\n            const res = Array(len).fill(0);\n            for (let i = len - 1; i >= 0; i--) {\n                res[i] = int & 0xff;\n                int = int >>> 8;\n            }\n            return res;\n        };\n\n        for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) {\n            if (key % 10000 === 0 && isWorkerEnvironment()) {\n                self.sendStatusMessage(\"Calculating \" + l + \" values... \" + Math.floor(key / l * 100) + \"%\");\n            }\n\n            result = bitOp(input, intToByteArray(key, keyLength), xor, nullPreserving, scheme);\n            resultUtf8 = Utils.byteArrayToUtf8(result);\n            record = \"\";\n\n            if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue;\n            if (printKey) record += \"Key = \" + Utils.hex(key, (2*keyLength)) + \": \";\n            record += outputHex ? toHex(result) : Utils.escapeWhitespace(resultUtf8);\n\n            output.push(record);\n        }\n\n        return output.join(\"\\n\");\n    }\n\n}\n\nexport default XORBruteForce;\n"
  },
  {
    "path": "src/core/operations/XORChecksum.mjs",
    "content": "/**\n * @author Thomas Weißschuh [thomas@t-8ch.de]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\n\n/**\n * XOR Checksum operation\n */\nclass XORChecksum extends Operation {\n\n    /**\n     * XORChecksum constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XOR Checksum\";\n        this.module = \"Crypto\";\n        this.description = \"XOR Checksum splits the input into blocks of a configurable size and performs the XOR operation on these blocks.\";\n        this.infoURL = \"https://wikipedia.org/wiki/XOR\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Blocksize\",\n                type: \"number\",\n                value: 4\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const blocksize = args[0];\n        input = new Uint8Array(input);\n\n        const res = Array(blocksize);\n        res.fill(0);\n\n        for (const chunk of Utils.chunked(input, blocksize)) {\n            for (let i = 0; i < blocksize; i++) {\n                res[i] ^= chunk[i];\n            }\n        }\n\n        return toHex(res, \"\");\n    }\n}\n\nexport default XORChecksum;\n"
  },
  {
    "path": "src/core/operations/XPathExpression.mjs",
    "content": "/**\n * @author Mikescher (https://github.com/Mikescher | https://mikescher.com)\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport xmldom from \"@xmldom/xmldom\";\nimport xpath from \"xpath\";\n\n/**\n * XPath expression operation\n */\nclass XPathExpression extends Operation {\n\n    /**\n     * XPathExpression constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XPath expression\";\n        this.module = \"Code\";\n        this.description = \"Extract information from an XML document with an XPath query\";\n        this.infoURL = \"https://wikipedia.org/wiki/XPath\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"XPath\",\n                \"type\": \"string\",\n                \"value\": \"\"\n            },\n            {\n                \"name\": \"Result delimiter\",\n                \"type\": \"binaryShortString\",\n                \"value\": \"\\\\n\"\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const [query, delimiter] = args;\n\n        let doc;\n        try {\n            doc = new xmldom.DOMParser({\n                errorHandler: {\n                    fatalError(e) {\n                        throw e;\n                    }\n                }\n            }).parseFromString(input, \"application/xml\");\n        } catch (err) {\n            throw new OperationError(\"Invalid input XML.\");\n        }\n\n        let nodes;\n        try {\n            nodes = xpath.parse(query).select({ node: doc, allowAnyNamespaceForNoPrefix: true });\n        } catch (err) {\n            throw new OperationError(`Invalid XPath. Details:\\n${err.message}.`);\n        }\n\n        const nodeToString = function(node) {\n            return node.toString();\n        };\n\n        return nodes.map(nodeToString).join(delimiter);\n    }\n\n}\n\nexport default XPathExpression;\n"
  },
  {
    "path": "src/core/operations/XSalsa20.mjs",
    "content": "/**\n * @author joostrijneveld [joost@joostrijneveld.nl]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport { toHex } from \"../lib/Hex.mjs\";\nimport { salsa20Block, hsalsa20 } from \"../lib/Salsa20.mjs\";\n\n/**\n * XSalsa20 operation\n */\nclass XSalsa20 extends Operation {\n\n    /**\n     * XSalsa20 constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XSalsa20\";\n        this.module = \"Ciphers\";\n        this.description = \"XSalsa20 is a variant of the Salsa20 stream cipher designed by Daniel J. Bernstein; XSalsa uses longer nonces.<br><br><b>Key:</b> XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> XSalsa20 uses a nonce of 24 bytes (192 bits).<br><br><b>Counter:</b> XSalsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/Salsa20#XSalsa20_with_192-bit_nonce\";\n        this.inputType = \"string\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n            {\n                \"name\": \"Nonce\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\", \"Integer\"]\n            },\n            {\n                \"name\": \"Counter\",\n                \"type\": \"number\",\n                \"value\": 0,\n                \"min\": 0\n            },\n            {\n                \"name\": \"Rounds\",\n                \"type\": \"option\",\n                \"value\": [\"20\", \"12\", \"8\"]\n            },\n            {\n                \"name\": \"Input\",\n                \"type\": \"option\",\n                \"value\": [\"Hex\", \"Raw\"]\n            },\n            {\n                \"name\": \"Output\",\n                \"type\": \"option\",\n                \"value\": [\"Raw\", \"Hex\"]\n            }\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = Utils.convertToByteArray(args[0].string, args[0].option),\n            nonceType = args[1].option,\n            rounds = parseInt(args[3], 10),\n            inputType = args[4],\n            outputType = args[5];\n\n        if (key.length !== 16 && key.length !== 32) {\n            throw new OperationError(`Invalid key length: ${key.length} bytes.\n\nXSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`);\n        }\n\n        let counter, nonce;\n        if (nonceType === \"Integer\") {\n            nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, \"little\");\n        } else {\n            nonce = Utils.convertToByteArray(args[1].string, args[1].option);\n            if (!(nonce.length === 24)) {\n                throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.\n\nXSalsa20 uses a nonce of 24 bytes (192 bits).`);\n            }\n        }\n        counter = Utils.intToByteArray(args[2], 8, \"little\");\n\n        const xsalsaKey = hsalsa20(key, nonce.slice(0, 16), rounds);\n\n        const output = [];\n        input = Utils.convertToByteArray(input, inputType);\n\n        let counterAsInt = Utils.byteArrayToInt(counter, \"little\");\n        for (let i = 0; i < input.length; i += 64) {\n            counter = Utils.intToByteArray(counterAsInt, 8, \"little\");\n            const stream = salsa20Block(xsalsaKey, nonce.slice(16, 24), counter, rounds);\n            for (let j = 0; j < 64 && i + j < input.length; j++) {\n                output.push(input[i + j] ^ stream[j]);\n            }\n            counterAsInt++;\n        }\n\n        if (outputType === \"Hex\") {\n            return toHex(output);\n        } else {\n            return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);\n        }\n    }\n\n    /**\n     * Highlight XSalsa20\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlight(pos, args) {\n        const inputType = args[4],\n            outputType = args[5];\n        if (inputType === \"Raw\" && outputType === \"Raw\") {\n            return pos;\n        }\n    }\n\n    /**\n     * Highlight XSalsa20 in reverse\n     *\n     * @param {Object[]} pos\n     * @param {number} pos[].start\n     * @param {number} pos[].end\n     * @param {Object[]} args\n     * @returns {Object[]} pos\n     */\n    highlightReverse(pos, args) {\n        const inputType = args[4],\n            outputType = args[5];\n        if (inputType === \"Raw\" && outputType === \"Raw\") {\n            return pos;\n        }\n    }\n\n}\n\nexport default XSalsa20;\n"
  },
  {
    "path": "src/core/operations/XXTEADecrypt.mjs",
    "content": "/**\n * @author devcydo [devcydo@gmail.com]\n * @author Ma Bingyao [mabingyao@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport {decrypt} from \"../lib/XXTEA.mjs\";\n\n/**\n * XXTEA Decrypt operation\n */\nclass XXTEADecrypt extends Operation {\n\n    /**\n     * XXTEADecrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XXTEA Decrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block.\";\n        this.infoURL = \"https://wikipedia.org/wiki/XXTEA\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option));\n        try {\n            return decrypt(new Uint8Array(input), key).buffer;\n        } catch (err) {\n            throw new OperationError(\"Unable to decrypt using this key\");\n        }\n    }\n\n}\n\nexport default XXTEADecrypt;\n"
  },
  {
    "path": "src/core/operations/XXTEAEncrypt.mjs",
    "content": "/**\n * @author devcydo [devcydo@gmail.com]\n * @author Ma Bingyao [mabingyao@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {encrypt} from \"../lib/XXTEA.mjs\";\n\n/**\n * XXTEA Encrypt operation\n */\nclass XXTEAEncrypt extends Operation {\n\n    /**\n     * XXTEAEncrypt constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"XXTEA Encrypt\";\n        this.module = \"Ciphers\";\n        this.description = \"Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block.\";\n        this.infoURL = \"https://wikipedia.org/wiki/XXTEA\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                \"name\": \"Key\",\n                \"type\": \"toggleString\",\n                \"value\": \"\",\n                \"toggleValues\": [\"Hex\", \"UTF8\", \"Latin1\", \"Base64\"]\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option));\n        return encrypt(new Uint8Array(input), key).buffer;\n    }\n\n}\n\nexport default XXTEAEncrypt;\n"
  },
  {
    "path": "src/core/operations/YAMLToJSON.mjs",
    "content": "/**\n * @author ccarpo [ccarpo@gmx.net]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport jsYaml from \"js-yaml\";\n/**\n * YAML to JSON operation\n */\nclass YAMLToJSON extends Operation {\n\n    /**\n     * YAMLToJSON constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"YAML to JSON\";\n        this.module = \"Default\";\n        this.description = \"Convert YAML to JSON\";\n        this.infoURL = \"https://en.wikipedia.org/wiki/YAML\";\n        this.inputType = \"string\";\n        this.outputType = \"JSON\";\n        this.args = [];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {JSON}\n     */\n    run(input, args) {\n        try {\n            return jsYaml.load(input);\n        } catch (err) {\n            throw new OperationError(\"Unable to parse YAML: \" + err);\n        }\n    }\n\n}\n\nexport default YAMLToJSON;\n"
  },
  {
    "path": "src/core/operations/YARARules.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport OperationError from \"../errors/OperationError.mjs\";\nimport Yara from \"libyara-wasm\";\nimport { isWorkerEnvironment } from \"../Utils.mjs\";\n\n/**\n * YARA Rules operation\n */\nclass YARARules extends Operation {\n\n    /**\n     * YARARules constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"YARA Rules\";\n        this.module = \"Yara\";\n        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>\";\n        this.infoURL = \"https://wikipedia.org/wiki/YARA\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [\n            {\n                name: \"Rules\",\n                type: \"text\",\n                value: \"\",\n                rows: 5\n            },\n            {\n                name: \"Show strings\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Show string lengths\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Show metadata\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Show counts\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Show rule warnings\",\n                type: \"boolean\",\n                value: true\n            },\n            {\n                name: \"Show console module messages\",\n                type: \"boolean\",\n                value: true\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        if (isWorkerEnvironment())\n            self.sendStatusMessage(\"Instantiating YARA...\");\n        const [rules, showStrings, showLengths, showMeta, showCounts, showRuleWarns, showConsole] = args;\n        return new Promise((resolve, reject) => {\n            Yara().then(yara => {\n                if (isWorkerEnvironment()) self.sendStatusMessage(\"Converting data for YARA.\");\n                let matchString = \"\";\n\n                const inpArr = new Uint8Array(input); // Turns out embind knows that JS uint8array <==> C++ std::string\n\n                if (isWorkerEnvironment()) self.sendStatusMessage(\"Running YARA matching.\");\n\n                const resp = yara.run(inpArr, rules);\n\n                if (isWorkerEnvironment()) self.sendStatusMessage(\"Processing data.\");\n\n                if (resp.compileErrors.size() > 0) {\n                    for (let i = 0; i < resp.compileErrors.size(); i++) {\n                        const compileError = resp.compileErrors.get(i);\n                        if (!compileError.warning) {\n                            reject(new OperationError(`Error on line ${compileError.lineNumber}: ${compileError.message}`));\n                        } else if (showRuleWarns) {\n                            matchString += `Warning on line ${compileError.lineNumber}: ${compileError.message}\\n`;\n                        }\n                    }\n                }\n\n                if (showConsole) {\n                    const consoleLogs = resp.consoleLogs;\n                    for (let i = 0; i < consoleLogs.size(); i++) {\n                        matchString += consoleLogs.get(i) + \"\\n\";\n                    }\n                }\n\n                const matchedRules = resp.matchedRules;\n                for (let i = 0; i < matchedRules.size(); i++) {\n                    const rule = matchedRules.get(i);\n                    const matches = rule.resolvedMatches;\n                    let meta = \"\";\n                    if (showMeta && rule.metadata.size() > 0) {\n                        meta += \" [\";\n                        for (let j = 0; j < rule.metadata.size(); j++) {\n                            meta += `${rule.metadata.get(j).identifier}: ${rule.metadata.get(j).data}, `;\n                        }\n                        meta = meta.slice(0, -2) + \"]\";\n                    }\n                    const countString = matches.size() === 0 ? \"\" : (showCounts ? ` (${matches.size()} time${matches.size() > 1 ? \"s\" : \"\"})` : \"\");\n                    if (matches.size() === 0 || !(showStrings || showLengths)) {\n                        matchString += `Input matches rule \"${rule.ruleName}\"${meta}${countString.length > 0 ? ` ${countString}`: \"\"}.\\n`;\n                    } else {\n                        matchString += `Rule \"${rule.ruleName}\"${meta} matches${countString}:\\n`;\n                        for (let j = 0; j < matches.size(); j++) {\n                            const match = matches.get(j);\n                            if (showStrings || showLengths) {\n                                matchString += `Pos ${match.location}, ${showLengths ? `length ${match.matchLength}, ` : \"\"}identifier ${match.stringIdentifier}${showStrings ? `, data: \"${match.data}\"` : \"\"}\\n`;\n                            }\n                        }\n                    }\n                }\n                resolve(matchString);\n            });\n        });\n    }\n\n}\n\nexport default YARARules;\n"
  },
  {
    "path": "src/core/operations/Zip.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport Utils from \"../Utils.mjs\";\nimport {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from \"../lib/Zlib.mjs\";\nimport zip from \"zlibjs/bin/zip.min.js\";\n\nconst Zlib = zip.Zlib;\n\nconst ZIP_COMPRESSION_METHOD_LOOKUP = {\n    \"Deflate\":      Zlib.Zip.CompressionMethod.DEFLATE,\n    \"None (Store)\": Zlib.Zip.CompressionMethod.STORE\n};\n\nconst ZIP_OS_LOOKUP = {\n    \"MSDOS\":     Zlib.Zip.OperatingSystem.MSDOS,\n    \"Unix\":      Zlib.Zip.OperatingSystem.UNIX,\n    \"Macintosh\": Zlib.Zip.OperatingSystem.MACINTOSH\n};\n\n/**\n * Zip operation\n */\nclass Zip extends Operation {\n\n    /**\n     * Zip constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Zip\";\n        this.module = \"Compression\";\n        this.description = \"Compresses data using the PKZIP algorithm with the given filename.<br><br>No support for multiple files at this time.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Zip_(file_format)\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"File\";\n        this.args = [\n            {\n                name: \"Filename\",\n                type: \"string\",\n                value: \"file.txt\"\n            },\n            {\n                name: \"Comment\",\n                type: \"string\",\n                value: \"\"\n            },\n            {\n                name: \"Password\",\n                type: \"binaryString\",\n                value: \"\"\n            },\n            {\n                name: \"Compression method\",\n                type: \"option\",\n                value: [\"Deflate\", \"None (Store)\"]\n            },\n            {\n                name: \"Operating system\",\n                type: \"option\",\n                value: [\"MSDOS\", \"Unix\", \"Macintosh\"]\n            },\n            {\n                name: \"Compression type\",\n                type: \"option\",\n                value: COMPRESSION_TYPE\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {File}\n     */\n    run(input, args) {\n        const filename = args[0],\n            password = Utils.strToByteArray(args[2]),\n            options = {\n                filename: Utils.strToByteArray(filename),\n                comment: Utils.strToByteArray(args[1]),\n                compressionMethod: ZIP_COMPRESSION_METHOD_LOOKUP[args[3]],\n                os: ZIP_OS_LOOKUP[args[4]],\n                deflateOption: {\n                    compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]]\n                },\n            },\n            zip = new Zlib.Zip();\n\n        if (password.length)\n            zip.setPassword(password);\n        zip.addFile(new Uint8Array(input), options);\n        return new File([zip.compress()], filename);\n    }\n\n}\n\nexport default Zip;\n"
  },
  {
    "path": "src/core/operations/ZlibDeflate.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from \"../lib/Zlib.mjs\";\nimport zlibAndGzip from \"zlibjs/bin/zlib_and_gzip.min.js\";\n\nconst Zlib = zlibAndGzip.Zlib;\n\n/**\n * Zlib Deflate operation\n */\nclass ZlibDeflate extends Operation {\n\n    /**\n     * ZlibDeflate constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Zlib Deflate\";\n        this.module = \"Compression\";\n        this.description = \"Compresses data using the deflate algorithm adding zlib headers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Zlib\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Compression type\",\n                type: \"option\",\n                value: COMPRESSION_TYPE\n            }\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const deflate = new Zlib.Deflate(new Uint8Array(input), {\n            compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]]\n        });\n        return new Uint8Array(deflate.compress()).buffer;\n    }\n\n}\n\nexport default ZlibDeflate;\n"
  },
  {
    "path": "src/core/operations/ZlibInflate.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Operation from \"../Operation.mjs\";\nimport {INFLATE_BUFFER_TYPE} from \"../lib/Zlib.mjs\";\nimport zlibAndGzip from \"zlibjs/bin/zlib_and_gzip.min.js\";\n\nconst Zlib = zlibAndGzip.Zlib;\n\nconst ZLIB_BUFFER_TYPE_LOOKUP = {\n    \"Adaptive\": Zlib.Inflate.BufferType.ADAPTIVE,\n    \"Block\":    Zlib.Inflate.BufferType.BLOCK,\n};\n\n/**\n * Zlib Inflate operation\n */\nclass ZlibInflate extends Operation {\n\n    /**\n     * ZlibInflate constructor\n     */\n    constructor() {\n        super();\n\n        this.name = \"Zlib Inflate\";\n        this.module = \"Compression\";\n        this.description = \"Decompresses data which has been compressed using the deflate algorithm with zlib headers.\";\n        this.infoURL = \"https://wikipedia.org/wiki/Zlib\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"ArrayBuffer\";\n        this.args = [\n            {\n                name: \"Start index\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Initial output buffer size\",\n                type: \"number\",\n                value: 0\n            },\n            {\n                name: \"Buffer expansion type\",\n                type: \"option\",\n                value: INFLATE_BUFFER_TYPE\n            },\n            {\n                name: \"Resize buffer after decompression\",\n                type: \"boolean\",\n                value: false\n            },\n            {\n                name: \"Verify result\",\n                type: \"boolean\",\n                value: false\n            }\n        ];\n        this.checks = [\n            {\n                pattern: \"^\\\\x78(\\\\x01|\\\\x9c|\\\\xda|\\\\x5e)\",\n                flags: \"\",\n                args: [0, 0, \"Adaptive\", false, false]\n            },\n        ];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {ArrayBuffer}\n     */\n    run(input, args) {\n        const inflate = new Zlib.Inflate(new Uint8Array(input), {\n            index: args[0],\n            bufferSize: args[1],\n            bufferType: ZLIB_BUFFER_TYPE_LOOKUP[args[2]],\n            resize: args[3],\n            verify: args[4]\n        });\n        return new Uint8Array(inflate.decompress()).buffer;\n    }\n\n}\n\nexport default ZlibInflate;\n"
  },
  {
    "path": "src/core/vendor/DisassembleX86-64.mjs",
    "content": "/*-------------------------------------------------------------------------------------------------------------------------\nCreated by Damian Recoskie (https://github.com/Recoskie/X86-64-Disassembler-JS) \n  & exported for CyberChef by Matt [me@mitt.dev]\n---------------------------------------------------------------------------------------------------------------------------\nMIT License\n\nCopyright (c) 2019 Damian Recoskie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n-------------------------------------------------------------------------------------------------------------------------*/\n\n\n/*-------------------------------------------------------------------------------------------------------------------------\nBinary byte code array.\n---------------------------------------------------------------------------------------------------------------------------\nFunction ^LoadBinCode()^ takes a string input of hex and loads it into the BinCode array it is recommended that the location\nthe hex string is read from a file, or sector matches the disassemblers set base address using function ^SetBasePosition()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar BinCode = [];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nWhen Bit Mode is 2 the disassembler will default to decoding 64 bit binary code possible settings are 0=16 bit, 1=32 bit, 2=64 bit.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar BitMode = 2;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe variable CodePos is the position in the BinCode array starts at 0 for each new section loaded in by ^LoadBinCode()^.\n---------------------------------------------------------------------------------------------------------------------------\nThe function ^NextByte()^ moves CodePos, and the Disassemblers Base address by one stored in Pos64, Pos32.\nThe BinCode array is designed for loading in a section of binary that is supposed to be from the set Base address in Pos64, and Pos32.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar CodePos = 0x00000000;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Pos64, and Pos32 is the actual base address that instructions are supposed to be from in memory when they are loaded\ninto the BinCode array using the Function ^LoadBinCode()^.\n---------------------------------------------------------------------------------------------------------------------------\nThe function ^SetBasePosition()^ sets the base location in Pos64, and Pos32, and Code Segment.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar Pos64 = 0x00000000, Pos32 = 0x00000000;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nCode Segment is used in 16 bit binaries in which the segment is times 16 (Left Shift 4) added to the 16 bit address position.\nThis was done to load more programs in 16 bit space at an selected segment location. In 16 bit X86 processors the instruction\npointer register counts from 0000 hex to FFFF hex and starts over at 0000 hex. Allowing a program to be a max length of\n65535 bytes long. The Code Segment is multiplied by 16 then is added to the instruction pointer position in memory.\n---------------------------------------------------------------------------------------------------------------------------\nIn 32 bit, and 64 bit the address combination is large enough that segmented program loading was no longer required.\nHowever 32 bit still supports Segmented addressing if used, but 64 bit binaries do not. Also if the code segment is set\n36, or higher in 32 bit binaries this sets SEG:OFFSET address format for each instructions Memory position.\n---------------------------------------------------------------------------------------------------------------------------\nIn 64 bit mode, an programs instructions are in a 64 bit address using the processors full instruction pointer, but in 32\nbit instructions the first 32 bit of the instruction pointer is used. In 16 bit the first 16 bits of the instruction pointer\nis used, but with the code segment. Each instruction is executed in order by the Instruction pointer that goes in sectional sizes\n\"RIP (64)/EIP (32)/IP (16)\" Depending on the Bit mode the 64 bit CPU is set in, or if the CPU is 32 bit to begin with.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar CodeSeg = 0x0000;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe InstructionHex String stores the Bytes of decoded instructions. It is shown to the left side of the disassembled instruction.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar InstructionHex = \"\";\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe InstructionPos String stores the start position of a decoded binary instruction in memory from the function ^GetPosition()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar InstructionPos = \"\";\n\n/*-------------------------------------------------------------------------------------------------------------------------\nDecoding display options.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar ShowInstructionHex = true; //setting to show the hex code of the instruction beside the decoded instruction output.\nvar ShowInstructionPos = true; //setting to show the instruction address position.\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Opcode, and Opcode map.\n---------------------------------------------------------------------------------------------------------------------------\nThe first 0 to 255 (Byte) value that is read is the selected instruction code, however some codes are used as Adjustment to\nremove limitations that are read by the function ^DecodePrefixAdjustments()^.\n---------------------------------------------------------------------------------------------------------------------------\nBecause X86 was limited to 255 instructions An number was sacrificed to add more instructions.\nBy using one of the 0 to 255 instructions like 15 which is \"0F\" as an hex number the next 0 to 255 value is an hole\nnew set of 0 to 255 instructions these are called escape code prefixes.\n---------------------------------------------------------------------------------------------------------------------------\nBellow XX is the opcode combined with the adjustment escape codes thus how opcode is used numerically in the disassembler.\n---------------------------------------------------------------------------------------------------------------------------\n00,00000000 = 0, lower 8 bit opcode at max 00,11111111 = 255. (First byte opcodes XX) Opcodes values 0 to 255.\n01,00000000 = 256, lower 8 bit opcode at max 01,11111111 = 511. (Two byte opcodes 0F XX) Opcodes values 256 to 511.\n10,00000000 = 512, lower 8 bit opcode at max 10,11111111 = 767. (Three byte opcodes 0F 38 XX) Opcodes values 512 to 767.\n11,00000000 = 768, lower 8 bit opcode at max 11,11111111 = 1023. (Three byte opcodes 0F 3A XX) Opcodes values 768 to 1023.\n---------------------------------------------------------------------------------------------------------------------------\nThe lower 8 bits is the selectable opcode 0 to 255 plus one from 255 is 1,00000000 = 256 thus 256 acts as the place holder.\nThe vector adjustment codes contain an map bit selection the map bits go in order to the place holder map bits are in.\nThis makes it so the map bits can be placed where the place holder bits are.\n---------------------------------------------------------------------------------------------------------------------------\nVEX.mmmmm = 000_00b (1-byte map), 000_01b (2-byte map), 000_10b (0Fh,38h), 000_11b (0Fh,3Ah)\nEVEX.mm = 00b (1-byte map), 01b (2-byte map), 10b (0Fh,38h), 11b (0Fh,3Ah)\n--------------------------------------------------------------------------------------------------------------------------\nFunction ^DecodePrefixAdjustments()^ reads opcodes that act as settings it only ends when Opcode is an actual\ninstruction code value 0 to 1023 inducing escape codes. Opcode is Used by function ^DecodeOpcode()^ with the Mnemonic array.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar Opcode = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nOpcode is used as the index for the point in the structure to land on in the \"Mnemonics\".\n---------------------------------------------------------------------------------------------------------------------------\nX86 has an amazing architectural pattern that is like an fractal in many ways. Previously an experiment was done to make\nthis an one dimensional array, but after testing it proved that it was slower because each of the branches had to be\ncalculated to an unique index in memory in which lots of combinations map to the same instructions well some changed.\nThe calculation took more time than comparing if an index is an reference to another array to optionally use an encoding.\n---------------------------------------------------------------------------------------------------------------------------\nThe first branch is an array 2 in size which separates opcodes that change between register, and memory mode.\n---------------------------------------------------------------------------------------------------------------------------\nThe second branch is an array 8 in size which uses an register as an 0 to 7 value for the selected instruction code called grouped opcodes.\nThe second branch can be branched into another array 8 in size this covers the last three bits of the ModR/M byte for static opcodes.\n---------------------------------------------------------------------------------------------------------------------------\nThe third branch is an array 4 in size which is the SIMD modes. The third branch can branch to an array 4 in size again under\nany of the 4 elements in the SIMD modes for instructions that change by vector extension type.\n---------------------------------------------------------------------------------------------------------------------------\nThe fifth branch is an array 3 in size which branches to encoding's that change by the set size attribute.\n---------------------------------------------------------------------------------------------------------------------------\nEach branch can be combined in any combination, but only in order. If we branch to an array 2 in size under an specific opcode\nlike this [\"\",\"\"] then decide to branch memory mode to an array 4 in size we end up with [\"\",[\"\",\"\",\"\",\"\"]] for making it only\nactive in memory mode and controlled by SIMD modes, but then if we decide to branch one of the 4 SIMD modes to an array 8\nin size for register opcode separation under one SIMD mode, or an few we can't. We can only branch to an array 3 in size\nas that comes next after the array 4 in size. WE also do not need the first branch to be an array it can be an single opcode\nencoding. We also do not need the first branch to be an array 2 in size it can be any starting branch then the rest must go\nin order from that branch point.\n---------------------------------------------------------------------------------------------------------------------------\nOpcode is used by the function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^.\nThe function ^DecodeOpcode()^ Gives back the instructions name.\n--------------------------------------------------------------------------------------------------------------------------*/\n\nconst Mnemonics = [\n  /*------------------------------------------------------------------------------------------------------------------------\n  First Byte operations 0 to 255.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"ADD\",\"ADD\",\"ADD\",\"ADD\",\"ADD\",\"ADD\",\"PUSH ES\",\"POP ES\",\n  \"OR\",\"OR\",\"OR\",\"OR\",\"OR\",\"OR\",\"PUSH CS\"\n  ,\n  \"\" //*Two byte instructions prefix sets opcode 01,000000000 next byte read is added to the lower 8 bit's.\n  ,\n  \"ADC\",\"ADC\",\"ADC\",\"ADC\",\"ADC\",\"ADC\",\"PUSH SS\",\"POP SS\",\n  \"SBB\",\"SBB\",\"SBB\",\"SBB\",\"SBB\",\"SBB\",\"PUSH DS\",\"POP DS\",\n  \"AND\",\"AND\",\"AND\",\"AND\",\"AND\",\"AND\",\n  \"ES:[\", //Extra segment override sets SegOveride \"ES:[\".\n  \"DAA\",\n  \"SUB\",\"SUB\",\"SUB\",\"SUB\",\"SUB\",\"SUB\",\n  \"CS:[\", //Code segment override sets SegOveride \"CS:[\".\n  \"DAS\",\n  \"XOR\",\"XOR\",\"XOR\",\"XOR\",\"XOR\",\"XOR\",\n  \"SS:[\", //Stack segment override sets SegOveride \"SS:[\".\n  \"AAA\",\n  \"CMP\",\"CMP\",\"CMP\",\"CMP\",\"CMP\",\"CMP\",\n  \"DS:[\", //Data Segment override sets SegOveride \"DS:[\".\n  \"AAS\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  Start of Rex Prefix adjustment setting uses opcodes 40 to 4F. These opcodes are only decoded as adjustment settings\n  by the function ^DecodePrefixAdjustments()^ while in 64 bit mode. If not in 64 bit mode the codes are not read\n  by the function ^DecodePrefixAdjustments()^ which allows the opcode to be set 40 to 4F hex in which the defined\n  instructions bellow are used by ^DecodeOpcode()^.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"INC\",\"INC\",\"INC\",\"INC\",\"INC\",\"INC\",\"INC\",\"INC\",\n  \"DEC\",\"DEC\",\"DEC\",\"DEC\",\"DEC\",\"DEC\",\"DEC\",\"DEC\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  End of the Rex Prefix adjustment setting opcodes.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"PUSH\",\"PUSH\",\"PUSH\",\"PUSH\",\"PUSH\",\"PUSH\",\"PUSH\",\"PUSH\",\n  \"POP\",\"POP\",\"POP\",\"POP\",\"POP\",\"POP\",\"POP\",\"POP\",\n  [\"PUSHA\",\"PUSHAD\",\"\"],[\"POPA\",\"POPAD\",\"\"],\n  [\"BOUND\",\"BOUND\",\"\"], //EVEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined BOUND instruction is used.\n  \"MOVSXD\",\n  \"FS:[\",\"GS:[\", //Sets SegOveride \"FS:[\" next opcode sets \"GS:[\".\n  \"\",\"\", //Operand Size, and Address size adjustment to ModR/M.\n  \"PUSH\",\"IMUL\",\"PUSH\",\"IMUL\",\n  \"INS\",\"INS\",\"OUTS\",\"OUTS\",\n  \"JO\",\"JNO\",\"JB\",\"JAE\",\"JE\",\"JNE\",\"JBE\",\"JA\",\n  \"JS\",\"JNS\",\"JP\",\"JNP\",\"JL\",\"JGE\",\"JLE\",\"JG\",\n  [\"ADD\",\"OR\",\"ADC\",\"SBB\",\"AND\",\"SUB\",\"XOR\",\"CMP\"], //Group opcode uses the ModR/M register selection 0 though 7 giving 8 instruction in one opcode.\n  [\"ADD\",\"OR\",\"ADC\",\"SBB\",\"AND\",\"SUB\",\"XOR\",\"CMP\"],\n  [\"ADD\",\"OR\",\"ADC\",\"SBB\",\"AND\",\"SUB\",\"XOR\",\"CMP\"],\n  [\"ADD\",\"OR\",\"ADC\",\"SBB\",\"AND\",\"SUB\",\"XOR\",\"CMP\"],\n  \"TEST\",\"TEST\",\"XCHG\",\"XCHG\",\n  \"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\n  [\"LEA\",\"???\"], //*ModR/M Register, and memory mode separation.\n  \"MOV\",\n  [\"POP\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n  [[\"NOP\",\"\",\"\",\"\"],[\"NOP\",\"\",\"\",\"\"],[\"PAUSE\",\"\",\"\",\"\"],[\"NOP\",\"\",\"\",\"\"]],\n  \"XCHG\",\"XCHG\",\"XCHG\",\"XCHG\",\"XCHG\",\"XCHG\",\"XCHG\",\n  [\"CWDE\",\"CBW\",\"CDQE\"], //*Opcode 0 to 3 for instructions that change name by size setting.\n  [\"CDQ\",\"CWD\",\"CQO\"],\n  \"CALL\",\"WAIT\",\n  [\"PUSHFQ\",\"PUSHF\",\"PUSHFQ\"],\n  [\"POPFQ\",\"POPF\",\"POPFQ\"],\n  \"SAHF\",\"LAHF\",\n  \"MOV\",\"MOV\",\"MOV\",\"MOV\",\n  \"MOVS\",\"MOVS\",\n  \"CMPS\",\"CMPS\",\n  \"TEST\",\"TEST\",\n  \"STOS\",\"STOS\",\n  \"LODS\",\"LODS\",\n  \"SCAS\",\"SCAS\",\n  \"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\n  \"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\"MOV\",\n  [\"ROL\",\"ROR\",\"RCL\",\"RCR\",\"SHL\",\"SHR\",\"SAL\",\"SAR\"],\n  [\"ROL\",\"ROR\",\"RCL\",\"RCR\",\"SHL\",\"SHR\",\"SAL\",\"SAR\"],\n  \"RET\",\"RET\",\n  \"LES\", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used.\n  \"LDS\", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used.\n  [\n    \"MOV\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n    [\"XABORT\",\"XABORT\",\"XABORT\",\"XABORT\",\"XABORT\",\"XABORT\",\"XABORT\",\"XABORT\"]\n  ],\n  [\n    \"MOV\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n    [\"XBEGIN\",\"XBEGIN\",\"XBEGIN\",\"XBEGIN\",\"XBEGIN\",\"XBEGIN\",\"XBEGIN\",\"XBEGIN\"]\n  ],\n  \"ENTER\",\"LEAVE\",\"RETF\",\"RETF\",\"INT\",\"INT\",\"INTO\",\n  [\"IRETD\",\"IRET\",\"IRETQ\"],\n  [\"ROL\",\"ROR\",\"RCL\",\"RCR\",\"SHL\",\"SHR\",\"SAL\",\"SAR\"],\n  [\"ROL\",\"ROR\",\"RCL\",\"RCR\",\"SHL\",\"SHR\",\"SAL\",\"SAR\"],\n  [\"ROL\",\"ROR\",\"RCL\",\"RCR\",\"SHL\",\"SHR\",\"SAL\",\"SAR\"],\n  [\"ROL\",\"ROR\",\"RCL\",\"RCR\",\"SHL\",\"SHR\",\"SAL\",\"SAR\"],\n  \"AAMB\",\"AADB\",\"???\",\n  \"XLAT\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  X87 FPU.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [\n    [\"FADD\",\"FMUL\",\"FCOM\",\"FCOMP\",\"FSUB\",\"FSUBR\",\"FDIV\",\"FDIVR\"],\n    [\"FADD\",\"FMUL\",\"FCOM\",\"FCOMP\",\"FSUB\",\"FSUBR\",\"FDIV\",\"FDIVR\"]\n  ],\n  [\n    [\"FLD\",\"???\",\"FST\",\"FSTP\",\"FLDENV\",\"FLDCW\",\"FNSTENV\",\"FNSTCW\"],\n    [\n      \"FLD\",\"FXCH\",\n      [\"FNOP\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n      \"FSTP1\",\n      [\"FCHS\",\"FABS\",\"???\",\"???\",\"FTST\",\"FXAM\",\"???\",\"???\"],\n      [\"FLD1\",\"FLDL2T\",\"FLDL2E\",\"FLDPI\",\"FLDLG2\",\"FLDLN2\",\"FLDZ\",\"???\"],\n      [\"F2XM1\",\"FYL2X\",\"FPTAN\",\"FPATAN\",\"FXTRACT\",\"FPREM1\",\"FDECSTP\",\"FINCSTP\"],\n      [\"FPREM\",\"FYL2XP1\",\"FSQRT\",\"FSINCOS\",\"FRNDINT\",\"FSCALE\",\"FSIN\",\"FCOS\"]\n    ]\n  ],\n  [\n    [\"FIADD\",\"FIMUL\",\"FICOM\",\"FICOMP\",\"FISUB\",\"FISUBR\",\"FIDIV\",\"FIDIVR\"],\n    [\n      \"FCMOVB\",\"FCMOVE\",\"FCMOVBE\",\"FCMOVU\",\"???\",\n      [\"???\",\"FUCOMPP\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n      \"???\",\"???\"\n    ]\n  ],\n  [\n    [\"FILD\",\"FISTTP\",\"FIST\",\"FISTP\",\"???\",\"FLD\",\"???\",\"FSTP\"],\n    [\n      \"CMOVNB\",\"FCMOVNE\",\"FCMOVNBE\",\"FCMOVNU\",\n      [\"FENI\",\"FDISI\",\"FNCLEX\",\"FNINIT\",\"FSETPM\",\"???\",\"???\",\"???\"],\n      \"FUCOMI\",\"FCOMI\",\"???\"\n    ]\n  ],\n  [\n    [\"FADD\",\"FMUL\",\"FCOM\",\"DCOMP\",\"FSUB\",\"FSUBR\",\"FDIV\",\"FDIVR\"],\n    [\"FADD\",\"FMUL\",\"FCOM2\",\"FCOMP3\",\"FSUBR\",\"FSUB\",\"FDIVR\",\"FDIV\"]\n  ],\n  [\n    [\"FLD\",\"FISTTP\",\"FST\",\"FSTP\",\"FRSTOR\",\"???\",\"FNSAVE\",\"FNSTSW\"],\n    [\"FFREE\",\"FXCH4\",\"FST\",\"FSTP\",\"FUCOM\",\"FUCOMP\",\"???\",\"???\"]\n  ],\n  [\n    [\"FIADD\",\"FIMUL\",\"FICOM\",\"FICOMP\",\"FISUB\",\"FISUBR\",\"FIDIV\",\"FIDIVR\"],\n    [\n      \"FADDP\",\"FMULP\",\"FCOMP5\",\n      [\"???\",\"FCOMPP\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n      \"FSUBRP\",\"FSUBP\",\"FDIVRP\",\"FDIVP\"\n    ]\n  ],\n  [\n    [\"FILD\",\"FISTTP\",\"FIST\",\"FISTP\",\"FBLD\",\"FILD\",\"FBSTP\",\"FISTP\"],\n    [\n      \"FFREEP\",\"FXCH7\",\"FSTP8\",\"FSTP9\",\n      [\"FNSTSW\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n      \"FUCOMIP\",\"FCOMIP\",\"???\"\n    ]\n  ],\n  /*------------------------------------------------------------------------------------------------------------------------\n  End of X87 FPU.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"LOOPNE\",\"LOOPE\",\"LOOP\",\"JRCXZ\",\n  \"IN\",\"IN\",\"OUT\",\"OUT\",\n  \"CALL\",\"JMP\",\"JMP\",\"JMP\",\n  \"IN\",\"IN\",\"OUT\",\"OUT\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  The Repeat, and lock prefix opcodes apply to the next opcode.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"LOCK\", //Adds LOCK to the start of instruction. When Opcode F0 hex is read by function ^DecodePrefixAdjustments()^ sets PrefixG2 to LOCK.\n  \"ICEBP\", //Instruction ICEBP.\n  \"REPNE\", //Adds REPNE (Opcode F2 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REPNE.\n  \"REP\", //Adds REP (Opcode F3 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REP.\n  /*------------------------------------------------------------------------------------------------------------------------\n  End of Repeat, and lock instruction adjustment codes.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"HLT\",\"CMC\",\n  [\"TEST\",\"???\",\"NOT\",\"NEG\",\"MUL\",\"IMUL\",\"DIV\",\"IDIV\"],\n  [\"TEST\",\"???\",\"NOT\",\"NEG\",\"MUL\",\"IMUL\",\"DIV\",\"IDIV\"],\n  \"CLC\",\"STC\",\"CLI\",\"STI\",\"CLD\",\"STD\",\n  [\"INC\",\"DEC\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n  [\n    [\"INC\",\"DEC\",\"CALL\",\"CALL\",\"JMP\",\"JMP\",\"PUSH\",\"???\"],\n    [\"INC\",\"DEC\",\"CALL\",\"???\",\"JMP\",\"???\",\"PUSH\",\"???\"]\n  ],\n  /*------------------------------------------------------------------------------------------------------------------------\n  Two Byte Opcodes 256 to 511. Opcodes plus 256 goes to 511 used by escape code \"0F\", Or\n  set directly by adding map bits \"01\" because \"01 00000000\" bin = 256 plus opcode.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [\n    [\"SLDT\",\"STR\",\"LLDT\",\"LTR\",\"VERR\",\"VERW\",\"JMPE\",\"???\"],\n    [\"SLDT\",\"STR\",\"LLDT\",\"LTR\",\"VERR\",\"VERW\",\"JMPE\",\"???\"]\n  ],\n  [\n    [\"SGDT\",\"SIDT\",\"LGDT\",\"LIDT\",\"SMSW\",\"???\",\"LMSW\",\"INVLPG\"],\n    [\n      [\"???\",\"VMCALL\",\"VMLAUNCH\",\"VMRESUME\",\"VMXOFF\",\"???\",\"???\",\"???\"],\n      [\"MONITOR\",\"MWAIT\",\"CLAC\",\"STAC\",\"???\",\"???\",\"???\",\"ENCLS\"],\n      [\"XGETBV\",\"XSETBV\",\"???\",\"???\",\"VMFUNC\",\"XEND\",\"XTEST\",\"ENCLU\"],\n      [\"VMRUN\",\"VMMCALL\",\"VMLOAD\",\"VMSAVE\",\"STGI\",\"CLGI\",\"SKINIT\",\"INVLPGA\"],\n      \"SMSW\",\"???\",\"LMSW\",\n      [\"SWAPGS\",\"RDTSCP\",\"MONITORX\",\"MWAITX\",\"???\",\"???\",\"???\",\"???\"]\n    ]\n  ],\n  [\"LAR\",\"LAR\"],[\"LSL\",\"LSL\"],\"???\",\n  \"SYSCALL\",\"CLTS\",\"SYSRET\",\"INVD\",\n  \"WBINVD\",\"???\",\"UD2\",\"???\",\n  [[\"PREFETCH\",\"PREFETCHW\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\"???\"],\n  \"FEMMS\",\n  \"\", //3DNow Instruction name is encoded by the IMM8 operand.\n  [\n    [\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"],\n    [\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"]\n  ],\n  [\n    [\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"],\n    [\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"]\n  ],\n  [\n    [\"MOVLPS\",\"MOVLPD\",\"MOVSLDUP\",\"MOVDDUP\"],\n    [\"MOVHLPS\",\"???\",\"MOVSLDUP\",\"MOVDDUP\"]\n  ],\n  [[\"MOVLPS\",\"MOVLPD\",\"???\",\"???\"],\"???\"],\n  [\"UNPCKLPS\",\"UNPCKLPD\",\"???\",\"???\"], //An instruction with 4 operations uses the 4 SIMD modes as an Vector instruction.\n  [\"UNPCKHPS\",\"UNPCKHPD\",\"???\",\"???\"],\n  [[\"MOVHPS\",\"MOVHPD\",\"MOVSHDUP\",\"???\"],[\"MOVLHPS\",\"???\",\"MOVSHDUP\",\"???\"]],\n  [[\"MOVHPS\",\"MOVHPD\",\"???\",\"???\"],\"???\"],\n  [[\"PREFETCHNTA\",\"PREFETCHT0\",\"PREFETCHT1\",\"PREFETCHT2\",\"???\",\"???\",\"???\",\"???\"],\"???\"],\n  \"???\",\n  [[[\"BNDLDX\",\"\",\"\",\"\"],[\"BNDMOV\",\"\",\"\",\"\"],[\"BNDCL\",\"\",\"\",\"\"],[\"BNDCU\",\"\",\"\",\"\"]],\n  [\"???\",[\"BNDMOV\",\"\",\"\",\"\"],[\"BNDCL\",\"\",\"\",\"\"],[\"BNDCU\",\"\",\"\",\"\"]]],\n  [[[\"BNDSTX\",\"\",\"\",\"\"],[\"BNDMOV\",\"\",\"\",\"\"],[\"BNDMK\",\"\",\"\",\"\"],[\"BNDCN\",\"\",\"\",\"\"]],\n  [\"???\",[\"BNDMOV\",\"\",\"\",\"\"],\"???\",[\"BNDCN\",\"\",\"\",\"\"]]],\n  \"???\",\"???\",\"???\",\n  \"NOP\",\n  [\"???\",\"MOV\"],[\"???\",\"MOV\"], //CR and DR register Move\n  [\"???\",\"MOV\"],[\"???\",\"MOV\"], //CR and DR register Move\n  [\"???\",\"MOV\"],\"???\", //TR (TEST REGISTER) register Move\n  [\"???\",\"MOV\"],\"???\", //TR (TEST REGISTER) register Move\n  [\n    [\"MOVAPS\",\"MOVAPS\",\"MOVAPS\",\"MOVAPS\"],\n    [\"MOVAPD\",\"MOVAPD\",\"MOVAPD\",\"MOVAPD\"],\n    \"???\",\"???\"\n  ],\n  [\n    [\n      [\"MOVAPS\",\"MOVAPS\",\"MOVAPS\",\"MOVAPS\"],\n      [\"MOVAPD\",\"MOVAPD\",\"MOVAPD\",\"MOVAPD\"],\n      [\"\",\"\",\"\",[\"MOVNRAPS\",\"MOVNRNGOAPS\",\"MOVNRAPS\"]],\n      [\"\",\"\",\"\",[\"MOVNRAPD\",\"MOVNRNGOAPD\",\"MOVNRAPD\"]]\n    ],\n    [\n      [\"MOVAPS\",\"MOVAPS\",\"MOVAPS\",\"MOVAPS\"],\n      [\"MOVAPD\",\"MOVAPD\",\"MOVAPD\",\"MOVAPD\"],\n      \"???\",\"???\"\n    ]\n  ],\n  [\n    [\"CVTPI2PS\",\"\",\"\",\"\"],[\"CVTPI2PD\",\"\",\"\",\"\"], //Is not allowed to be Vector encoded.\n    \"CVTSI2SS\",\"CVTSI2SD\"\n  ],\n  [\n    [\n      \"MOVNTPS\",\"MOVNTPD\",\n      [\"MOVNTSS\",\"\",\"\",\"\"],[\"MOVNTSD\",\"\",\"\",\"\"] //SSE4a can not be vector encoded.\n    ],\"???\"\n  ],\n  [\n    [\"CVTTPS2PI\",\"\",\"\",\"\"],[\"CVTTPD2PI\",\"\",\"\",\"\"], //Is not allowed to be Vector encoded.\n    \"CVTTSS2SI\",\"CVTTSD2SI\"\n  ],\n  [\n    [\"CVTPS2PI\",\"\",\"\",\"\"],[\"CVTPD2PI\",\"\",\"\",\"\"], //Is not allowed to be Vector encoded.\n    \"CVTSS2SI\",\"CVTSD2SI\"\n  ],\n  [\"UCOMISS\",\"UCOMISD\",\"???\",\"???\"],\n  [\"COMISS\",\"COMISD\",\"???\",\"???\"],\n  \"WRMSR\",\"RDTSC\",\"RDMSR\",\"RDPMC\",\n  \"SYSENTER\",\"SYSEXIT\",\"???\",\n  \"GETSEC\",\n  \"\", //*Three byte instructions prefix combo 0F 38 (Opcode = 01,00111000) sets opcode 10,000000000 next byte read is added to the lower 8 bit's.\n  \"???\",\n  \"\", //*Three byte instructions prefix combo 0F 3A (Opcode = 01,00111010) sets opcode 11,000000000 next byte read is added to the lower 8 bit's.\n  \"???\",\"???\",\"???\",\"???\",\"???\",\n  \"CMOVO\",\n  [\n    [\"CMOVNO\",[\"KANDW\",\"\",\"KANDQ\"],\"\",\"\"],\n    [\"CMOVNO\",[\"KANDB\",\"\",\"KANDD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"CMOVB\",[\"KANDNW\",\"\",\"KANDNQ\"],\"\",\"\"],\n    [\"CMOVB\",[\"KANDNB\",\"\",\"KANDND\"],\"\",\"\"],\"\",\"\"\n  ],\n  [[\"CMOVAE\",\"KANDNR\",\"\",\"\"],\"\",\"\",\"\"],\n  [\n    [\"CMOVE\",[\"KNOTW\",\"\",\"KNOTQ\"],\"\",\"\"],\n    [\"CMOVE\",[\"KNOTB\",\"\",\"KNOTD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"CMOVNE\",[\"KORW\",\"\",\"KORQ\"],\"\",\"\"],\n    [\"CMOVNE\",[\"KORB\",\"\",\"KORD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"CMOVBE\",[\"KXNORW\",\"\",\"KXNORQ\"],\"\",\"\"],\n    [\"CMOVBE\",[\"KXNORB\",\"\",\"KXNORD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"CMOVA\",[\"KXORW\",\"\",\"KXORQ\"],\"\",\"\"],\n    [\"CMOVA\",[\"KXORB\",\"\",\"KXORD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [[\"CMOVS\",\"KMERGE2L1H\",\"\",\"\"],\"\",\"\",\"\"],\n  [[\"CMOVNS\",\"KMERGE2L1L\",\"\",\"\"],\"\",\"\",\"\"],\n  [\n    [\"CMOVP\",[\"KADDW\",\"\",\"KADDQ\"],\"\",\"\"],\n    [\"CMOVP\",[\"KADDB\",\"\",\"KADDD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"CMOVNP\",[\"KUNPCKWD\",\"\",\"KUNPCKDQ\"],\"\",\"\"],\n    [\"CMOVNP\",[\"KUNPCKBW\",\"\",\"???\"],\"\",\"\"],\"\",\"\"\n  ],\n  \"CMOVL\",\"CMOVGE\",\"CMOVLE\",\"CMOVG\",\n  [\n    \"???\",\n    [\n      [\"MOVMSKPS\",\"MOVMSKPS\",\"\",\"\"],[\"MOVMSKPD\",\"MOVMSKPD\",\"\",\"\"],\n      \"???\",\"???\"\n    ]\n  ],\n  [\"SQRTPS\",\"SQRTPD\",\"SQRTSS\",\"SQRTSD\"],\n  [\n    [\"RSQRTPS\",\"RSQRTPS\",\"\",\"\"],\"???\",\n    [\"RSQRTSS\",\"RSQRTSS\",\"\",\"\"],\"???\"\n  ],\n  [\n    [\"RCPPS\",\"RCPPS\",\"\",\"\"],\"???\",\n    [\"RCPSS\",\"RCPSS\",\"\",\"\"],\"???\"\n  ],\n  [\"ANDPS\",\"ANDPD\",\"???\",\"???\"],\n  [\"ANDNPS\",\"ANDNPD\",\"???\",\"???\"],\n  [\"ORPS\",\"ORPD\",\"???\",\"???\"],\n  [\"XORPS\",\"XORPD\",\"???\",\"???\"],\n  [\n    [\"ADDPS\",\"ADDPS\",\"ADDPS\",\"ADDPS\"],\n    [\"ADDPD\",\"ADDPD\",\"ADDPD\",\"ADDPD\"],\n    \"ADDSS\",\"ADDSD\"\n  ],\n  [\n    [\"MULPS\",\"MULPS\",\"MULPS\",\"MULPS\"],\n    [\"MULPD\",\"MULPD\",\"MULPD\",\"MULPD\"],\n    \"MULSS\",\"MULSD\"\n  ],\n  [\n    [\"CVTPS2PD\",\"CVTPS2PD\",\"CVTPS2PD\",\"CVTPS2PD\"],\n    [\"CVTPD2PS\",\"CVTPD2PS\",\"CVTPD2PS\",\"CVTPD2PS\"],\n    \"CVTSS2SD\",\"CVTSD2SS\"\n  ],\n  [[\"CVTDQ2PS\",\"\",\"CVTQQ2PS\"],[\"CVTPS2DQ\",\"\",\"???\"],\"CVTTPS2DQ\",\"???\"],\n  [\n    [\"SUBPS\",\"SUBPS\",\"SUBPS\",\"SUBPS\"],\n    [\"SUBPD\",\"SUBPD\",\"SUBPD\",\"SUBPD\"],\n    \"SUBSS\",\"SUBSD\"\n  ],\n  [\"MINPS\",\"MINPD\",\"MINSS\",\"MINSD\"],\n  [\"DIVPS\",\"DIVPD\",\"DIVSS\",\"DIVSD\"],\n  [\"MAXPS\",\"MAXPD\",\"MAXSS\",\"MAXSD\"],\n  [[\"PUNPCKLBW\",\"\",\"\",\"\"],\"PUNPCKLBW\",\"\",\"\"],\n  [[\"PUNPCKLWD\",\"\",\"\",\"\"],\"PUNPCKLWD\",\"\",\"\"],\n  [[\"PUNPCKLDQ\",\"\",\"\",\"\"],\"PUNPCKLDQ\",\"\",\"\"],\n  [[\"PACKSSWB\",\"\",\"\",\"\"],\"PACKSSWB\",\"\",\"\"],\n  [[\"PCMPGTB\",\"\",\"\",\"\"],[\"PCMPGTB\",\"PCMPGTB\",\"PCMPGTB\",\"\"],\"\",\"\"],\n  [[\"PCMPGTW\",\"\",\"\",\"\"],[\"PCMPGTW\",\"PCMPGTW\",\"PCMPGTW\",\"\"],\"\",\"\"],\n  [[\"PCMPGTD\",\"\",\"\",\"\"],[\"PCMPGTD\",\"PCMPGTD\",[\"PCMPGTD\",\"\",\"???\"],[\"PCMPGTD\",\"\",\"???\"]],\"\",\"\"],\n  [[\"PACKUSWB\",\"\",\"\",\"\"],\"PACKUSWB\",\"\",\"\"],\n  [[\"PUNPCKHBW\",\"\",\"\",\"\"],\"PUNPCKHBW\",\"\",\"\"],\n  [[\"PUNPCKHWD\",\"\",\"\",\"\"],\"PUNPCKHWD\",\"\",\"\"],\n  [[\"PUNPCKHDQ\",\"\",\"\",\"\"],[\"PUNPCKHDQ\",\"\",\"???\"],\"\",\"\"],\n  [[\"PACKSSDW\",\"\",\"\",\"\"],[\"PACKSSDW\",\"\",\"???\"],\"\",\"\"],\n  [\"???\",\"PUNPCKLQDQ\",\"???\",\"???\"],\n  [\"???\",\"PUNPCKHQDQ\",\"???\",\"???\"],\n  [[\"MOVD\",\"\",\"\",\"\"],[\"MOVD\",\"\",\"MOVQ\"],\"\",\"\"],\n  [\n    [\n      [\"MOVQ\",\"\",\"\",\"\"],\n      [\"MOVDQA\",\"MOVDQA\",[\"MOVDQA32\",\"\",\"MOVDQA64\"],[\"MOVDQA32\",\"\",\"MOVDQA64\"]],\n      [\"MOVDQU\",\"MOVDQU\",[\"MOVDQU32\",\"\",\"MOVDQU64\"],\"\"],\n      [\"\",\"\",[\"MOVDQU8\",\"\",\"MOVDQU16\"],\"\"]\n    ],\n    [\n      [\"MOVQ\",\"\",\"\",\"\"],\n      [\"MOVDQA\",\"MOVDQA\",[\"MOVDQA32\",\"\",\"MOVDQA64\"],[\"MOVDQA32\",\"\",\"MOVDQA64\"]],\n      [\"MOVDQU\",\"MOVDQU\",[\"MOVDQU32\",\"\",\"MOVDQU64\"],\"\"],\n      [\"\",\"\",[\"MOVDQU8\",\"\",\"MOVDQU16\"],\"\"]\n    ]\n  ],\n  [\n    [\"PSHUFW\",\"\",\"\",\"\"],\n    [\"PSHUFD\",\"PSHUFD\",[\"PSHUFD\",\"\",\"???\"],[\"PSHUFD\",\"\",\"???\"]],\n    \"PSHUFHW\",\n    \"PSHUFLW\"\n  ],\n  [\n    \"???\",\n    [\n      \"???\",\"???\",\n      [[\"PSRLW\",\"\",\"\",\"\"],\"PSRLW\",\"\",\"\"],\"???\",\n      [[\"PSRAW\",\"\",\"\",\"\"],\"PSRAW\",\"\",\"\"],\"???\",\n      [[\"PSLLW\",\"\",\"\",\"\"],\"PSLLW\",\"\",\"\"],\"???\"\n    ]\n  ],\n  [\n    [\"???\",[\"\",\"\",[\"PRORD\",\"\",\"PRORQ\"],\"\"],\"???\",\"???\"],\n    [\"???\",[\"\",\"\",[\"PROLD\",\"\",\"PROLQ\"],\"\"],\"???\",\"???\"],\n    [[\"PSRLD\",\"\",\"\",\"\"],[\"PSRLD\",\"PSRLD\",[\"PSRLD\",\"\",\"???\"],[\"PSRLD\",\"\",\"???\"]],\"\",\"\"],\n    \"???\",\n    [[\"PSRAD\",\"\",\"\",\"\"],[\"PSRAD\",\"PSRAD\",[\"PSRAD\",\"\",\"PSRAQ\"],[\"PSRAD\",\"\",\"???\"]],\"\",\"\"],\n    \"???\",\n    [[\"PSLLD\",\"\",\"\",\"\"],[\"PSLLD\",\"PSLLD\",[\"PSLLD\",\"\",\"???\"],[\"PSLLD\",\"\",\"???\"]],\"\",\"\"],\n    \"???\"\n  ],\n  [\n    \"???\",\n    [\n      \"???\",\"???\",\n      [[\"PSRLQ\",\"PSRLQ\",\"\",\"\"],\"PSRLQ\",\"\",\"\"],[\"???\",\"PSRLDQ\",\"???\",\"???\"],\n      \"???\",\"???\",\n      [[\"PSLLQ\",\"PSLLQ\",\"\",\"\"],\"PSLLQ\",\"\",\"\"],[\"???\",\"PSLLDQ\",\"???\",\"???\"]\n    ]\n  ],\n  [[\"PCMPEQB\",\"\",\"\",\"\"],[\"PCMPEQB\",\"PCMPEQB\",\"PCMPEQB\",\"\"],\"\",\"\"],\n  [[\"PCMPEQW\",\"\",\"\",\"\"],[\"PCMPEQW\",\"PCMPEQW\",\"PCMPEQW\",\"\"],\"\",\"\"],\n  [[\"PCMPEQD\",\"\",\"\",\"\"],[\"PCMPEQD\",\"PCMPEQD\",[\"PCMPEQD\",\"\",\"???\"],[\"PCMPEQD\",\"\",\"???\"]],\"\",\"\"],\n  [[\"EMMS\",[\"ZEROUPPER\",\"ZEROALL\",\"\"],\"\",\"\"],\"???\",\"???\",\"???\"],\n  [\n    [\"VMREAD\",\"\",[\"CVTTPS2UDQ\",\"\",\"CVTTPD2UDQ\"],\"\"],\n    [\"EXTRQ\",\"\",[\"CVTTPS2UQQ\",\"\",\"CVTTPD2UQQ\"],\"\"],\n    [\"???\",\"\",\"CVTTSS2USI\",\"\"],\n    [\"INSERTQ\",\"\",\"CVTTSD2USI\",\"\"]\n  ],\n  [\n    [\"VMWRITE\",\"\",[\"CVTPS2UDQ\",\"\",\"CVTPD2UDQ\"], \"\"],\n    [\"EXTRQ\",\"\",[\"CVTPS2UQQ\",\"\",\"CVTPD2UQQ\"],\"\"],\n    [\"???\",\"\",\"CVTSS2USI\",\"\"],\n    [\"INSERTQ\",\"\",\"CVTSD2USI\",\"\"]\n  ],\n  [\n    \"???\",\n    [\"\",\"\",[\"CVTTPS2QQ\",\"\",\"CVTTPD2QQ\"],\"\"],\n    [\"\",\"\",[\"CVTUDQ2PD\",\"\",\"CVTUQQ2PD\"],\"CVTUDQ2PD\"],\n    [\"\",\"\",[\"CVTUDQ2PS\",\"\",\"CVTUQQ2PS\"],\"\"]\n  ],\n  [\n    \"???\",\n    [\"\",\"\",[\"CVTPS2QQ\",\"\",\"CVTPD2QQ\"],\"\"],\n    [\"\",\"\",\"CVTUSI2SS\",\"\"],\n    [\"\",\"\",\"CVTUSI2SD\",\"\"]\n  ],\n  [\n    \"???\",[\"HADDPD\",\"HADDPD\",\"\",\"\"],\n    \"???\",[\"HADDPS\",\"HADDPS\",\"\",\"\"]\n  ],\n  [\n    \"???\",[\"HSUBPD\",\"HSUBPD\",\"\",\"\"],\n    \"???\",[\"HSUBPS\",\"HSUBPS\",\"\",\"\"]\n  ],\n  [[\"MOVD\",\"\",\"\",\"\"],[\"MOVD\",\"\",\"MOVQ\"],[\"MOVQ\",\"MOVQ\",[\"???\",\"\",\"MOVQ\"],\"\"],\"???\"],\n  [\n    [\"MOVQ\",\"\",\"\",\"\"],\n    [\"MOVDQA\",\"MOVDQA\",[\"MOVDQA32\",\"\",\"MOVDQA64\"],[\"MOVDQA32\",\"\",\"MOVDQA64\"]],\n    [\"MOVDQU\",\"MOVDQU\",[\"MOVDQU32\",\"\",\"MOVDQU64\"],\"\"],\n    [\"???\",\"\",[\"MOVDQU8\",\"\",\"MOVDQU16\"],\"\"]\n  ],\n  \"JO\",\"JNO\",\"JB\",\"JAE\",\n  [[\"JE\",\"JKZD\",\"\",\"\"],\"\",\"\",\"\"],[[\"JNE\",\"JKNZD\",\"\",\"\"],\"\",\"\",\"\"], //K1OM.\n  \"JBE\",\"JA\",\"JS\",\"JNS\",\"JP\",\"JNP\",\"JL\",\"JGE\",\"JLE\",\"JG\",\n  [\n    [\"SETO\",[\"KMOVW\",\"\",\"KMOVQ\"],\"\",\"\"],\n    [\"SETO\",[\"KMOVB\",\"\",\"KMOVD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"SETNO\",[\"KMOVW\",\"\",\"KMOVQ\"],\"\",\"\"],\n    [\"SETNO\",[\"KMOVB\",\"\",\"KMOVD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"SETB\",[\"KMOVW\",\"\",\"???\"],\"\",\"\"],\n    [\"SETB\",[\"KMOVB\",\"\",\"???\"],\"\",\"\"],\"\",\n    [\"SETB\",[\"KMOVD\",\"\",\"KMOVQ\"],\"\",\"\"]\n  ],\n  [\n    [\"SETAE\",[\"KMOVW\",\"\",\"???\"],\"\",\"\"],\n    [\"SETAE\",[\"KMOVB\",\"\",\"???\"],\"\",\"\"],\"\",\n    [\"SETAE\",[\"KMOVD\",\"\",\"KMOVQ\"],\"\",\"\"]\n  ],\n  \"SETE\",[[\"SETNE\",\"KCONCATH\",\"\",\"\"],\"\",\"\",\"\"],\n  \"SETBE\",[[\"SETA\",\"KCONCATL\",\"\",\"\"],\"\",\"\",\"\"],\n  [\n    [\"SETS\",[\"KORTESTW\",\"\",\"KORTESTQ\"],\"\",\"\"],\n    [\"SETS\",[\"KORTESTB\",\"\",\"KORTESTD\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"SETNS\",[\"KTESTW\",\"\",\"KTESTQ\"],\"\",\"\"],\n    [\"SETNS\",[\"KTESTB\",\"\",\"KTESTD\"],\"\",\"\"],\"\",\"\"\n  ],\n  \"SETP\",\"SETNP\",\"SETL\",\"SETGE\",\"SETLE\",\"SETG\",\n  \"PUSH\",\"POP\",\n  \"CPUID\", //Identifies the CPU and which Instructions the current CPU can use.\n  \"BT\",\n  \"SHLD\",\"SHLD\",\n  \"XBTS\",\"IBTS\",\n  \"PUSH\",\"POP\",\n  \"RSM\",\n  \"BTS\",\n  \"SHRD\",\"SHRD\",\n  [\n    [\n      [\"FXSAVE\",\"???\",\"FXSAVE64\"],[\"FXRSTOR\",\"???\",\"FXRSTOR64\"],\n      \"LDMXCSR\",\"STMXCSR\",\n      [\"XSAVE\",\"\",\"XSAVE64\"],[\"XRSTOR\",\"\",\"XRSTOR64\"],\n      [\"XSAVEOPT\",\"CLWB\",\"XSAVEOPT64\"],\n      [\"CLFLUSHOPT\",\"CLFLUSH\",\"\"]\n    ],\n    [\n      [\"???\",\"???\",[\"RDFSBASE\",\"\",\"\",\"\"],\"???\"],[\"???\",\"???\",[\"RDGSBASE\",\"\",\"\",\"\"],\"???\"],\n      [\"???\",\"???\",[\"WRFSBASE\",\"\",\"\",\"\"],\"???\"],[\"???\",\"???\",[\"WRGSBASE\",\"\",\"\",\"\"],\"???\"],\n      \"???\",\n      [\"LFENCE\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n      [\"MFENCE\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n      [\"SFENCE\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"]\n    ]\n  ],\n  \"IMUL\",\n  \"CMPXCHG\",\"CMPXCHG\",\n  [\"LSS\",\"???\"],\n  \"BTR\",\n  [\"LFS\",\"???\"],\n  [\"LGS\",\"???\"],\n  \"MOVZX\",\"MOVZX\",\n  [\n    [\"JMPE\",\"\",\"\",\"\"],\"???\",\n    [\"POPCNT\",\"POPCNT\",\"\",\"\"],\"???\"\n  ],\n  \"???\",\n  [\"???\",\"???\",\"???\",\"???\",\"BT\",\"BTS\",\"BTR\",\"BTC\"],\n  \"BTC\",\n  [\n    [\"BSF\",\"\",\"\",\"\"],\"???\",\n    [\"TZCNT\",\"TZCNT\",\"\",\"\"],[\"BSF\",\"TZCNTI\",\"\",\"\"]\n  ],\n  [\n    [\"BSR\",\"\",\"\",\"\"],\"???\",\n    [\"LZCNT\",\"LZCNT\",\"\",\"\"],[\"BSR\",\"\",\"\",\"\"]\n  ],\n  \"MOVSX\",\"MOVSX\",\n  \"XADD\",\"XADD\",\n  [\n    [\"CMP,PS,\",\"CMP,PS,\",\"CMP,PS,\",\"CMP,PS,\"],\n    [\"CMP,PD,\",\"CMP,PD,\",\"CMP,PD,\",\"CMP,PD,\"],\n    [\"CMP,SS,\",\"CMP,SS,\",\"CMP,SS,\",\"\"],\n    [\"CMP,SD,\",\"CMP,SD,\",\"CMP,SD,\",\"\"]\n  ],\n  [\"MOVNTI\",\"???\"],\n  [[\"PINSRW\",\"\",\"\",\"\"],\"PINSRW\",\"\",\"\"],\n  [\"???\",[[\"PEXTRW\",\"\",\"\",\"\"],\"PEXTRW\",\"\",\"\"]],\n  [\"SHUFPS\",\"SHUFPD\",\"???\",\"???\"],\n  [\n    [\n      \"???\",\n      [\"CMPXCHG8B\",\"\",\"CMPXCHG16B\"],\n      \"???\",\n      [\"XRSTORS\",\"\",\"XRSTORS64\"],\n      [\"XSAVEC\",\"\",\"XSAVEC64\"],\n      [\"XSAVES\",\"\",\"XSAVES64\"],\n      [\"VMPTRLD\",\"VMCLEAR\",\"VMXON\",\"???\"],[\"VMPTRST\",\"???\",\"???\",\"???\"]\n    ],\n    [\n      \"???\",\n      [\"SSS\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"], //Synthetic virtual machine operation codes.\n      \"???\",\"???\",\"???\",\"???\",\n      \"RDRAND\",\"RDSEED\"\n    ]\n  ],\n  \"BSWAP\",\"BSWAP\",\"BSWAP\",\"BSWAP\",\"BSWAP\",\"BSWAP\",\"BSWAP\",\"BSWAP\",\n  [\"???\",[\"ADDSUBPD\",\"ADDSUBPD\",\"\",\"\"],\"???\",[\"ADDSUBPS\",\"ADDSUBPS\",\"\",\"\"]],\n  [[\"PSRLW\",\"\",\"\",\"\"],\"PSRLW\",\"\",\"\"],\n  [[\"PSRLD\",\"\",\"\",\"\"],[\"PSRLD\",\"PSRLD\",[\"PSRLD\",\"\",\"???\"],\"\"],\"\",\"\"],\n  [[\"PSRLQ\",\"\",\"\",\"\"],\"PSRLQ\",\"\",\"\"],\n  [[\"PADDQ\",\"\",\"\",\"\"],\"PADDQ\",\"\",\"\"],\n  [[\"PMULLW\",\"\",\"\",\"\"],\"PMULLW\",\"\",\"\"],\n  [\n    [\"???\",\"MOVQ\",\"???\",\"???\"],\n    [\"???\",\"MOVQ\",[\"MOVQ2DQ\",\"\",\"\",\"\"],[\"MOVDQ2Q\",\"\",\"\",\"\"]]\n  ],\n  [\"???\",[[\"PMOVMSKB\",\"\",\"\",\"\"],[\"PMOVMSKB\",\"PMOVMSKB\",\"\",\"\"],\"???\",\"???\"]],\n  [[\"PSUBUSB\",\"\",\"\",\"\"],\"PSUBUSB\",\"\",\"\"],\n  [[\"PSUBUSW\",\"\",\"\",\"\"],\"PSUBUSW\",\"\",\"\"],\n  [[\"PMINUB\",\"\",\"\",\"\"],\"PMINUB\",\"\",\"\"],\n  [[\"PAND\",\"\",\"\",\"\"],[\"PAND\",\"PAND\",[\"PANDD\",\"\",\"PANDQ\"],[\"PANDD\",\"\",\"PANDQ\"]],\"\",\"\"],\n  [[\"PADDUSB\",\"\",\"\",\"\"],\"PADDUSB\",\"\",\"\"],\n  [[\"PADDUSW\",\"\",\"\",\"\"],\"PADDUSW\",\"\",\"\"],\n  [[\"PMAXUB\",\"\",\"\",\"\"],\"PMAXUB\",\"\",\"\"],\n  [[\"PANDN\",\"\",\"\",\"\"],[\"PANDN\",\"PANDN\",[\"PANDND\",\"\",\"PANDNQ\"],[\"PANDND\",\"\",\"PANDNQ\"]],\"\",\"\"],\n  [[\"PAVGB\",\"\",\"\",\"\"],\"PAVGB\",\"\",\"\"],\n  [\n    [[\"PSRAW\",\"\",\"\",\"\"],[\"PSRAW\",\"PSRAW\",\"PSRAW\",\"\"],\"\",\"\"],\n    [[\"PSRAW\",\"\",\"\",\"\"],[\"PSRAW\",\"PSRAW\",\"PSRAW\",\"\"],\"\",\"\"]\n  ],\n  [[\"PSRAD\",\"\",\"\",\"\"],[\"PSRAD\",\"PSRAD\",[\"PSRAD\",\"\",\"PSRAQ\"],\"\"],\"\",\"\"],\n  [[\"PAVGW\",\"\",\"\",\"\"],\"PAVGW\",\"\",\"\"],\n  [[\"PMULHUW\",\"\",\"\",\"\"],\"PMULHUW\",\"\",\"\"],\n  [[\"PMULHW\",\"\",\"\",\"\"],\"PMULHW\",\"\",\"\"],\n  [\n    \"???\",\n    [\"CVTTPD2DQ\",\"CVTTPD2DQ\",\"CVTTPD2DQ\",\"\"],\n    [\"CVTDQ2PD\",\"CVTDQ2PD\",[\"CVTDQ2PD\",\"CVTDQ2PD\",\"CVTQQ2PD\"],\"CVTDQ2PD\"],\n    \"CVTPD2DQ\"\n  ],\n  [[[\"MOVNTQ\",\"\",\"\",\"\"],[\"MOVNTDQ\",\"\",\"???\"],\"???\",\"???\"],\"???\"],\n  [[\"PSUBSB\",\"\",\"\",\"\"],\"PSUBSB\",\"\",\"\"],\n  [[\"PSUBSW\",\"\",\"\",\"\"],\"PSUBSW\",\"\",\"\"],\n  [[\"PMINSW\",\"\",\"\",\"\"],\"PMINSW\",\"\",\"\"],\n  [[\"POR\",\"\",\"\",\"\"],[\"POR\",\"POR\",[\"PORD\",\"\",\"PORQ\"],[\"PORD\",\"\",\"PORQ\"]],\"\",\"\"],\n  [[\"PADDSB\",\"\",\"\",\"\"],\"PADDSB\",\"\",\"\"],\n  [[\"PADDSW\",\"\",\"\",\"\"],\"PADDSW\",\"\",\"\"],\n  [[\"PMAXSW\",\"\",\"\",\"\"],\"PMAXSW\",\"\",\"\"],\n  [[\"PXOR\",\"\",\"\",\"\"],[\"PXOR\",\"PXOR\",[\"PXORD\",\"\",\"PXORQ\"],[\"PXORD\",\"\",\"PXORQ\"]],\"\",\"\"],\n  [[\"???\",\"???\",\"???\",[\"LDDQU\",\"LDDQU\",\"\",\"\"]],\"???\"],\n  [[\"PSLLW\",\"\",\"\",\"\"],\"PSLLW\",\"\",\"\"],\n  [[\"PSLLD\",\"\",\"\",\"\"],[\"PSLLD\",\"\",\"???\"],\"\",\"\"],\n  [[\"PSLLQ\",\"\",\"\",\"\"],\"PSLLQ\",\"\",\"\"],\n  [[\"PMULUDQ\",\"\",\"\",\"\"],\"PMULUDQ\",\"\",\"\"],\n  [[\"PMADDWD\",\"\",\"\",\"\"],\"PMADDWD\",\"\",\"\"],\n  [[\"PSADBW\",\"\",\"\",\"\"],\"PSADBW\",\"\",\"\"],\n  [\"???\",[[\"MASKMOVQ\",\"\",\"\",\"\"],[\"MASKMOVDQU\",\"MASKMOVDQU\",\"\",\"\"],\"???\",\"???\"]],\n  [[\"PSUBB\",\"\",\"\",\"\"],\"PSUBB\",\"\",\"\"],\n  [[\"PSUBW\",\"\",\"\",\"\"],\"PSUBW\",\"\",\"\"],\n  [[\"PSUBD\",\"\",\"\",\"\"],[\"PSUBD\",\"PSUBD\",[\"PSUBD\",\"\",\"???\"],[\"PSUBD\",\"\",\"???\"]],\"\",\"\"],\n  [[\"PSUBQ\",\"\",\"\",\"\"],\"PSUBQ\",\"\",\"\"],\n  [[\"PADDB\",\"\",\"\",\"\"],\"PADDB\",\"\",\"\"],\n  [[\"PADDW\",\"\",\"\",\"\"],\"PADDW\",\"\",\"\"],\n  [[\"PADDD\",\"\",\"\",\"\"],[\"PADDD\",\"PADDD\",[\"PADDD\",\"\",\"???\"],[\"PADDD\",\"\",\"???\"]],\"\",\"\"],\n  \"???\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  Three Byte operations 0F38. Opcodes plus 512 goes to 767 used by escape codes \"0F,38\", Or\n  set directly by adding map bits \"10\" because \"10 00000000\" bin = 512 plus opcode.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [[\"PSHUFB\",\"\",\"\",\"\"],\"PSHUFB\",\"???\",\"???\"],\n  [[\"PHADDW\",\"\",\"\",\"\"],[\"PHADDW\",\"PHADDW\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PHADDD\",\"\",\"\",\"\"],[\"PHADDD\",\"PHADDD\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PHADDSW\",\"\",\"\",\"\"],[\"PHADDSW\",\"PHADDSW\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PMADDUBSW\",\"\",\"\",\"\"],\"PMADDUBSW\",\"???\",\"???\"],\n  [[\"PHSUBW\",\"\",\"\",\"\"],[\"PHSUBW\",\"PHSUBW\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PHSUBD\",\"\",\"\",\"\"],[\"PHSUBD\",\"PHSUBD\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PHSUBSW\",\"\",\"\",\"\"],[\"PHSUBSW\",\"PHSUBSW\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PSIGNB\",\"\",\"\",\"\"],[\"PSIGNB\",\"PSIGNB\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PSIGNW\",\"\",\"\",\"\"],[\"PSIGNW\",\"PSIGNW\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PSIGND\",\"\",\"\",\"\"],[\"PSIGND\",\"PSIGND\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PMULHRSW\",\"\",\"\",\"\"],\"PMULHRSW\",\"???\",\"???\"],\n  [\"???\",[\"\",\"PERMILPS\",[\"PERMILPS\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"PERMILPD\",\"PERMILPD\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"TESTPS\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"TESTPD\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"PBLENDVB\",\"PBLENDVB\",\"PSRLVW\",\"\"],[\"\",\"\",\"PMOVUSWB\",\"\"],\"???\"],\n  [\"???\",[\"\",\"\",\"PSRAVW\",\"\"],[\"\",\"\",\"PMOVUSDB\",\"\"],\"???\"],\n  [\"???\",[\"\",\"\",\"PSLLVW\",\"\"],[\"\",\"\",\"PMOVUSQB\",\"\"],\"???\"],\n  [\"???\",[\"\",\"CVTPH2PS\",[\"CVTPH2PS\",\"\",\"???\"],\"\"],[\"\",\"\",\"PMOVUSDW\",\"\"],\"???\"],\n  [\"???\",[\"BLENDVPS\",\"BLENDVPS\",[\"PRORVD\",\"\",\"PRORVQ\"],\"\"],[\"\",\"\",\"PMOVUSQW\",\"\"],\"???\"],\n  [\"???\",[\"BLENDVPD\",\"BLENDVPD\",[\"PROLVD\",\"\",\"PROLVQ\"],\"\"],[\"\",\"\",\"PMOVUSQD\",\"\"],\"???\"],\n  [\"???\",[\"\",\"PERMPS\",[\"PERMPS\",\"\",\"PERMPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"PTEST\",\"PTEST\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"BROADCASTSS\",[\"BROADCASTSS\",\"\",\"???\"],[\"BROADCASTSS\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"BROADCASTSD\",[\"BROADCASTF32X2\",\"\",\"BROADCASTSD\"],[\"???\",\"\",\"BROADCASTSD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"BROADCASTF128\",[\"BROADCASTF32X4\",\"\",\"BROADCASTF64X2\"],[\"BROADCASTF32X4\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"BROADCASTF32X8\",\"\",\"BROADCASTF64X4\"],[\"???\",\"\",\"BROADCASTF64X4\"]],\"???\",\"???\"],\n  [[\"PABSB\",\"\",\"\",\"\"],\"PABSB\",\"???\",\"???\"],\n  [[\"PABSW\",\"\",\"\",\"\"],\"PABSW\",\"???\",\"???\"],\n  [[\"PABSD\",\"\",\"\",\"\"],[\"PABSD\",\"\",\"???\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"PABSQ\",\"\"],\"???\",\"???\"],\n  [\"???\",\"PMOVSXBW\",[\"\",\"\",\"PMOVSWB\",\"\"],\"???\"],\n  [\"???\",\"PMOVSXBD\",[\"\",\"\",\"PMOVSDB\",\"\"],\"???\"],\n  [\"???\",\"PMOVSXBQ\",[\"\",\"\",\"PMOVSQB\",\"\"],\"???\"],\n  [\"???\",\"PMOVSXWD\",[\"\",\"\",\"PMOVSDW\",\"\"],\"???\"],\n  [\"???\",\"PMOVSXWQ\",[\"\",\"\",\"PMOVSQW\",\"\"],\"???\"],\n  [\"???\",\"PMOVSXDQ\",[\"\",\"\",\"PMOVSQD\",\"\"],\"???\"],\n  [\"???\",[\"\",\"\",[\"PTESTMB\",\"\",\"PTESTMW\"],\"\"],[\"\",\"\",[\"PTESTNMB\",\"\",\"PTESTNMW\"],\"\"],\"???\"],\n  [\"???\",[\"\",\"\",[\"PTESTMD\",\"\",\"PTESTMQ\"],[\"PTESTMD\",\"\",\"???\"]],[\"\",\"\",[\"PTESTNMD\",\"\",\"PTESTNMQ\"],\"\"],\"???\"],\n  [\"???\",\"PMULDQ\",[\"\",\"\",[\"PMOVM2B\",\"\",\"PMOVM2W\"],\"\"],\"???\"],\n  [\"???\",[\"PCMPEQQ\",\"PCMPEQQ\",\"PCMPEQQ\",\"\"],[\"\",\"\",[\"PMOVB2M\",\"\",\"PMOVW2M\"],\"\"],\"???\"],\n  [[\"???\",[\"MOVNTDQA\",\"\",\"???\"],\"???\",\"???\"],[\"???\",\"???\",[\"\",\"\",[\"???\",\"\",\"PBROADCASTMB2Q\"],\"\"],\"???\"]],\n  [\"???\",[\"PACKUSDW\",\"\",\"???\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"MASKMOVPS\",[\"SCALEFPS\",\"\",\"SCALEFPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"MASKMOVPD\",[\"SCALEFSS\",\"\",\"SCALEFSD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"MASKMOVPS\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"MASKMOVPD\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",\"PMOVZXBW\",[\"\",\"\",\"PMOVWB\",\"\"],\"???\"],\n  [\"???\",\"PMOVZXBD\",[\"\",\"\",\"PMOVDB\",\"\"],\"???\"],\n  [\"???\",\"PMOVZXBQ\",[\"\",\"\",\"PMOVQB\",\"\"],\"???\"],\n  [\"???\",\"PMOVZXWD\",[\"\",\"\",\"PMOVDW\",\"\"],\"???\"],\n  [\"???\",\"PMOVZXWQ\",[\"\",\"\",\"PMOVQW\",\"\"],\"???\"],\n  [\"???\",\"PMOVZXDQ\",[\"\",\"\",[\"PMOVQD\",\"PMOVQD\",\"\"],\"\"],\"???\"],\n  [\"???\",[\"\",\"PERMD\",[\"PERMD\",\"\",\"PERMQ\"],[\"PERMD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"PCMPGTQ\",\"PCMPGTQ\",\"PCMPGTQ\",\"\"],\"???\",\"???\"],\n  [\"???\",\"PMINSB\",[\"\",\"\",[\"PMOVM2D\",\"\",\"PMOVM2Q\"],\"\"],\"???\"],\n  [\"???\",[\"PMINSD\",\"PMINSD\",[\"PMINSD\",\"\",\"PMINSQ\"],[\"PMINSD\",\"\",\"???\"]],[\"\",\"\",[\"PMOVD2M\",\"\",\"PMOVQ2M\"],\"\"],\"???\"],\n  [\"???\",\"PMINUW\",[\"\",\"\",\"PBROADCASTMW2D\",\"\"],\"???\"],\n  [\"???\",[\"PMINUD\",\"PMINUD\",[\"PMINUD\",\"\",\"PMINUQ\"],[\"PMINUD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",\"PMAXSB\",\"???\",\"???\"],\n  [\"???\",[\"PMAXSD\",\"PMAXSD\",[\"PMAXSD\",\"\",\"PMAXSQ\"],[\"PMAXSD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",\"PMAXUW\",\"???\",\"???\"],\n  [\"???\",[\"PMAXUD\",\"PMAXUD\",[\"PMAXUD\",\"\",\"PMAXUQ\"],[\"PMAXUD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"PMULLD\",\"PMULLD\",[\"PMULLD\",\"\",\"PMULLQ\"],[\"PMULLD\",\"\",\"\"]],\"???\",\"???\"],\n  [\"???\",[\"PHMINPOSUW\",[\"PHMINPOSUW\",\"PHMINPOSUW\",\"\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"GETEXPPS\",\"\",\"GETEXPPD\"],[\"GETEXPPS\",\"\",\"GETEXPPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"GETEXPSS\",\"\",\"GETEXPSD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PLZCNTD\",\"\",\"PLZCNTQ\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"PSRLVD\",\"\",\"PSRLVQ\"],[\"PSRLVD\",\"\",\"PSRLVQ\"],[\"PSRLVD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"PSRAVD\",\"\",\"\"],[\"PSRAVD\",\"\",\"PSRAVQ\"],[\"PSRAVD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"PSLLVD\",\"\",\"PSLLVQ\"],[\"PSLLVD\",\"\",\"PSLLVQ\"],[\"PSLLVD\",\"\",\"???\"]],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"\",[\"RCP14PS\",\"\",\"RCP14PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"RCP14SS\",\"\",\"RCP14SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"RSQRT14PS\",\"\",\"RSQRT14PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"RSQRT14SS\",\"\",\"RSQRT14SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"ADDNPS\",\"\",\"ADDNPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"GMAXABSPS\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"GMINPS\",\"\",\"GMINPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"GMAXPS\",\"\",\"GMAXPD\"]],\"???\",\"???\"],\n  \"\",\n  [\"???\",[\"\",\"\",\"\",[\"FIXUPNANPS\",\"\",\"FIXUPNANPD\"]],\"???\",\"???\"],\n  \"\",\"\",\n  [\"???\",[\"\",\"PBROADCASTD\",[\"PBROADCASTD\",\"\",\"???\"],[\"PBROADCASTD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"PBROADCASTQ\",[\"BROADCASTI32X2\",\"\",\"PBROADCASTQ\"],[\"???\",\"\",\"PBROADCASTQ\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"BROADCASTI128\",[\"BROADCASTI32X4\",\"\",\"BROADCASTI64X2\"],[\"BROADCASTI32X4\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"BROADCASTI32X8\",\"\",\"BROADCASTI64X4\"],[\"???\",\"\",\"BROADCASTI64X4\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PADCD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PADDSETCD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PSBBD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PSUBSETBD\",\"\",\"???\"]],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"\",[\"PBLENDMD\",\"\",\"PBLENDMQ\"],[\"PBLENDMD\",\"\",\"PBLENDMQ\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"BLENDMPS\",\"\",\"BLENDMPD\"],[\"BLENDMPS\",\"\",\"BLENDMPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PBLENDMB\",\"\",\"PBLENDMW\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"\",\"\",[\"PSUBRD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"SUBRPS\",\"\",\"SUBRPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PSBBRD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PSUBRSETBD\",\"\",\"???\"]],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"\",\"\",[\"PCMPLTD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PERMI2B\",\"\",\"PERMI2W\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PERMI2D\",\"\",\"PERMI2Q\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PERMI2PS\",\"\",\"PERMI2PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"PBROADCASTB\",[\"PBROADCASTB\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"PBROADCASTW\",[\"PBROADCASTW\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"???\",[\"\",\"\",[\"PBROADCASTB\",\"\",\"???\"],\"\"],\"???\",\"???\"]],\n  [\"???\",[\"???\",[\"\",\"\",[\"PBROADCASTW\",\"\",\"???\"],\"\"],\"???\",\"???\"]],\n  [\"???\",[\"\",\"\",[\"PBROADCASTD\",\"\",\"PBROADCASTQ\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PERMT2B\",\"\",\"PERMT2W\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PERMT2D\",\"\",\"PERMT2Q\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PERMT2PS\",\"\",\"PERMT2PD\"],\"\"],\"???\",\"???\"],\n  [[\"???\",\"INVEPT\",\"???\",\"???\"],\"???\"],\n  [[\"???\",\"INVVPID\",\"???\",\"???\"],\"???\"],\n  [[\"???\",\"INVPCID\",\"???\",\"???\"],\"???\"],\n  [\"???\",[\"???\",\"???\",\"PMULTISHIFTQB\",\"???\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"SCALEPS\",\"\",\"???\"]],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",\"\",\"\",[\"PMULHUD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PMULHD\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"EXPANDPS\",\"\",\"EXPANDPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PEXPANDD\",\"\",\"PEXPANDQ\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"COMPRESSPS\",\"\",\"COMPRESSPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PCOMPRESSD\",\"\",\"PCOMPRESSQ\"],\"\"],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",\"\",[\"PERMB\",\"\",\"PERMW\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\n  [\"???\",[\"\",[\"PGATHERDD\",\"\",\"PGATHERDQ\"],[\"PGATHERDD\",\"\",\"PGATHERDQ\"],[\"PGATHERDD\",\"\",\"PGATHERDQ\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"PGATHERQD\",\"\",\"PGATHERQQ\"],[\"PGATHERQD\",\"\",\"PGATHERQQ\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"GATHERDPS\",\"\",\"GATHERDPD\"],[\"GATHERDPS\",\"\",\"GATHERDPD\"],[\"GATHERDPS\",\"\",\"GATHERDPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"GATHERQPS\",\"\",\"GATHERQPD\"],[\"GATHERQPS\",\"\",\"GATHERQPD\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\n  [\"???\",[\"\",[\"FMADDSUB132PS\",\"\",\"FMADDSUB132PD\"],[\"FMADDSUB132PS\",\"\",\"FMADDSUB132PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBADD132PS\",\"\",\"FMSUBADD132PD\"],[\"FMSUBADD132PS\",\"\",\"FMSUBADD132PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADD132PS\",\"\",\"FMADD132PD\"],[\"FMADD132PS\",\"\",\"FMADD132PD\"],[\"FMADD132PS\",\"\",\"FMADD132PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADD132SS\",\"\",\"FMADD132SD\"],[\"FMADD132SS\",\"\",\"FMADD132SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUB132PS\",\"\",\"FMSUB132PD\"],[\"FMSUB132PS\",\"\",\"FMSUB132PD\"],[\"FMSUB132PS\",\"\",\"FMSUB132PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUB132SS\",\"\",\"FMSUB132SD\"],[\"FMSUB132SS\",\"\",\"FMSUB132SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADD132PS\",\"\",\"FNMADD132PD\"],[\"FNMADD132PS\",\"\",\"FNMADD132PD\"],[\"NMADD132PS\",\"\",\"FNMADD132PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADD132SS\",\"\",\"FNMADD132SD\"],[\"FNMADD132SS\",\"\",\"FNMADD132SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUB132PS\",\"\",\"FNMSUB132PD\"],[\"FNMSUB132PS\",\"\",\"FNMSUB132PD\"],[\"FNMSUB132PS\",\"\",\"FNMSUB132PS\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUB132SS\",\"\",\"FNMSUB132SD\"],[\"FNMSUB132SS\",\"\",\"FNMSUB132SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PSCATTERDD\",\"\",\"PSCATTERDQ\"],[\"PSCATTERDD\",\"\",\"PSCATTERDQ\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PSCATTERQD\",\"\",\"PSCATTERQQ\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"SCATTERDPS\",\"\",\"SCATTERDPD\"],[\"SCATTERDPS\",\"\",\"SCATTERDPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"SCATTERQPS\",\"\",\"SCATTERQPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"FMADD233PS\",\"\",\"???\"]],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",[\"FMADDSUB213PS\",\"\",\"FMADDSUB213PD\"],[\"FMADDSUB213PS\",\"\",\"FMADDSUB213PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBADD213PS\",\"\",\"FMSUBADD213PD\"],[\"FMSUBADD213PS\",\"\",\"FMSUBADD213PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADD213PS\",\"\",\"FMADD213PD\"],[\"FMADD213PS\",\"\",\"FMADD213PD\"],[\"FMADD213PS\",\"\",\"FMADD213PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADD213SS\",\"\",\"FMADD213SD\"],[\"FMADD213SS\",\"\",\"FMADD213SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUB213PS\",\"\",\"FMSUB213PD\"],[\"FMSUB213PS\",\"\",\"FMSUB213PD\"],[\"FMSUB213PS\",\"\",\"FMSUB213PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUB213SS\",\"\",\"FMSUB213SD\"],[\"FMSUB213SS\",\"\",\"FMSUB213SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADD213PS\",\"\",\"FNMADD213PD\"],[\"FNMADD213PS\",\"\",\"FNMADD213PD\"],[\"FNMADD213PS\",\"\",\"FNMADD213PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADD213SS\",\"\",\"FNMADD213SD\"],[\"FNMADD213SS\",\"\",\"FNMADD213SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUB213PS\",\"\",\"FNMSUB213PD\"],[\"FNMSUB213PS\",\"\",\"FNMSUB213PD\"],[\"FNMSUB213PS\",\"\",\"FNMSUB213PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUB213SS\",\"\",\"FNMSUB213SD\"],[\"FNMSUB213SS\",\"\",\"FNMSUB213SD\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"\",\"PMADD52LUQ\",[\"PMADD233D\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"PMADD52HUQ\",[\"PMADD231D\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADDSUB231PS\",\"\",\"FMADDSUB231PD\"],[\"FMADDSUB231PS\",\"\",\"FMADDSUB231PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBADD231PS\",\"\",\"FMSUBADD231PD\"],[\"FMSUBADD231PS\",\"\",\"FMSUBADD231PD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADD231PS\",\"\",\"FMADD231PD\"],[\"FMADD231PS\",\"\",\"FMADD231PD\"],[\"FMADD231PS\",\"\",\"FMADD231PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADD231SS\",\"\",\"FMADD231SD\"],[\"FMADD231SS\",\"\",\"FMADD231SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUB231PS\",\"\",\"FMSUB231PD\"],[\"FMSUB231PS\",\"\",\"FMSUB231PD\"],[\"FMSUB231PS\",\"\",\"FMSUB231PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUB231SS\",\"\",\"FMSUB231SD\"],[\"FMSUB231SS\",\"\",\"FMSUB231SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADD231PS\",\"\",\"FNMADD231PD\"],[\"FNMADD231PS\",\"\",\"FNMADD231PD\"],[\"FNMADD231PS\",\"\",\"FNMADD231PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADD231SS\",\"\",\"FNMADD231SD\"],[\"FNMADD231SS\",\"\",\"FNMADD231SD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUB231PS\",\"\",\"FNMSUB231PD\"],[\"FNMSUB231PS\",\"\",\"FNMSUB231PD\"],[\"FNMSUB231PS\",\"\",\"FNMSUB231PD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUB231SS\",\"\",\"FNMSUB231SD\"],[\"FNMSUB231SS\",\"\",\"FNMSUB231SD\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"\",[\"PCONFLICTD\",\"\",\"PCONFLICTQ\"],\"\"],\"???\",\"???\"],\n  \"???\",\n  [\n    [\n      [\"???\",[\"\",\"\",\"\",[\"GATHERPF0HINTDPS\",\"\",\"GATHERPF0HINTDPD\"]],\"???\",\"???\"],\n      [\"???\",[\"\",\"\",[\"GATHERPF0DPS\",\"\",\"GATHERPF0DPD\"],[\"GATHERPF0DPS\",\"\",\"\"]],\"???\",\"???\"],\n      [\"???\",[\"\",\"\",[\"GATHERPF1DPS\",\"\",\"GATHERPF1DPD\"],[\"GATHERPF1DPS\",\"\",\"\"]],\"???\",\"???\"],\n      \"???\",\n      [\"???\",[\"\",\"\",\"\",[\"SCATTERPF0HINTDPS\",\"\",\"SCATTERPF0HINTDPD\"]],\"???\",\"???\"],\n      [\"???\",[\"\",\"\",[\"SCATTERPF0DPS\",\"\",\"SCATTERPF0DPD\"],[\"VSCATTERPF0DPS\",\"\",\"\"]],\"???\",\"???\"],\n      [\"???\",[\"\",\"\",[\"SCATTERPF1DPS\",\"\",\"SCATTERPF1DPD\"],[\"VSCATTERPF1DPS\",\"\",\"\"]],\"???\",\"???\"],\n      \"???\"\n    ],\"???\"\n  ],\n  [\n    [\n      \"???\",\n      [\"???\",[\"\",\"\",[\"GATHERPF0QPS\",\"\",\"GATHERPF0QPD\"],\"\"],\"???\",\"???\"],\n      [\"???\",[\"\",\"\",[\"GATHERPF1QPS\",\"\",\"GATHERPF1QPD\"],\"\"],\"???\",\"???\"],\n      \"???\",\"???\",\n      [\"???\",[\"\",\"\",[\"SCATTERPF0QPS\",\"\",\"SCATTERPF0QPD\"],\"\"],\"???\",\"???\"],\n      [\"???\",[\"\",\"\",[\"SCATTERPF1QPS\",\"\",\"SCATTERPF1QPD\"],\"\"],\"???\",\"???\"],\n      \"???\"\n    ],\"???\"\n  ],\n  [[\"SHA1NEXTE\",\"\",\"\",\"\"],[\"\",\"\",[\"EXP2PS\",\"\",\"EXP2PD\"],[\"EXP223PS\",\"\",\"???\"]],\"???\",\"???\"],\n  [[\"SHA1MSG1\",\"\",\"\",\"\"],[\"\",\"\",\"\",[\"LOG2PS\",\"\",\"???\"]],\"???\",\"???\"],\n  [[\"SHA1MSG2\",\"\",\"\",\"\"],[\"\",\"\",[\"RCP28PS\",\"\",\"RCP28PD\"],[\"RCP23PS\",\"\",\"???\"]],\"???\",\"???\"],\n  [[\"SHA256RNDS2\",\"\",\"\",\"\"],[\"\",\"\",[\"RCP28SS\",\"\",\"RCP28SD\"],[\"RSQRT23PS\",\"\",\"???\"]],\"???\",\"???\"],\n  [[\"SHA256MSG1\",\"\",\"\",\"\"],[\"\",\"\",[\"RSQRT28PS\",\"\",\"RSQRT28PD\"],[\"ADDSETSPS\",\"\",\"???\"]],\"???\",\"???\"],\n  [[\"SHA256MSG2\",\"\",\"\",\"\"],[\"\",\"\",[\"RSQRT28SS\",\"\",\"RSQRT28SD\"],[\"PADDSETSD\",\"\",\"???\"]],\"???\",\"???\"],\n  \"???\",\"???\",\n  [[[\"\",\"\",\"\",[\"LOADUNPACKLD\",\"\",\"LOADUNPACKLQ\"]],[\"\",\"\",\"\",[\"PACKSTORELD\",\"\",\"PACKSTORELQ\"]],\"???\",\"???\"],\"???\"],\n  [[[\"\",\"\",\"\",[\"LOADUNPACKLPS\",\"\",\"LOADUNPACKLPD\"]],[\"\",\"\",\"\",[\"PACKSTORELPS\",\"\",\"PACKSTORELPD\"]],\"???\",\"???\"],\"???\"],\n  \"???\",\"???\",\n  [[[\"\",\"\",\"\",[\"LOADUNPACKHD\",\"\",\"LOADUNPACKHQ\"]],[\"\",\"\",\"\",[\"PACKSTOREHD\",\"\",\"PACKSTOREHQ\"]],\"???\",\"???\"],\"???\"],\n  [[[\"\",\"\",\"\",[\"LOADUNPACKHPS\",\"\",\"LOADUNPACKHPD\"]],[\"\",\"\",\"\",[\"PACKSTOREHPS\",\"\",\"PACKSTOREHPD\"]],\"???\",\"???\"],\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"AESIMC\",\"AESIMC\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"AESENC\",\"AESENC\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"AESENCLAST\",\"AESENCLAST\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"AESDEC\",\"AESDEC\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"AESDECLAST\",\"AESDECLAST\",\"\",\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\n    [\"MOVBE\",\"\",\"\",\"\"],\n    [\"MOVBE\",\"\",\"\",\"\"],\"???\",\n    [\"CRC32\",\"\",\"\",\"\"]\n  ],\n  [\n    [\"MOVBE\",\"\",\"\",\"\"],\n    [\"MOVBE\",\"\",\"\",\"\"],\"???\",\n    [\"CRC32\",\"\",\"\",\"\"]\n  ],\n  [\"???\",[\"\",\"ANDN\",\"\",\"\"],\"???\",\"???\"],\n  [\n    \"???\",\n    [\"???\",[\"\",\"BLSR\",\"\",\"\"],\"???\",\"???\"],\n    [\"???\",[\"\",\"BLSMSK\",\"\",\"\"],\"???\",\"???\"],\n    [\"???\",[\"\",\"BLSI\",\"\",\"\"],\"???\",\"???\"],\n    \"???\",\"???\",\"???\",\"???\"\n  ],\"???\",\n  [\n    [\"\",\"BZHI\",\"\",\"\"],\"???\",\n    [\"\",\"PEXT\",\"\",\"\"],\n    [\"\",\"PDEP\",\"\",\"\"]\n  ],\n  [\n    \"???\",\n    [\"ADCX\",\"\",\"\",\"\"],\n    [\"ADOX\",\"\",\"\",\"\"],\n    [\"\",\"MULX\",\"\",\"\"]\n  ],\n  [\n    [\"\",\"BEXTR\",\"\",\"\"],\n    [\"\",\"SHLX\",\"\",\"\"],\n    [\"\",\"SARX\",\"\",\"\"],\n    [\"\",\"SHRX\",\"\",\"\"]\n  ],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  Three Byte operations 0F38. Opcodes plus 768 goes to 767 used by escape codes \"0F, 3A\", Or\n  set directly by adding map bits \"11\" because \"11 00000000\" bin = 768 plus opcode.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [\"???\",[\"\",\"PERMQ\",\"PERMQ\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"PERMPD\",\"PERMPD\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"PBLENDD\",\"\",\"\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"ALIGND\",\"\",\"ALIGNQ\"],[\"ALIGND\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"PERMILPS\",[\"PERMILPS\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"PERMILPD\",\"PERMILPD\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"PERM2F128\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"PERMF32X4\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"ROUNDPS\",\"ROUNDPS\",[\"RNDSCALEPS\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"ROUNDPD\",\"ROUNDPD\",\"RNDSCALEPD\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"ROUNDSS\",\"ROUNDSS\",[\"RNDSCALESS\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"ROUNDSD\",\"ROUNDSD\",\"RNDSCALESD\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"BLENDPS\",\"BLENDPS\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"BLENDPD\",\"BLENDPD\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"PBLENDW\",\"PBLENDW\",\"\",\"\"],\"???\",\"???\"],\n  [[\"PALIGNR\",\"\",\"\",\"\"],\"PALIGNR\",\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [[\"???\",\"PEXTRB\",\"???\",\"???\"],[\"???\",\"PEXTRB\",\"???\",\"???\"]],\n  [[\"???\",\"PEXTRW\",\"???\",\"???\"],[\"???\",\"PEXTRW\",\"???\",\"???\"]],\n  [\"???\",[\"PEXTRD\",\"\",\"PEXTRQ\"],\"???\",\"???\"],\n  [\"???\",\"EXTRACTPS\",\"???\",\"???\"],\n  [\"???\",[\"\",\"INSERTF128\",[\"INSERTF32X4\",\"\",\"INSERTF64X2\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"EXTRACTF128\",[\"EXTRACTF32X4\",\"\",\"EXTRACTF64X2\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"INSERTF32X8\",\"\",\"INSERTF64X4\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"EXTRACTF32X8\",\"\",\"EXTRACTF64X4\"],\"\"],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",\"CVTPS2PH\",[\"CVTPS2PH\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PCMP,UD,\",\"\",\"PCMP,UQ,\"],[\"PCMP,UD,\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PCM,PD,\",\"\",\"PCM,PQ,\"],[\"PCM,PD,\",\"\",\"???\"]],\"???\",\"???\"],\n  [\"???\",\"PINSRB\",\"???\",\"???\"],\n  [\"???\",[\"INSERTPS\",\"\",\"???\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"PINSRD\",\"\",\"PINSRQ\"],[\"PINSRD\",\"\",\"PINSRQ\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"SHUFF32X4\",\"\",\"SHUFF64X2\"],\"\"],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",\"\",[\"PTERNLOGD\",\"\",\"PTERNLOGQ\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"GETMANTPS\",\"\",\"GETMANTPD\"],[\"GETMANTPS\",\"\",\"GETMANTPD\"]],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"GETMANTSS\",\"\",\"GETMANTSD\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",[\"KSHIFTRB\",\"\",\"KSHIFTRW\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"KSHIFTRD\",\"\",\"KSHIFTRQ\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"KSHIFTLB\",\"\",\"KSHIFTLW\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"KSHIFTLD\",\"\",\"KSHIFTLQ\"],\"\",\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"INSERTI128\",[\"INSERTI32X4\",\"\",\"INSERTI64X2\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"EXTRACTI128\",[\"EXTRACTI32X4\",\"\",\"EXTRACTI64X2\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"INSERTI32X8\",\"\",\"INSERTI64X4\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"EXTRACTI32X8\",\"\",\"EXTRACTI64X4\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\n  [\"???\",[\"\",\"KEXTRACT\",[\"PCMP,UB,\",\"\",\"PCMP,UW,\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"PCM,PB,\",\"\",\"PCM,PW,\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"DPPS\",\"DPPS\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"DPPD\",\"DPPD\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"MPSADBW\",\"MPSADBW\",[\"DBPSADBW\",\"\",\"???\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"SHUFI32X4\",\"\",\"SHUFI64X2\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"PCLMULQDQ\",\"PCLMULQDQ\",\"\",\"\"],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",\"PERM2I128\",\"\",\"\"],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",[\"PERMIL2PS\",\"\",\"PERMIL2PS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"PERMIL2PD\",\"\",\"PERMIL2PD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"BLENDVPS\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"BLENDVPD\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"PBLENDVB\",\"\",\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\n  [\"???\",[\"\",\"\",[\"RANGEPS\",\"\",\"RANGEPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"RANGESS\",\"\",\"RANGESD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",\"\",[\"RNDFXPNTPS\",\"\",\"RNDFXPNTPD\"]],\"???\",\"???\"],\n  \"???\",\n  [\"???\",[\"\",\"\",[\"FIXUPIMMPS\",\"\",\"FIXUPIMMPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"FIXUPIMMSS\",\"\",\"FIXUPIMMSD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"REDUCEPS\",\"\",\"REDUCEPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"REDUCESS\",\"\",\"REDUCESD\"],\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",[\"FMADDSUBPS\",\"\",\"FMADDSUBPS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADDSUBPD\",\"\",\"FMADDSUBPD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBADDPS\",\"\",\"FMSUBADDPS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBADDPD\",\"\",\"FMSUBADDPD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"PCMPESTRM\",\"PCMPESTRM\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"PCMPESTRI\",\"PCMPESTRI\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"PCMPISTRM\",\"PCMPISTRM\",\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"PCMPISTRI\",\"PCMPISTRI\",\"\",\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\n  [\"???\",[\"\",\"\",[\"FPCLASSPS\",\"\",\"FPCLASSPD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",\"\",[\"FPCLASSSS\",\"\",\"FPCLASSSD\"],\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADDPS\",\"\",\"FMADDPS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADDPD\",\"\",\"FMADDPD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADDSS\",\"\",\"FMADDSS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMADDSD\",\"\",\"FMADDSD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBPS\",\"\",\"FMSUBPS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBPD\",\"\",\"FMSUBPD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBSS\",\"\",\"FMSUBSS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FMSUBSD\",\"\",\"FMSUBSD\"],\"\",\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"\",[\"FNMADDPS\",\"\",\"FNMADDPS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADDPD\",\"\",\"FNMADDPD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADDSS\",\"\",\"FNMADDSS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMADDSD\",\"\",\"FNMADDSD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUBPS\",\"\",\"FNMSUBPS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUBPD\",\"\",\"FNMSUBPD\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUBSS\",\"\",\"FNMSUBSS\"],\"\",\"\"],\"???\",\"???\"],\n  [\"???\",[\"\",[\"FNMSUBSD\",\"\",\"FNMSUBSD\"],\"\",\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [[\"\",\"\",\"\",\"CVTFXPNTUDQ2PS\"],[\"\",\"\",\"\",[\"CVTFXPNTPS2UDQ\",\"\",\"???\"]],\"???\",[\"\",\"\",\"\",\"CVTFXPNTPD2UDQ\"]],\n  [[\"\",\"\",\"\",\"CVTFXPNTDQ2PS\"],[\"\",\"\",\"\",[\"CVTFXPNTPS2DQ\",\"\",\"???\"]],\"???\",\"???\"],\n  \"SHA1RNDS4\",\n  \"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"AESKEYGENASSIST\",\"AESKEYGENASSIST\",\"\",\"\"],\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",\"???\",\"???\",[\"\",\"\",\"\",\"CVTFXPNTPD2DQ\"]],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",\"???\",\"???\",[\"\",\"RORX\",\"\",\"\"]],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  AMD XOP 8.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPMACSSWW\",\"VPMACSSWD\",\"VPMACSSDQL\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPMACSSDD\",\"VPMACSSDQH\",\"???\",\"???\",\"???\",\"???\",\"???\",\"VPMACSWW\",\"VPMACSWD\",\"VPMACSDQL\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"VPMACSDD\",\"VPMACSDQH\",\n  \"???\",\"???\",[\"VPCMOV\",\"\",\"VPCMOV\"],[\"VPPERM\",\"\",\"VPPERM\"],\"???\",\"???\",\"VPMADCSSWD\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPMADCSWD\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPROTB\",\"VPROTW\",\"VPROTD\",\"VPROTQ\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPCOM,B,\",\"VPCOM,W,\",\"VPCOM,D,\",\"VPCOM,Q,\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPCOM,UB,\",\"VPCOM,UW,\",\"VPCOM,UD,\",\"VPCOM,UQ,\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  AMD XOP 9.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"???\",\n  [\"???\",\"BLCFILL\",\"BLSFILL\",\"BLCS\",\"TZMSK\",\"BLCIC\",\"BLSIC\",\"T1MSKC\"],[\"???\",\"BLCMSK\",\"???\",\"???\",\"???\",\"???\",\"BLCI\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"???\",[\"LLWPCB\",\"SLWPCB\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"]],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VFRCZPS\",\"VFRCZPD\",\"VFRCZSS\",\"VFRCZSD\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"VPROTB\",\"\",\"VPROTB\"],[\"VPROTW\",\"\",\"VPROTW\"],[\"VPROTD\",\"\",\"VPROTD\"],[\"VPROTQ\",\"\",\"VPROTQ\"],\n  [\"VPSHLB\",\"\",\"VPSHLB\"],[\"VPSHLW\",\"\",\"VPSHLW\"],[\"VPSHLD\",\"\",\"VPSHLD\"],[\"VPSHLQ\",\"\",\"VPSHLQ\"],\n  [\"VPSHAB\",\"\",\"VPSHAB\"],[\"VPSHAW\",\"\",\"VPSHAW\"],[\"VPSHAD\",\"\",\"VPSHAD\"],[\"VPSHAQ\",\"\",\"VPSHAQ\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPHADDBW\",\"VPHADDBD\",\"VPHADDBQ\",\"???\",\"???\",\"VPHADDWD\",\"VPHADDWQ\",\"???\",\"???\",\"???\",\"VPHADDDQ\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPHADDUBWD\",\"VPHADDUBD\",\"VPHADDUBQ\",\"???\",\"???\",\"VPHADDUWD\",\"VPHADDUWQ\",\"???\",\"???\",\"???\",\"VPHADDUDQ\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VPHSUBBW\",\"VPHSUBWD\",\"VPHSUBDQ\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  AMD XOP A.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"BEXTR\",\"???\",[\"LWPINS\",\"LWPVAL\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  L1OM Vector.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"???\",\"???\",\"???\",\"???\",\"DELAY\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [[\"VLOADD\",\"VLOADQ\",\"\",\"\"],\"???\"],\"???\",\n  [[\"VLOADUNPACKLD\",\"VLOADUNPACKLQ\",\"\",\"\"],\"???\"],\n  [[\"VLOADUNPACKHD\",\"VLOADUNPACKHQ\",\"\",\"\"],\"???\"],\n  [[\"VSTORED\",\"VSTOREQ\",\"\",\"\"],\"???\"],\"???\",\n  [[\"VPACKSTORELD\",\"VPACKSTORELQ\",\"\",\"\"],\"???\"],\n  [[\"VPACKSTOREHD\",\"VPACKSTOREHQ\",\"\",\"\"],\"???\"],\n  [\"VGATHERD\",\"???\"],[\"VGATHERPFD\",\"???\"],\"???\",[\"VGATHERPF2D\",\"???\"],\n  [\"VSCATTERD\",\"???\"],[\"VSCATTERPFD\",\"???\"],\"???\",[\"VSCATTERPF2D\",\"???\"],\n  [\"VCMP,PS,\",\"VCMP,PD,\",\"\",\"\"],\"VCMP,PI,\",\"VCMP,PU,\",\"???\",\n  [\"VCMP,PS,\",\"VCMP,PD,\",\"\",\"\"],\"VCMP,PI,\",\"VCMP,PU,\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VTESTPI\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"VADDPS\",\"VADDPD\",\"\",\"\"],\"VADDPI\",\"???\",\"VADDSETCPI\",\"???\",\"VADCPI\",\"VADDSETSPS\",\"VADDSETSPI\",\n  [\"VADDNPS\",\"VADDNPD\",\"\",\"\"],\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  [\"VSUBPS\",\"VSUBPD\",\"\",\"\"],\"VSUBPI\",\"???\",\"VSUBSETBPI\",\"???\",\"VSBBPI\",\"???\",\"???\",\n  [\"VSUBRPS\",\"VSUBRPD\",\"\",\"\"],\"VSUBRPI\",\"???\",\"VSUBRSETBPI\",\"???\",\"VSBBRPI\",\"???\",\"???\",\n  [\"VMADD231PS\",\"VMADD231PD\",\"\",\"\"],\"VMADD231PI\",\n  [\"VMADD213PS\",\"VMADD213PD\",\"\",\"\"],\"???\",\n  [\"VMADD132PS\",\"VMADD132PD\",\"\",\"\"],\"???\",\n  \"VMADD233PS\",\"VMADD233PI\",\n  [\"VMSUB231PS\",\"VMSUB231PD\",\"\",\"\"],\"???\",\n  [\"VMSUB213PS\",\"VMSUB213PD\",\"\",\"\"],\"???\",\n  [\"VMSUB132PS\",\"VMSUB132PD\",\"\",\"\"],\"???\",\"???\",\"???\",\n  [\"VMADDN231PS\",\"VMADDN231PD\",\"\",\"\"],\"???\",\n  [\"VMADDN213PS\",\"VMADDN213PD\",\"\",\"\"],\"???\",\n  [\"VMADDN132PS\",\"VMADDN132PD\",\"\",\"\"],\"???\",\"???\",\"???\",\n  [\"VMSUBR231PS\",\"VMSUBR231PD\",\"\",\"\"],\"???\",\n  [\"VMSUBR213PS\",\"VMSUBR213PD\",\"\",\"\"],\"???\",\n  [\"VMSUBR132PS\",\"VMSUBR132PD\",\"\",\"\"],\"???\",\n  [\"VMSUBR23C1PS\",\"VMSUBR23C1PD\",\"\",\"\"],\"???\",\n  [\"VMULPS\",\"VMULPD\",\"\",\"\"],\"VMULHPI\",\"VMULHPU\",\"VMULLPI\",\"???\",\"???\",\"VCLAMPZPS\",\"VCLAMPZPI\",\n  [\"VMAXPS\",\"VMAXPD\",\"\",\"\"],\"VMAXPI\",\"VMAXPU\",\"???\",\n  [\"VMINPS\",\"VMINPD\",\"\",\"\"],\"VMINPI\",\"VMINPU\",\"???\",\n  [\"???\",\"VCVT,PD2PS,\",\"\",\"\"],[\"VCVTPS2PI\",\"VCVT,PD2PI,\",\"\",\"\"],[\"VCVTPS2PU\",\"VCVT,PD2PU,\",\"\",\"\"],\"???\",\n  [\"???\",\"VCVT,PS2PD,\",\"\",\"\"],[\"VCVTPI2PS\",\"VCVT,PI2PD,\",\"\",\"\"],[\"VCVTPU2PS\",\"VCVT,PU2PD,\",\"\",\"\"],\"???\",\n  \"VROUNDPS\",\"???\",\"VCVTINSPS2U10\",\"VCVTINSPS2F11\",\"???\",\"VCVTPS2SRGB8\",\"VMAXABSPS\",\"???\",\n  \"VSLLPI\",\"VSRAPI\",\"VSRLPI\",\"???\",\n  [\"VANDNPI\",\"VANDNPQ\",\"\",\"\"],[\"VANDPI\",\"VANDPQ\",\"\",\"\"],\n  [\"VORPI\",\"VORPQ\",\"\",\"\"],[\"VXORPI\",\"VXORPQ\",\"\",\"\"],\n  \"VBINTINTERLEAVE11PI\",\"VBINTINTERLEAVE21PI\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VEXP2LUTPS\",\"VLOG2LUTPS\",\"VRSQRTLUTPS\",\"???\",\"VGETEXPPS\",\"???\",\"???\",\"???\",\n  \"VSCALEPS\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VRCPRESPS\",\"???\",\"VRCPREFINEPS\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  \"VFIXUPPS\",\"VSHUF128X32\",\"VINSERTFIELDPI\",\"VROTATEFIELDPI\",\"???\",\"???\",\"???\",\"???\",\n  \"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  L1OM Mask, Mem, and bit opcodes.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\"???\",\"BSFI\"],[\"???\",\"BSFI\"],[\"???\",\"BSFI\"],[\"???\",\"BSFI\"],\n  [\"???\",\"BSRI\"],[\"???\",\"BSRI\"],[\"???\",\"BSRI\"],[\"???\",\"BSRI\"],\n  [\"???\",\"BSFF\"],[\"???\",\"BSFF\"],[\"???\",\"BSFF\"],[\"???\",\"BSFF\"],\n  [\"???\",\"BSRF\"],[\"???\",\"BSRF\"],[\"???\",\"BSRF\"],[\"???\",\"BSRF\"],\n  [\"???\",\"BITINTERLEAVE11\"],[\"???\",\"BITINTERLEAVE11\"],[\"???\",\"BITINTERLEAVE11\"],[\"???\",\"BITINTERLEAVE11\"],\n  [\"???\",\"BITINTERLEAVE21\"],[\"???\",\"BITINTERLEAVE21\"],[\"???\",\"BITINTERLEAVE21\"],[\"???\",\"BITINTERLEAVE21\"],\n  [\"???\",\"INSERTFIELD\"],[\"???\",\"INSERTFIELD\"],[\"???\",\"INSERTFIELD\"],[\"???\",\"INSERTFIELD\"],\n  [\"???\",\"ROTATEFIELD\"],[\"???\",\"ROTATEFIELD\"],[\"???\",\"ROTATEFIELD\"],[\"???\",\"ROTATEFIELD\"],\n  [\"???\",\"COUNTBITS\"],[\"???\",\"COUNTBITS\"],[\"???\",\"COUNTBITS\"],[\"???\",\"COUNTBITS\"],\n  [\"???\",\"QUADMASK16\"],[\"???\",\"QUADMASK16\"],[\"???\",\"QUADMASK16\"],[\"???\",\"QUADMASK16\"],\n  \"???\",\"???\",\"???\",\"???\",\n  \"VKMOVLHB\",\n  [[\"CLEVICT1\",\"CLEVICT2\",\"LDVXCSR\",\"STVXCSR\",\"???\",\"???\",\"???\",\"???\"],\"???\"],\n  [[\"VPREFETCH1\",\"VPREFETCH2\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\"???\"],\n  [[\"VPREFETCH1\",\"VPREFETCH2\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"],\"???\"],\n  \"VKMOV\",\"VKMOV\",\"VKMOV\",\"VKMOV\",\n  \"VKNOT\",\"VKANDNR\",\"VKANDN\",\"VKAND\",\n  \"VKXNOR\",\"VKXOR\",\"VKORTEST\",\"VKOR\",\n  \"???\",\"VKSWAPB\",\n  [\"???\",[\"DELAY\",\"SPFLT\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"]],\n  [\"???\",[\"DELAY\",\"SPFLT\",\"???\",\"???\",\"???\",\"???\",\"???\",\"???\"]]\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Operand type array each operation code can use different operands that must be decoded after the select Opcode.\nBasically some instruction may use the ModR/M talked about above while some may use an Immediate, or Both.\nAn Immediate input uses the byte after the opcode as a number some instructions combine a number and an ModR/M address selection\nBy using two bytes for each encoding after the opcode. X86 uses very few operand types for input selections to instructions, but\nthere are many useful combinations. The order the operands are \"displayed\" is the order they are in the Operands string for the\noperation code.\n---------------------------------------------------------------------------------------------------------------------------\nThe first 2 digits is the selected operand type, and for if the operand can change size. Again more opcodes where sacrificed\nto make this an setting Opcode \"66\" goes 16 bit this is explained in detail in the SizeAttrSelect variable section that is\nadjusted by the function ^DecodePrefixAdjustments()^. The Variable SizeAttrSelect effects all operand formats that are decoded by\ndifferent functions except for single size. Don't forget X86 uses very few operand types in which different prefix adjustments\nare used to add extra functionality to each operand type. The next two numbers is the operands size settings.\nIf the operand number is set to the operand version that can not change size then the next two numbers act as a single size for\nfaster decoding. Single size is also used to select numbers that are higher than the max size to select special registers that\nare used by some instructions like Debug Registers.\n---------------------------------------------------------------------------------------------------------------------------\nRegisters have 8, 16, 32, 64, 128, 256, 512 names. The selected ModR/M address location uses a pointer name that shows it's select\nsize then it's location in left, and right brackets like \"QWORD PTR[Address]\". The pointer name changes by sizes 8, 16, 64, 128, 256, 512.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst Operands = [\n  //------------------------------------------------------------------------------------------------------------------------\n  //First Byte operations.\n  //------------------------------------------------------------------------------------------------------------------------\n  \"06000A000003\",\"070E0B0E0003\",\"0A0006000003\",\"0B0E070E0003\",\"16000C000003\",\"170E0DE60003\",\"\",\"\",\n  \"06000A000003\",\"070E0B0E0003\",\"0A0006000003\",\"0B0E070E0003\",\"16000C000003\",\"170E0DE60003\",\"\",\"\",\n  \"06000A000003\",\"070E0B0E0003\",\"0A0006000003\",\"0B0E070E0003\",\"16000C000003\",\"170E0DE60003\",\"\",\"\",\n  \"06000A000003\",\"070E0B0E0003\",\"0A0006000003\",\"0B0E070E0003\",\"16000C000003\",\"170E0DE60003\",\"\",\"\",\n  \"06000A000003\",\"070E0B0E0003\",\"0A0006000003\",\"0B0E070E0003\",\"16000C000003\",\"170E0DE60003\",\"\",\"\",\n  \"06000A000003\",\"070E0B0E0003\",\"0A0006000003\",\"0B0E070E0003\",\"16000C000003\",\"170E0DE60003\",\"\",\"\",\n  \"06000A000003\",\"070E0B0E0003\",\"0A0006000003\",\"0B0E070E0003\",\"16000C000003\",\"170E0DE60003\",\"\",\"\",\n  \"06000A00\",\"070E0B0E\",\"0A000600\",\"0B0E070E\",\"16000C00\",\"170E0DE6\",\"\",\"\",\n  \"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\n  \"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\"03060003\",\n  \"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\n  \"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\"030A\",\n  [\"\",\"\",\"\"],[\"\",\"\",\"\"],\n  [\"0A020606\",\"0A010604\",\"\"],\n  \"0B0E0704\",\n  \"\",\"\",\"\",\"\",\n  \"0DE6\",\"0B0E070E0DE6\",\n  \"0DA1\",\"0B0E070E0DE1\",\n  \"22001A01\",\"230E1A01\",\"1A012000\",\"1A01210E\",\n  \"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\n  \"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\"10000002000C\",\n  [\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C00\"],\n  [\"070E0DE60003\",\"070E0DE60003\",\"070E0DE60003\",\"070E0DE60003\",\"070E0DE60003\",\"070E0DE60003\",\"070E0DE60003\",\"070E0DE6\"],\n  [\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C000003\",\"06000C00\"],\n  [\"070E0DE10003\",\"070E0DE10003\",\"070E0DE10003\",\"070E0DE10003\",\"070E0DE10003\",\"070E0DE10003\",\"070E0DE10003\",\"070E0DE1\"],\n  \"06000A00\",\"070E0B0E\",\n  \"0A0006000003\",\"0B0E070E0003\",\n  \"06000A000001\",\"070E0B0E0001\",\n  \"0A0006000001\",\"0B0E070E0001\",\n  \"06020A080001\",\n  [\"0B0E0601\",\"\"],\n  \"0A0806020001\",\n  [\"070A\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n  [[\"\",\"\",\"\",\"\"],[\"\",\"\",\"\",\"\"],[\"\",\"\",\"\",\"\"],[\"\",\"\",\"\",\"\"]],\n  \"170E030E0003\",\"170E030E0003\",\"170E030E0003\",\"170E030E0003\",\"170E030E0003\",\"170E030E0003\",\"170E030E0003\",\n  [\"\",\"\",\"\"],[\"\",\"\",\"\"],\n  \"0D060C01\", //CALL Ap (w:z).\n  \"\",\n  [\"\",\"\",\"\"],[\"\",\"\",\"\"],\n  \"\",\"\",\n  \"160004000001\",\"170E050E0001\",\n  \"040016000001\",\"050E170E0001\",\n  \"22002000\",\"230E210E\",\n  \"22002000\",\"230E210E\",\n  \"16000C00\",\"170E0DE6\",\n  \"22001600\",\"230E170E\",\"16002000\",\"170E210E\",\"16002200\",\"170E230E\",\n  \"02000C000001\",\"02000C000001\",\"02000C000001\",\"02000C000001\",\"02000C000001\",\"02000C000001\",\"02000C000001\",\"02000C000001\",\n  \"030E0D0E0001\",\"030E0D0E0001\",\"030E0D0E0001\",\"030E0D0E0001\",\"030E0D0E0001\",\"030E0D0E0001\",\"030E0D0E0001\",\"030E0D0E0001\",\n  [\"06000C00\",\"06000C00\",\"06000C00\",\"06000C00\",\"06000C00\",\"06000C00\",\"06000C00\",\"06000C00\"],\n  [\"070E0C00\",\"070E0C00\",\"070E0C00\",\"070E0C00\",\"070E0C00\",\"070E0C00\",\"070E0C00\",\"070E0C00\"],\n  \"0C010008\",\"0008\",\n  \"0B060906\",\"0B060906\",\n  [\n    \"06000C000001\",\"\",\"\",\"\",\"\",\"\",\"\",\n    [\"0C00\",\"0C00\",\"0C00\",\"0C00\",\"0C00\",\"0C00\",\"0C00\",\"0C00\"]\n  ],\n  [\n    \"070E0D060001\",\"\",\"\",\"\",\"\",\"\",\"\",\n    [\"1002\",\"1002\",\"1002\",\"1002\",\"1002\",\"1002\",\"1002\",\"1002\"]\n  ],\n  \"0C010C00\",\"\",\n  \"0C01\",\"\",\"2C00\",\n  \"0C00\",\"\",\n  [\"\",\"\",\"\"],\n  [\"06002A00\",\"06002A00\",\"06002A00\",\"06002A00\",\"06002A00\",\"06002A00\",\"06002A00\",\"06002A00\"],\n  [\"070E2A00\",\"070E2A00\",\"070E2A00\",\"070E2A00\",\"070E2A00\",\"070E2A00\",\"070E2A00\",\"070E2A00\"],\n  [\"06001800\",\"06001800\",\"06001800\",\"06001800\",\"06001800\",\"06001800\",\"06001800\",\"06001800\"],\n  [\"070E1800\",\"070E1800\",\"070E1800\",\"070E1800\",\"070E1800\",\"070E1800\",\"070E1800\",\"070E1800\"],\n  \"0C00\",\"0C00\",\"\",\n  \"1E00\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  X87 FPU.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [\n    [\"0604\",\"0604\",\"0604\",\"0604\",\"0604\",\"0604\",\"0604\",\"0604\"],\n    [\"24080609\",\"24080609\",\"0609\",\"0609\",\"24080609\",\"24080609\",\"24080609\",\"24080609\"]\n  ],\n  [\n    [\"0604\",\"\",\"0604\",\"0604\",\"0601\",\"0602\",\"0601\",\"0602\"],\n    [\n      \"0609\",\"0609\",\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      \"0609\",\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]\n    ]\n  ],\n  [\n    [\"0604\",\"0604\",\"0604\",\"0604\",\"0604\",\"0604\",\"0604\",\"0604\"],\n    [\n      \"24080609\",\"24080609\",\"24080609\",\"24080609\",\"\",\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\"\",\"\"\n    ]\n  ],\n  [\n    [\"0604\",\"0604\",\"0604\",\"0604\",\"\",\"0607\",\"\",\"0607\",\"\"],\n    [\n      \"24080609\",\"24080609\",\"24080609\",\"24080609\",\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      \"24080609\",\"24080609\",\"\"\n    ]\n  ],\n  [\n    [\"0606\",\"0606\",\"0606\",\"0606\",\"0606\",\"0606\",\"0606\",\"0606\"],\n    [\"06092408\",\"06092408\",\"0609\",\"0609\",\"06092408\",\"06092408\",\"06092408\",\"06092408\"]\n  ],\n  [\n    [\"0606\",\"0606\",\"0606\",\"0606\",\"0606\",\"\",\"0601\",\"0602\"],\n    [\"0609\",\"0609\",\"0609\",\"0609\",\"0609\",\"0609\",\"\",\"\"]\n  ],\n  [\n    [\"0602\",\"0602\",\"0602\",\"0602\",\"0602\",\"0602\",\"0602\",\"0602\"],\n    [\n      \"06092408\",\"06092408\",\"0609\",\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      \"06092408\",\"06092408\",\"06092408\",\"06092408\"\n    ]\n  ],\n  [\n    [\"0602\",\"0602\",\"0602\",\"0602\",\"0607\",\"0606\",\"0607\",\"0606\"],\n    [\n      \"0609\",\"0609\",\"0609\",\"0609\",\n      [\"1601\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      \"24080609\",\"24080609\",\n      \"\"\n    ]\n  ],\n  /*------------------------------------------------------------------------------------------------------------------------\n  End of X87 FPU.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"10000004\",\"10000004\",\"10000004\",\"10000004\",\n  \"16000C00\",\"170E0C00\",\"0C001600\",\"0C00170E\",\n  \"110E0008\",\n  \"110E0008\",\n  \"0D060C01\", //JMP Ap (w:z).\n  \"100000040004\",\n  \"16001A01\",\"170E1A01\",\n  \"1A011600\",\"1A01170E\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\n  [\"06000C00\",\"\",\"06000003\",\"06000003\",\"16000600\",\"0600\",\"16000600\",\"0600\"],\n  [\"070E0D06\",\"\",\"070E0003\",\"070E0003\",\"170E070E\",\"070E\",\"170E070E\",\"170E070E\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\n  [\"06000003\",\"06000003\",\"\",\"\",\"\",\"\",\"\",\"\"],\n  [\n    [\"070E0003\",\"070E0003\",\"070A0004\",\"090E0008\",\"070A0008\",\"090E0008\",\"070A\",\"\"],\n    [\"070E0003\",\"070E0003\",\"070A0008\",\"\",\"070A0008\",\"\",\"070A\",\"\"]\n  ],\n  /*------------------------------------------------------------------------------------------------------------------------\n  Two Byte operations.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [\n    [\"0602\",\"0602\",\"0602\",\"0602\",\"0602\",\"0602\",\"070E\",\"\"],\n    [\"070E\",\"070E\",\"0601\",\"0601\",\"0601\",\"0601\",\"070E\",\"\"]\n  ],\n  [\n    [\"0908\",\"0908\",\"0908\",\"0908\",\"0602\",\"\",\"0602\",\"0601\"],\n    [\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"170819081B08\",\"17081908\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"1708\",\"\",\"1708\",\"1708\",\"\",\"\",\"1602\",\"17081802\"],\n      \"070E\",\"\",\"0601\",\n      [\"\",\"\",\"170819081B08\",\"170819081B08\",\"\",\"\",\"\",\"\"]\n    ]\n  ],\n  [\"0B0E0612\",\"0B0E070E\"],[\"0B0E0612\",\"0B0E070E\"],\"\",\n  \"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\n  [[\"0601\",\"0601\",\"\",\"\",\"\",\"\",\"\",\"\"],\"\"],\n  \"\",\n  \"0A0A06A9\", //3DNow takes ModR/M, IMM8.\n  [\n    [\"0B700770\",\"0B700770\",\"0A040603\",\"0A040609\"],\n    [\"0B700770\",\"0B700770\",\"0A0412040604\",\"0A0412040604\"]\n  ],\n  [\n    [\"07700B70\",\"07700B70\",\"06030A04\",\"06090A04\"],\n    [\"07700B70\",\"07700B70\",\"060412040A04\",\"060412040A04\"]\n  ],\n  [\n    [\"0A0412040606\",\"0A0412040606\",\"0B700770\",\"0B700768\"],\n    [\"0A0412040604\",\"\",\"0B700770\",\"0B700770\"]\n  ],\n  [[\"06060A04\",\"06060A04\",\"\",\"\"],\"\"],\n  [\"0B70137007700140\",\"0B70137007700140\",\"\",\"\"],\n  [\"0B70137007700140\",\"0B70137007700140\",\"\",\"\"],\n  [[\"0A0412040606\",\"0A0412040606\",\"0B700770\",\"\"],[\"0A0412040604\",\"\",\"0B700770\",\"\"]],\n  [[\"06060A04\",\"06060A04\",\"\",\"\"],\"\"],\n  [[\"0601\",\"0601\",\"0601\",\"0601\",\"\",\"\",\"\",\"\"],\"\"],\n  \"\",\n  [[[\"0A0B07080180\",\"\",\"\",\"\"],[\"0A0B07100180\",\"\",\"\",\"\"],[\"0A0B07080180\",\"\",\"\",\"\"],[\"0A0B07080180\",\"\",\"\",\"\"]],\n  [\"\",[\"0A0B060B\",\"\",\"\",\"\"],[\"0A0B07080180\",\"\",\"\",\"\"],[\"0A0B07080180\",\"\",\"\",\"\"]]],\n  [[[\"07080A0B0180\",\"\",\"\",\"\"],[\"07100A0B0180\",\"\",\"\",\"\"],[\"0A0B07080180\",\"\",\"\",\"\"],[\"0A0B07080180\",\"\",\"\",\"\"]],\n  [\"\",[\"0A0B060B\",\"\",\"\",\"\"],\"\",[\"0A0B07080180\",\"\",\"\",\"\"]]],\n  \"\",\"\",\"\",\n  \"070E\",\n  [\"\",\"07080A0C0001\"],[\"\",\"07080A0D0001\"],\n  [\"\",\"0A0C07080001\"],[\"\",\"0A0D07080001\"],\n  [\"\",\"07080A0E0001\"],\"\",\n  [\"\",\"0A0E07080001\"],\"\",\n  [\n    [\"0A040648\",\"0B300730\",\"0B700770\",\"0A06066C0130\"],\n    [\"0A040648\",\"0B300730\",\"0B700770\",\"0A06066C0130\"],\n    \"\",\"\"\n  ],\n  [\n    [\n      [\"06480A04\",\"07300B30\",\"07700B70\",\"066C0A060130\"],\n      [\"06480A04\",\"07300B30\",\"07700B70\",\"066C0A060130\"],\n      [\"\",\"\",\"\",[\"066C0A060138\",\"066C0A060138\",\"066C0A060138\"]],\n      [\"\",\"\",\"\",[\"066C0A060138\",\"066C0A060138\",\"066C0A060138\"]]\n    ],\n    [\n      [\"06480A04\",\"07300B30\",\"07700B70\",\"066C0A06\"],\n      [\"06480A04\",\"07300B30\",\"07700B70\",\"066C0A06\"],\n      \"\",\"\"\n    ]\n  ],\n  [\n    [\"0A0406A9\",\"\",\"\",\"\"],[\"0A0406A9\",\"\",\"\",\"\"], //Not Allowed to be Vector encoded.\n    \"0A041204070C010A\",\"0A041204070C010A\"\n  ],\n  [\n    [\n      \"07700B70\",\"07700B70\",\n      [\"06030A04\",\"\",\"\",\"\"],[\"06060A04\",\"\",\"\",\"\"] //SSE4a can not be vector encoded.\n    ],\"\"\n  ],\n  [\n    [\"0A0A0649\",\"\",\"\",\"\"],[\"0A0A0648\",\"\",\"\",\"\"], //Not allowed to be Vector encoded.\n    \"0B0C06430109\",\"0B0C06490109\"\n  ],\n  [\n    [\"0A0A0649\",\"\",\"\",\"\"],[\"0A0A0648\",\"\",\"\",\"\"], //Not allowed to be vector encoded.\n    \"0B0C0643010A\",\"0B0C0649010A\"\n  ],\n  [\"0A0406430101\",\"0A0406490101\",\"\",\"\"],\n  [\"0A0406430101\",\"0A0406490101\",\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\n  \"\",\n  \"\",//Three byte opcodes 0F38\n  \"\",\n  \"\",//Three byte opcodes 0F3A\n  \"\",\"\",\"\",\"\",\"\",\n  \"0B0E070E\",\n  [\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"],\n  [\n    [\"0B0E070E0180\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\n    [\"0B0E070E0180\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0A02070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\n    [\"0A02070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"],\n  [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"],\n  [\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\n    [\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"\"],\"\",\"\"],\"\",\"\"\n  ],\n  \"0B0E070E\",\"0B0E070E\",\"0B0E070E\",\"0B0E070E\",\n  [\"\",[[\"0B0C0648\",\"0B0C0730\",\"\",\"\"],[\"0B0C0648\",\"0B0C0730\",\"\",\"\"],\"\",\"\"]],\n  [\"0B7007700142\",\"0B7007700142\",\"0A04120406430102\",\"0A04120406490102\"],\n  [\n    [\"0A040648\",\"0A040648\",\"\",\"\"],\"\",\n    [\"0A040643\",\"0A0412040643\",\"\",\"\"],\"\"\n  ],\n  [\n    [\"0A040648\",\"0A040648\",\"\",\"\"],\"\",\n    [\"0A040643\",\"0A0412040643\",\"\",\"\"],\"\"\n  ],\n  [\"0B70137007700140\",\"0B70137007700140\",\"\",\"\"],\n  [\"0B70137007700140\",\"0B70137007700140\",\"\",\"\"],\n  [\"0B70137007700140\",\"0B70137007700140\",\"\",\"\"],\n  [\"0B70137007700140\",\"0B70137007700140\",\"\",\"\"],\n  [\n    [\"0A040648\",\"0B3013300730\",\"0B70137007700152\",\"0A061206066C0152\"],\n    [\"0A040648\",\"0B3013300730\",\"0B70137007700152\",\"0A061206066C0152\"],\n    \"0A04120406430102\",\"0A04120406460102\"\n  ],\n  [\n    [\"0A040648\",\"0B3013300730\",\"0B70137007700152\",\"0A061206066C0152\"],\n    [\"0A040648\",\"0B3013300730\",\"0B70137007700152\",\"0A061206066C0152\"],\n    \"0A04120406430102\",\"0A04120406460102\"\n  ],\n  [\n    [\"0A040648\",\"0B300718\",\"0B7007380151\",\"0A06065A0171\"],\n    [\"0A040648\",\"0B180730\",\"0B3807700152\",\"0A05066C0152\"],\n    \"0A04120406430101\",\"0A04120406460102\"\n  ],\n  [[\"0B7007700142\",\"\",\"0B380770014A\"],[\"0B700770014A\",\"\",\"\"],\"0B7007700141\",\"\"],\n  [\n    [\"0A040648\",\"0B3013300730\",\"0B70137007700152\",\"0A061206066C0152\"],\n    [\"0A040648\",\"0B3013300730\",\"0B70137007700152\",\"0A061206066C0152\"],\n    \"0A04120406430102\",\"0A04120406460102\"\n  ],\n  [\"0B70137007700141\",\"0B70137007700141\",\"0A04120406430101\",\"0A04120406460101\"],\n  [\"0B70137007700142\",\"0B70137007700142\",\"0A04120406430102\",\"0A04120406460102\"],\n  [\"0B70137007700141\",\"0B70137007700141\",\"0A04120406430101\",\"0A04120406460101\"],\n  [[\"0A0A06A3\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A3\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A3\",\"\",\"\",\"\"],\"0B701370077001400108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"0A0F137007700108\",\"\"],\"\",\"\"], \n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"0A0F137007700108\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",[\"0A0F137007700148\",\"\",\"\"],[\"0A0F1206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0B70137007700148\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0B70137007700148\",\"\",\"\"],\"\",\"\"],\n  [\"\",\"0B70137007700140\",\"\",\"\"],\n  [\"\",\"0B70137007700140\",\"\",\"\"],\n  [[\"0A0A070C\",\"\",\"\",\"\"],[\"0A04070C0108\",\"\",\"0A04070C0108\"],\"\",\"\"],\n  [\n    [\n      [\"0A0A06A9\",\"\", \"\",\"\"],\n      [\"0B700770\",\"0B700770\",[\"0B7007700108\",\"\",\"0B700770\"],[\"0A06066C0128\",\"\",\"0A06066C0120\"]],\n      [\"0A040710\",\"0B700770\",[\"0B700770\",\"\",\"0B7007700108\"],\"\"],\n      [\"\",\"\",[\"0B7007700108\",\"\",\"0B700770\"],\"\"]\n    ],\n    [\n      [\"0A0A06A9\",\"\", \"\",\"\"],\n      [\"0B700770\",\"0B700770\",[\"0B7007700108\",\"\",\"0B700770\"],[\"0A06066C0148\",\"\",\"0A06066C0140\"]],\n      [\"0A040710\",\"0B700770\",[\"0B700770\",\"\",\"0B7007700108\"],\"\"],\n      [\"\",\"\",[\"0B7007700108\",\"\",\"0B700770\"],\"\"]\n    ]\n  ],\n  [\n    [\"0A0A06A90C00\",\"\",\"\",\"\"],\n    [\"0A0406480C00\",\"0B3007300C00\",[\"0B7007700C000108\",\"\",\"\"],[\"0A06066C0C000108\",\"\",\"\"]],\n    \"0B7007700C000108\",\n    \"0B7007700C000108\"\n  ],\n  [\n    \"\",\n    [\n      \"\",\"\",\n      [[\"060A0C00\",\"\",\"\",\"\"],\"137007700C000108\",\"\",\"\"],\"\",\n      [[\"060A0C00\",\"\",\"\",\"\"],\"137007700C000108\",\"\",\"\"],\"\",\n      [[\"060A0C00\",\"\",\"\",\"\"],\"137007700C000108\",\"\",\"\"],\"\"\n    ]\n  ],\n  [\n    [\"\",[\"\",\"\",[\"137007700C000148\",\"\",\"137007700C000140\"],\"\"],\"\",\"\"],\n    [\"\",[\"\",\"\",[\"137007700C000148\",\"\",\"137007700C000140\"],\"\"],\"\",\"\"],\n    [[\"060A0C00\",\"\",\"\",\"\"],[\"06480C00\",\"133007300C00\",[\"137007700C000148\",\"\",\"\"],[\"1206066C0C000148\",\"\",\"\"]],\"\",\"\"],\n    \"\",\n    [[\"060A0C00\",\"\",\"\",\"\"],[\"06480C00\",\"133007300C00\",[\"137007700C000148\",\"\",\"137007700C000140\"],[\"1206066C0C000148\",\"\",\"\"]],\"\",\"\"],\n    \"\",\n    [[\"060A0C00\",\"\",\"\",\"\"],[\"06480C00\",\"133007300C00\",[\"137007700C000148\",\"\",\"\"],[\"1206066C0C000148\",\"\",\"\"]],\"\",\"\"],\n    \"\"\n  ],\n  [\n    \"\",\n    [\n      \"\",\"\",\n      [[\"137007700C00\",\"137007700C00\",\"\",\"\"],\"137007700C000140\",\"\",\"\"],[\"\",\"137007700C000108\",\"\",\"\"],\n      \"\",\"\",\n      [[\"137007700C00\",\"137007700C00\",\"\",\"\"],\"137007100C000140\",\"\",\"\"],[\"\",\"137007700C000108\",\"\",\"\"]\n    ]\n  ],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040710\",\"13300B300730\",\"0A0F137007700108\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040710\",\"13300B300730\",\"0A0F137007700108\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040710\",\"13300B300730\",[\"0A0F137007700148\",\"\",\"\"],[\"0A0F1206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [[\"\",[\"\",\"\",\"\"],\"\",\"\"],\"\",\"\",\"\"],\n  [\n    [\"07080B080180\",\"\",[\"0B7007700141\",\"\",\"0B3807700149\"],\"\"],\n    [\"064F0C000C00\",\"\",[\"0B7007380149\",\"\",\"0B7007700141\"],\"\"],\n    [\"\",\"\",\"0B0C06440109\",\"\"],\n    [\"0A04064F0C000C00\",\"\",\"0B0C06460109\",\"\"]\n  ],\n  [\n    [\"0B0807080180\",\"\",[\"0B7007700142\",\"\",\"0B380770014A\"],\"\"],\n    [\"0A04064F\",\"\",[\"0B700738014A\",\"\",\"0B7007700142\"],\"\"],\n    [\"\",\"\",\"0B0C0644010A\",\"\"],\n    [\"0A04064F\",\"\",\"0B0C0646010A\",\"\"]\n  ],\n  [\n    \"\",\n    [\"\",\"\",[\"0B7007380149\",\"\",\"0B7007700141\"],\"\"],\n    [\"\",\"\",[\"0B7007380142\",\"\",\"0B700770014A\"],\"0A06065A0170\"],\n    [\"\",\"\",[\"0B700770014A\",\"\",\"0B3807700142\"],\"\"]\n  ],\n  [\n    \"\",\n    [\"\",\"\",[\"0B700738014A\",\"\",\"0B7007700142\"],\"\"],\n    [\"\",\"\",\"0A041204070C010A\",\"\"],\n    [\"\",\"\",\"0A041204070C010A\",\"\"]\n  ],\n  [\n    \"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"],\n    \"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"]\n  ],\n  [\n    \"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"],\n    \"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"]\n  ],\n  [[\"070C0A0A\",\"\",\"\",\"\"],[\"06240A040108\",\"\",\"06360A040108\"],[\"0A040646\",\"0A040646\",[\"\",\"\",\"0A0406460108\"],\"\"],\"\"],\n  [\n    [\"06A90A0A\",\"\",\"\",\"\"],\n    [\"06480A04\",\"07300B30\",[\"07700B700108\",\"\",\"07700B70\"],[\"066C0A060128\",\"\",\"066C0A060120\"]],\n    [\"06480A04\",\"07300B30\",[\"07700B70\",\"\",\"07700B700108\"],\"\"],\n    [\"\",\"\",[\"07700B700108\",\"\",\"07700B70\"],\"\"]\n  ],\n  \"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\n  [[\"1106000C\",\"120F1002\",\"\",\"\"],\"\",\"\",\"\"],[[\"1106000C\",\"120F1002\",\"\",\"\"],\"\",\"\",\"\"],\n  \"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\"1106000C\",\n  [\n    [\"0600\",[\"0A0F06F2\",\"\",\"0A0F06F6\"],\"\",\"\"],\n    [\"0600\",[\"0A0F06F0\",\"\",\"0A0F06F4\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0600\",[\"06120A0F\",\"\",\"06360A0F\"],\"\",\"\"],\n    [\"0600\",[\"06000A0F\",\"\",\"06240A0F\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0600\",[\"0A0F062F\",\"\",\"\"],\"\",\"\"],\n    [\"0600\",[\"0A0F062F\",\"\",\"\"],\"\",\"\"],\"\",\n    [\"0600\",[\"0A0F062F\",\"\",\"0A0F063F\"],\"\",\"\"]\n  ],\n  [\n    [\"0600\",[\"062F0A0F\",\"\",\"\"],\"\",\"\"],\n    [\"0600\",[\"062F0A0F\",\"\",\"\"],\"\",\"\"],\"\",\n    [\"0600\",[\"062F0A0F\",\"\",\"063F0A0F\"],\"\",\"\"]\n  ],\n  \"0600\",[[\"0600\",\"0A03120F06FF\",\"\",\"\"],\"\",\"\",\"\"],\n  \"0600\",[[\"0600\",\"0A03120F06FF\",\"\",\"\"],\"\",\"\",\"\"],\n  [\n    [\"0600\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\n    [\"0600\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  [\n    [\"0600\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\n    [\"0600\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\"\",\"\"\n  ],\n  \"0600\",\"0600\",\"0600\",\"0600\",\"0600\",\"0600\",\n  \"2608\",\"2608\",\n  \"\",\n  \"070E0B0E0003\",\n  \"070E0B0E0C00\",\"070E0B0E1800\",\n  \"0B0E070E\",\"070E0B0E\",\n  \"2808\",\"2808\",\n  \"\",\n  \"070E0B0E0003\",\n  \"070E0B0E0C00\",\"070E0B0E1800\",\n  [\n    [\n      [\"0601\",\"\",\"0601\"],[\"0601\",\"\",\"0601\"],\n      \"0603\",\"0603\",\n      [\"0601\",\"\",\"0601\"],[\"0601\",\"\",\"0601\"],\n      [\"0601\",\"0601\",\"0601\"],\n      [\"0601\",\"0601\",\"\"]\n    ],\n    [\n      [\"\",\"\",[\"0602\",\"\",\"\",\"\"],\"\"],[\"\",\"\",[\"0602\",\"\",\"\",\"\"],\"\"],\n      [\"\",\"\",[\"0602\",\"\",\"\",\"\"],\"\"],[\"\",\"\",[\"0602\",\"\",\"\",\"\"],\"\"],\n      \"\",\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]\n    ]\n  ],\n  \"0B0E070E\",\n  \"06000A000003\",\"070E0B0E0003\",\n  [\"0B0E090E\",\"\"],\n  \"070E0B0E0003\",\n  [\"0B0E090E\",\"\"],\n  [\"0B0E090E\",\"\"],\n  \"0B0E0600\",\"0B0E0602\",\n  [\n    [\"1002\",\"\",\"\",\"\"],\"\",\n    [\"0B060706\",\"0A020602\",\"\",\"\"],\"\"\n  ],\"\",\n  [\"\",\"\",\"\",\"\",\"070E0C000003\",\"070E0C000003\",\"070E0C000003\",\"070E0C000003\"],\n  \"0B0E070E0003\",\n  [\n    [\"0B0E070E0180\",\"\",\"\",\"\"],\"\",\n    [\"0B0E070E0180\",\"0A020602\",\"\",\"\"],[\"0B0E070E0180\",\"0A020602\",\"\",\"\"]\n  ],\n  [\n    [\"0B0E070E0180\",\"\",\"\",\"\"],\"\",\n    [\"0B0E070E0180\",\"0A020602\",\"\",\"\"],[\"0B0E070E0180\",\"\",\"\",\"\"]\n  ],\n  \"0B0E0600\",\"0B0E0602\",\n  \"06000A000003\",\"070E0B0E0003\",\n  [\n    [\"0A0406480C00\",\"0B30133007300C00\",\"0A0F137007700C000151\",\"0A0F066C0C000151\"],\n    [\"0A0406480C00\",\"0B30133007300C00\",\"0A0F137007700C000151\",\"0A0F066C0C000151\"],\n    [\"0A0406440C00\",\"0A04120406480C00\",\"0A0F120406440C000151\",\"\"],\n    [\"0A0406490C00\",\"0A04120406480C00\",\"0A0F120406460C000151\",\"\"]\n  ],\n  [\"06030A02\",\"\"],\n  [[\"0A0A06220C00\",\"\",\"\",\"\"],\"0A04120406220C000108\",\"\",\"\"],\n  [\"\",[[\"06020A0A0C00\",\"\",\"\",\"\"],\"06020A040C000108\",\"\",\"\"]],\n  [\"0B70137007700C000140\",\"0B70137007700C000140\",\"\",\"\"],\n  [\n    [\n      \"\",\n      [\"06060003\",\"\",\"060B0003\"],\n      \"\",\n      [\"0601\",\"\",\"0601\"],\n      [\"0601\",\"\",\"0601\"],\n      [\"0601\",\"\",\"0601\"],\n      [\"0606\",\"0606\",\"0606\",\"\"],[\"0606\",\"\",\"\",\"\"]\n    ],\n    [\n      \"\",\n      [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n      \"\",\"\",\"\",\"\",\n      \"070E\",\"070E\"\n    ]\n  ],\n  \"030E\",\"030E\",\"030E\",\"030E\",\"030E\",\"030E\",\"030E\",\"030E\",\n  [\"\",[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",[\"0A040648\",\"0B3013300730\",\"\",\"\"]],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137006480108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300648\",[\"0B70137006480108\",\"\",\"\"],\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137006480100\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700140\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [\n    [\"\",\"06490A040100\",\"\",\"\"],\n    [\"\",\"06490A040100\",[\"0A040649\",\"\",\"\",\"\"],[\"0A040649\",\"\",\"\",\"\"]]\n  ],\n  [\"\",[[\"0B0C06A0\",\"\",\"\",\"\"],[\"0B0C0640\",\"0B0C0730\",\"\",\"\"],\"\",\"\"]],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"0A061206066C0140\"]],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"0A061206066C0140\"]],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [\n    [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300648\",\"0B70137006480108\",\"\"],\"\",\"\"],\n    [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"0B70137006480108\",\"\"],\"\",\"\"]\n  ],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300648\",[\"0B70137006480108\",\"\",\"0B7013700648\"],\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [\n    \"\",\n    [\"0A040648\",\"0A040730\",\"0B3807700141\",\"\"],\n    [\"0A040649\",\"0B300738\",[\"0A0406480140\",\"0B7007380140\",\"0B700770014A\"],\"0A06065A0170\"],\n    \"0B3807700142\"\n  ],\n  [[[\"06090A0A\",\"\",\"\",\"\"],[\"07700B700108\",\"\",\"\"],\"\",\"\"],\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"0A061206066C0140\"]],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"0A061206066C0140\"]],\"\",\"\"],\n  [[\"\",\"\",\"\",[\"0A040648\",\"0A040730\",\"\",\"\"]],\"0000\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137006480108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0B70137006480108\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B7013700648\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700140\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [\"\",[[\"0A0A060A\",\"\",\"\",\"\"],[\"0B040648\",\"0B040648\",\"\",\"\"],\"\",\"\"]],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700140\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  \"\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  Three Byte operations 0F38.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0A040648\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B70137007700108\",\"\",\"\"],\n  [\"\",[\"\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B3013300730\",\"0B70137007700140\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B300730\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B300730\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406482E00\",\"0B30133007301530\",\"0B7013700770\",\"\"],[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",[\"\",\"\",\"0B7013700770\",\"\"],[\"\",\"\",\"071C0B70\",\"\"],\"\"],\n  [\"\",[\"\",\"\",\"0B7013700770\",\"\"],[\"\",\"\",\"070E0B70\",\"\"],\"\"],\n  [\"\",[\"\",\"0B300718\",[\"0B7007380109\",\"\",\"\"],\"\"],[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",[\"0A0407102E00\",\"0B30133007301530\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],\"\"],[\"\",\"\",\"071C0B70\",\"\"],\"\"],\n  [\"\",[\"0A0407102E00\",\"0B30133007301530\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],\"\"],[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",[\"\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],\"\"],\"\",\"\"],\n  [\"\",[\"0A040648\",\"0B300730\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B300644\",[\"0B7006440138\",\"\",\"\"],[\"0A0606440138\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"0A050646\",[\"0B6806460108\",\"\",\"0B700646\"],[\"\",\"\",\"0A060646\"]],\"\",\"\"],\n  [\"\",[\"\",\"0A050648\",[\"0B6806480138\",\"\",\"0B680648\"],[\"0A0606480138\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A06065A0108\",\"\",\"0A06065A\"],[\"\",\"\",\"0A06065A\"]],\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B7007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],\"0B7007700108\",\"\",\"\"],\n  [[\"0A0A06A9\",\"\",\"\",\"\"],[\"0B7007700148\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",\"0B7007700140\",\"\"],\"\",\"\"],\n  [\"\",\"0B7007380108\",[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",\"0B70071C0108\",[\"\",\"\",\"071C0B70\",\"\"],\"\"],\n  [\"\",\"0B70070E0108\",[\"\",\"\",\"070E0B70\",\"\"],\"\"],\n  [\"\",\"0B7007380108\",[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",\"0B70071C0108\",[\"\",\"\",\"071C0B70\",\"\"],\"\"],\n  [\"\",\"0B7007380108\",[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",[\"\",\"\",[\"0A0F137007700108\",\"\",\"0A0F13700770\"],\"\"],[\"\",\"\",[\"0A0F13700770\",\"\",\"0A0F137007700108\"],\"\"],\"\"],\n  [\"\",[\"\",\"\",[\"0A0F137007700148\",\"\",\"0A0F137007700140\"],[\"0A0F1206066C0148\",\"\",\"\"]],[\"\",\"\",[\"0A0F137007700140\",\"\",\"0A0F137007700148\"],\"\"],\"\"],\n  [\"\",\"0B70137007700140\",[\"\",\"\",[\"0B7006FF\",\"\",\"0B7006FF0108\"],\"\"],\"\"],\n  [\"\",[\"0A040648\",\"0B3013300730\",\"0A0F137007700140\",\"\"],[\"\",\"\",[\"0A0F0770\",\"\",\"0A0F07700108\"],\"\"],\"\"],\n  [[\"\",[\"0B7007700108\",\"\",\"\"],\"\",\"\"],[\"\",\"\",[\"\",\"\",[\"\",\"\",\"0B7006FF0108\"],\"\"],\"\"]],\n  [\"\",[\"0B70137007700148\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B3013300730\",[\"0B7013700770014A\",\"\",\"0B70137007700142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B3013300730\",[\"0A0412040644014A\",\"\",\"0A04120406480142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"073013300B30\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B3013300730\",\"\",\"\"],\"\",\"\"],\n  [\"\",\"0B7007380108\",[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",\"0B70071C0108\",[\"\",\"\",\"071C0B70\",\"\"],\"\"],\n  [\"\",\"0B70070E0108\",[\"\",\"\",\"070E0B70\",\"\"],\"\"],\n  [\"\",\"0B7007380108\",[\"\",\"\",\"07380B70\",\"\"],\"\"],\n  [\"\",\"0B70071C0108\",[\"\",\"\",\"071C0B70\",\"\"],\"\"],\n  [\"\",\"0B7007380108\",[\"\",\"\",[\"06480A04\",\"07380B70\",\"\"],\"\"],\"\"],\n  [\"\",[\"\",\"0A051205065A\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0108\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"0A040710\",\"0B3013300730\",\"0A0F137007700140\",\"\"],\"\",\"\"],\n  [\"\",\"0B70137007700108\",[\"\",\"\",[\"0B7006FF\",\"\",\"0B7006FF0108\"],\"\"],\"\"],\n  [\"\",[\"0A0412040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],[\"\",\"\",[\"0A0F0770\",\"\",\"0A0F07700108\"],\"\"],\"\"],\n  [\"\",\"0B70137007700108\",[\"\",\"\",\"0B7006FF0100\",\"\"],\"\"],\n  [\"\",[\"0A0412040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",\"0B70137007700108\",\"\",\"\"],\n  [\"\",[\"0A0412040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",\"0B70137007700108\",\"\",\"\"],\n  [\"\",[\"0A0412040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"0A0412040648\",\"0B3013300730\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"0A040648\",[\"0A040648\",\"0A040648\",\"\",\"\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B7007700159\",\"\",\"0B7007700151\"],[\"0A06066C0159\",\"\",\"0A06066C0151\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B7007700148\",\"\",\"0B7007700140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"\"],[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [\"\",[\"\",\"\",[\"0B7007700148\",\"\",\"0B7007700140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A04120406440108\",\"\",\"0A0412040646\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B7007700148\",\"\",\"0B7007700140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A04120406440108\",\"\",\"0A0412040646\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C0159\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C0159\",\"\",\"0A061206066C0151\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C0159\",\"\",\"0A061206066C0151\"]],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C0149\",\"\",\"0A061206066C0141\"]],\"\",\"\"],\n  \"\",\"\",\n  [\"\",[\"\",\"0B300644\",[\"0B7006440128\",\"\",\"\"],[\"0A0606440128\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"0B300646\",[\"0B7006460128\",\"\",\"0B7006460120\"],[\"\",\"\",\"0A0606460120\"]],\"\",\"\"],\n  [\"\",[\"\",\"0A050648\",[\"0B6806480128\",\"\",\"0B6806480120\"],[\"0A0606480128\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A06065A0128\",\"\",\"0A06065A0120\"],[\"\",\"\",\"0A06065A0120\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06120F066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06120F066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06120F066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06120F066C0148\",\"\",\"\"]],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [\"\",[\"\",\"\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],[\"0A061206066C0148\",\"\",\"0A061206066C0140\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700158\",\"\",\"0B70137007700150\"],[\"0A061206066C0158\",\"\",\"0A061206066C0150\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700108\",\"\",\"0B7013700770\"],\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06120F066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06120F066C0148\",\"\",\"\"]],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [\"\",[\"\",\"\",\"\",[\"0A0F1206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700108\",\"\",\"0B7013700770\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B300640\",[\"0B7006400108\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B300642\",[\"0B7006420108\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"\",\"\",[\"0B7006000108\",\"\",\"\"],\"\"],\"\",\"\"]],\n  [\"\",[\"\",[\"\",\"\",[\"0B7006100108\",\"\",\"\"],\"\"],\"\",\"\"]],\n  [\"\",[\"\",\"\",[\"0B70062F0108\",\"\",\"0B70063F\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700108\",\"\",\"0B7013700770\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700148\",\"\",\"0B70137007700140\"],\"\"],\"\",\"\"],\n  [[\"\",\"0B0C060B0180\",\"\",\"\"],\"\"],\n  [[\"\",\"0B0C060B0180\",\"\",\"\"],\"\"],\n  [[\"\",\"0B0C060B0180\",\"\",\"\"],\"\"],\n  [\"\",[\"\",\"\",\"0B70137007700140\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C014A\",\"\",\"\"]],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B7007700108\",\"\",\"0B700770\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B7007700108\",\"\",\"0B700770\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"07700B700108\",\"\",\"07700B70\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"07700B700108\",\"\",\"07700B70\"],\"\"],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",\"\",[\"0B70137007700108\",\"\",\"0B7013700770\"],\"\"],\"\",\"\"],\n  \"\",\"\",\n  [\"\",[\"\",[\"0B30073013300124\",\"\",\"0B30064813300124\"],[\"0B700770012C\",\"\",\"0B7007380124\"],[\"0A06066C012C\",\"\",\"0A06065A0124\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A04073012040104\",\"\",\"0B30073013300104\"],[\"0B380770010C\",\"\",\"0B7007700104\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30073013300134\",\"\",\"0B30064813300134\"],[\"0B700770013C\",\"\",\"0B7007380134\"],[\"0A06066C013C\",\"\",\"0A06065A0104\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A04073012040104\",\"\",\"0B30073013300104\"],[\"0B380770010C\",\"\",\"0B7007700104\"],\"\"],\"\",\"\"],\n  \"\",\"\",\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040714\",\"\",\"0A0412040718\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040714\",\"\",\"0A0412040718\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040714\",\"\",\"0A0412040718\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040714\",\"\",\"0A0412040718\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"07700B70010C\",\"\",\"07380B700104\"],[\"066C0A06012C\",\"\",\"065A0A060124\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"07700B38010C\",\"\",\"07700B700104\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"07700B70013C\",\"\",\"07380B700134\"],[\"066C0A06013C\",\"\",\"065A0A060134\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"07700B38010C\",\"\",\"07700B700104\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A061206066C011A\",\"\",\"\"]],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [\"\",[\"\",\"\",\"0B70137007700140\",[\"0A061206066C0118\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",\"0B70137007700140\",[\"0A061206066C0148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B3013300730\",\"\",\"0B3013300730\"],[\"0B7013700770014A\",\"\",\"0B70137007700142\"],[\"0A061206066C015A\",\"\",\"0A061206066C0152\"]],\"\",\"\"],\n  [\"\",[\"\",[\"0A0412040644\",\"\",\"0A0412040646\"],[\"0A0412040644010A\",\"\",\"0A04120406460102\"],\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [\"\",[\"\",\"\",[\"0B7007700148\",\"\",\"0B7007700140\"],\"\"],\"\",\"\"],\n  \"\",\n  [\n    [\n      [\"\",[\"\",\"\",\"\",[\"060C013C\",\"\",\"060A0134\"]],\"\",\"\"],\n      [\"\",[\"\",\"\",[\"060C013C\",\"\",\"060A0134\"],[\"060C013C\",\"\",\"\"]],\"\",\"\"],\n      [\"\",[\"\",\"\",[\"060C013C\",\"\",\"070A0134\"],[\"060C013C\",\"\",\"\"]],\"\",\"\"],\n      \"\",\n      [\"\",[\"\",\"\",\"\",[\"060C013C\",\"\",\"060A0134\"]],\"\",\"\"],\n      [\"\",[\"\",\"\",[\"060C013C\",\"\",\"060A0134\"],[\"060C013C\",\"\",\"\"]],\"\",\"\"],\n      [\"\",[\"\",\"\",[\"060C013C\",\"\",\"060A0134\"],[\"060C013C\",\"\",\"\"]],\"\",\"\"],\n      \"\"\n    ],\"\"\n  ],\n  [\n    [\n      \"\",\n      [\"\",[\"\",\"\",[\"060C010C\",\"\",\"060C0104\"],\"\"],\"\",\"\"],\n      [\"\",[\"\",\"\",[\"060C010C\",\"\",\"060C0104\"],\"\"],\"\",\"\"],\n      \"\",\"\",\n      [\"\",[\"\",\"\",[\"060C010C\",\"\",\"060C0104\"],\"\"],\"\",\"\"],\n      [\"\",[\"\",\"\",[\"060C010C\",\"\",\"060C0104\"],\"\"],\"\",\"\"],\n      \"\"\n    ],\"\"\n  ],\n  [[\"0A040648\",\"\",\"\",\"\"],[\"\",\"\",[\"0A06066C0159\",\"\",\"0A06066C0151\"],[\"0A06066C0109\",\"\",\"\"]],\"\",\"\"],\n  [[\"0A040648\",\"\",\"\",\"\"],[\"\",\"\",\"\",[\"0A06066C0109\",\"\",\"\"]],\"\",\"\"],\n  [[\"0A040648\",\"\",\"\",\"\"],[\"\",\"\",[\"0A06066C0159\",\"\",\"0A06066C0151\"],[\"0A06066C0109\",\"\",\"\"]],\"\",\"\"],\n  [[\"0A0406482E00\",\"\",\"\",\"\"],[\"\",\"\",[\"0A04120406440109\",\"\",\"0A04120406460101\"],[\"0A06066C0109\",\"\",\"\"]],\"\",\"\"],\n  [[\"0A040648\",\"\",\"\",\"\"],[\"\",\"\",[\"0A06066C0159\",\"\",\"0A06066C0151\"],[\"0A06066C015A\",\"\",\"\"]],\"\",\"\"],\n  [[\"0A040648\",\"\",\"\",\"\"],[\"\",\"\",[\"0A04120406440109\",\"\",\"0A04120406460101\"],[\"0A06066C0148\",\"\",\"\"]],\"\",\"\"],\n  \"\",\"\",\n  [[[\"\",\"\",\"\",[\"0A06060C0120\",\"\",\"0A06060C0128\"]],[\"\",\"\",\"\",[\"060C0A060128\",\"\",\"060C0A060120\"]],\"\",\"\"],\"\"],\n  [[[\"\",\"\",\"\",[\"0A06060C0130\",\"\",\"0A06060C0138\"]],[\"\",\"\",\"\",[\"060C0A060138\",\"\",\"060C0A060130\"]],\"\",\"\"],\"\"],\n  \"\",\"\",\n  [[[\"\",\"\",\"\",[\"0A06060C0120\",\"\",\"0A06060C0128\"]],[\"\",\"\",\"\",[\"060C0A060128\",\"\",\"060C0A060120\"]],\"\",\"\"],\"\"],\n  [[[\"\",\"\",\"\",[\"0A06060C0130\",\"\",\"0A06060C0138\"]],[\"\",\"\",\"\",[\"060C0A060138\",\"\",\"060C0A060130\"]],\"\",\"\"],\"\"],\n  \"\",\"\",\"\",\"\",\"\",\n  [\"\",[\"0A040648\",\"0A040648\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A040648\",\"0A0412040648\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A040648\",\"0A0412040648\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A040648\",\"0A0412040648\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A040648\",\"0A0412040648\",\"\",\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\n    [\"0B0E070E0180\",\"\",\"\",\"\"],\n    [\"0B0E070E0180\",\"\",\"\",\"\"],\"\",\n    [\"0B0C06000180\",\"\",\"\",\"\"]\n  ],\n  [\n    [\"070E0B0E0180\",\"\",\"\",\"\"],\n    [\"070E0B0E0180\",\"\",\"\",\"\"],\"\",\n    [\"0B0C070E0180\",\"\",\"\",\"\"]\n  ],\n  [\"\",[\"\",\"0B0C130C070C\",\"\",\"\"],\"\",\"\"],\n  [\n    \"\",\n    [\"\",[\"\",\"130C070C\",\"\",\"\"],\"\",\"\"],\n    [\"\",[\"\",\"130C070C\",\"\",\"\"],\"\",\"\"],\n    [\"\",[\"\",\"130C070C\",\"\",\"\"],\"\",\"\"],\n    \"\",\"\",\"\",\"\"\n  ],\"\",\n  [\n    [\"\",\"0B0C070C130C\",\"\",\"\"],\"\",\n    [\"\",\"0B0C130C070C\",\"\",\"\"],\n    [\"\",\"0B0C130C070C\",\"\",\"\"]\n  ],\n  [\n    \"\",\n    [\"0B0C070C\",\"\",\"\",\"\"],\n    [\"0B0C070C\",\"\",\"\",\"\"],\n    [\"\",\"0B0C130C070C1B0C\",\"\",\"\"]\n  ],\n  [\n    [\"\",\"0B0C130C070C\",\"\",\"\"],\n    [\"\",\"0B0C130C070C\",\"\",\"\"],\n    [\"\",\"0B0C130C070C\",\"\",\"\"],\n    [\"\",\"0B0C130C070C\",\"\",\"\"]\n  ],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  Three Byte operations 0F3A.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  [\"\",[\"\",\"0A05065A0C00\",\"0B7007700C000140\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0A05065A0C00\",\"0B7007700C000140\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007300C00\",\"\",\"\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700C000148\",\"\",\"0B70137007700C000140\"],[\"0A061206066C0C000108\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"0B3007300C00\",[\"0B7007700C000148\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B3007300C00\",\"0B7007700C000140\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0A051205065A0C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06066C0C000108\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0B3007300C00\",[\"0B7007700C000149\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0B3007300C00\",\"0B7007700C000141\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406440C00\",\"0A04120406440C00\",[\"0A04120406440C000109\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"0A0406460C00\",\"0A04120406460C00\",\"0A04120406460C000101\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0B30133007300C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0B30133007300C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0B30133007300C00\",\"\",\"\"],\"\",\"\"],\n  [[\"0A0A06A90C00\",\"\",\"\",\"\"],\"0B70137007700C000108\",\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [[\"\",\"06000A040C000108\",\"\",\"\"],[\"\",\"070C0A040C000108\",\"\",\"\"]],\n  [[\"\",\"06020A040C000108\",\"\",\"\"],[\"\",\"070C0A040C000108\",\"\",\"\"]],\n  [\"\",[\"06240A040C000108\",\"\",\"06360A040C00\"],\"\",\"\"],\n  [\"\",\"070C0A040C000108\",\"\",\"\"],\n  [\"\",[\"\",\"0A05120506480C00\",[\"0B70137006480C000108\",\"\",\"0B70137006480C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"06480A050C00\",[\"06480B700C000108\",\"\",\"06480B700C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A061206065A0C000108\",\"\",\"0A061206065A0C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"065A0A060C000108\",\"\",\"065A0A060C00\"],\"\"],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",\"07180B300C00\",[\"07380B700C000109\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A0F137007700C000148\",\"\",\"0A0F137007700C000140\"],[\"0A0F1206066C0C000148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A0F137007700C000148\",\"\",\"0A0F137007700C000140\"],[\"0A0F1206066C0C000148\",\"\",\"\"]],\"\",\"\"],\n  [\"\",\"0A04120406200C000108\",\"\",\"\"],\n  [\"\",[\"0A04120406440C000108\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406240C00\",\"\",\"0A04120406360C00\"],[\"0A04120406240C000108\",\"\",\"0A04120406360C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700C000148\",\"\",\"0B70137007700C000140\"],\"\"],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",\"\",[\"0B70137007700C000148\",\"\",\"0B70137007700C000140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B7007700C000149\",\"\",\"0B7007700C000141\"],[\"0A06066C0C000159\",\"\",\"0A06066C0C000151\"]],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A04120406440C000109\",\"\",\"0A04120406460C000101\"],\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"\",[\"\",[\"0A0F06FF0C00\",\"\",\"0A0F06FF0C00\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A0F06FF0C00\",\"\",\"0A0F06FF0C00\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A0F06FF0C00\",\"\",\"0A0F06FF0C00\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A0F06FF0C00\",\"\",\"0A0F06FF0C00\"],\"\",\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [\"\",[\"\",\"0A05120506480C00\",[\"0B70137006480C000108\",\"\",\"0B70137006480C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"06480A050C00\",[\"06480B700C000108\",\"\",\"06480B700C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A061206065A0C000108\",\"\",\"0A061206065A0C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"065A0A060C000108\",\"\",\"065A0A060C00\"],\"\"],\"\",\"\"],\n  \"\",\"\",\n  [\"\",[\"\",\"0A0F063F0C00\",[\"0A0F137007700C000108\",\"\",\"0A0F137007700C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A0F137007700C000108\",\"\",\"0A0F137007700C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0B30133007300C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0A04120406480C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0B30133007300C00\",[\"0B70137007700C000108\",\"\",\"\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B70137007700C000148\",\"\",\"0B70137007700C000140\"],\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0A04120406480C00\",\"\",\"\"],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",\"0A051205065A0C00\",\"\",\"\"],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",[\"0B301330073015300E00\",\"\",\"0B301330153007300E00\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B301330073015300E00\",\"\",\"0B301330153007300E00\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B30133007301530\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0B30133007301530\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",\"0A051205065A1505\",\"\",\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\n  [\"\",[\"\",\"\",[\"0B70137007700C000149\",\"\",\"0B70137007700C000141\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A04120406440C000109\",\"\",\"0A04120406460C000101\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",\"\",[\"0A06066C0C000159\",\"\",\"0A06066C0C000151\"]],\"\",\"\"],\n  \"\",\n  [\"\",[\"\",\"\",[\"0B70137007700C000149\",\"\",\"0B70137007700C000141\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A04120406440C000109\",\"\",\"0A04120406460C000101\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0B7007700C000149\",\"\",\"0B7007700C000141\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A04120406440C000109\",\"\",\"0A04120406460C000101\"],\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0A0406480C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0A0406480C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0A0406480C00\",\"\",\"\"],\"\",\"\"],\n  [\"\",[\"0A0406480C00\",\"0A0406480C00\",\"\",\"\"],\"\",\"\"],\n  \"\",\"\",\n  [\"\",[\"\",\"\",[\"0A0F07700C000148\",\"\",\"0A0F07700C000140\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",\"\",[\"0A0F06440C000108\",\"\",\"0A0F06460C00\"],\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406441530\",\"\",\"0A04120415300644\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406461530\",\"\",\"0A04120415300646\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406441530\",\"\",\"0A04120415300644\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406461530\",\"\",\"0A04120415300646\"],\"\",\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406441530\",\"\",\"0A04120415300644\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406461530\",\"\",\"0A04120415300646\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406441530\",\"\",\"0A04120415300644\"],\"\",\"\"],\"\",\"\"],\n  [\"\",[\"\",[\"0A04120406461530\",\"\",\"0A04120415300646\"],\"\",\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [[\"\",\"\",\"\",\"0A06066C0C000141\"],[\"\",\"\",\"\",[\"0A06066C0C000159\",\"\",\"\"]],\"\",[\"\",\"\",\"\",\"0A06066C0C000151\"]],\n  [[\"\",\"\",\"\",\"0A06066C0C000141\"],[\"\",\"\",\"\",[\"0A06066C0C000159\",\"\",\"\"]],\"\",\"\"],\n  \"0A0406480C00\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"\",[\"0A0406480C00\",\"0A0406480C00\",\"\",\"\"],\"\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\n  [\"\",\"\",\"\",[\"\",\"\",\"\",\"0A06066C0C000151\"]],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"\",\"\",\"\",[\"\",\"0B0C070C0C00\",\"\",\"\"]],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  AMD XOP 8.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A04120406481404\",\"0A04120406481404\",\"0A04120406481404\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A04120406481404\",\"0A04120406481404\",\"\",\"\",\"\",\"\",\"\",\"0A04120406481404\",\"0A04120406481404\",\"0A04120406481404\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"0A04120406481404\",\"0A04120406481404\",\n  \"\",\"\",[\"0B30133007301530\",\"\",\"0B30133015300730\"],[\"0A04120406481404\",\"\",\"0A04120414040648\"],\"\",\"\",\"0A04120406481404\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A04120406481404\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"0A0406480C00\",\"0A0406480C00\",\"0A0406480C00\",\"0A0406480C00\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A04120406480C00\",\"0A04120406480C00\",\"0A04120406480C00\",\"0A04120406480C00\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A04120406480C00\",\"0A04120406480C00\",\"0A04120406480C00\",\"0A04120406480C00\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  AMD XOP 9.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"\",\n  [\"\",\"130C070C\",\"130C070C\",\"130C070C\",\"130C070C\",\"130C070C\",\"130C070C\",\"130C070C\"],\n  [\"\",\"130C070C\",\"\",\"\",\"\",\"\",\"130C070C\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"\",[\"070C\",\"070C\",\"\",\"\",\"\",\"\",\"\",\"\"]],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0B300730\",\"0B300730\",\"0B300730\",\"0B300730\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],\n  [\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],\n  [\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],[\"0A0406481204\",\"\",\"0A0412040648\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A040648\",\"0A040648\",\"0A040648\",\"\",\"\",\"0A040648\",\"0A040648\",\"\",\"\",\"\",\"0A040648\",\"\",\"\",\"\",\"\",\"\",\n  \"0A040648\",\"0A040648\",\"0A040648\",\"\",\"\",\"0A040648\",\"0A040648\",\"\",\"\",\"\",\"0A040648\",\"\",\"\",\"\",\"\",\"\",\n  \"0A040648\",\"0A040648\",\"0A040648\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  /*------------------------------------------------------------------------------------------------------------------------\n  AMD XOP A.\n  ------------------------------------------------------------------------------------------------------------------------*/\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0B0C070C0C020180\",\"\",[\"130C06240C020180\",\"130C06240C020180\",\"\",\"\",\"\",\"\",\"\",\"\"],\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  L1OM Vector.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"\",\"\",\"\",\"\",\"1206\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [[\"0A0606610120\",\"0A0606610120\",\"\",\"\"],\"\"],\"\",\n  [[\"0A0606610120\",\"0A0606610120\",\"\",\"\"],\"\"],\n  [[\"0A0606610120\",\"0A0606610120\",\"\",\"\"],\"\"],\n  [[\"0A0606610100\",\"0A0606610100\",\"\",\"\"],\"\"],\"\",\n  [[\"0A0606610100\",\"0A0606610100\",\"\",\"\"],\"\"],\n  [[\"0A0606610100\",\"0A0606610100\",\"\",\"\"],\"\"],\n  [\"0A06066C0124\",\"\"],[\"066C0124\",\"\"],\"\",[\"066C0124\",\"\"],\n  [\"066C0A060104\",\"\"],[\"066C0104\",\"\"],\"\",[\"066C0104\",\"\"],\n  [\"0A0F120606610150\",\"0A0F120606610150\",\"\",\"\"],\"0A0F120606610140\",\"0A0F120606610140\",\"\",\n  [\"0A0F120606610150\",\"0A0F120606610150\",\"\",\"\"],\"0A0F120606610140\",\"0A0F120606610140\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A0F120606610140\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"0A06120606610140\",\"\",\"0A06120F06610140\",\"\",\"0A06120F06610140\",\"0A06120606610150\",\"0A06120606610140\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"0A06120606610140\",\"\",\"0A06120F06610140\",\"\",\"0A06120F06610140\",\"\",\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"0A06120606610140\",\"\",\"0A06120F06610140\",\"\",\"0A06120F06610140\",\"\",\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"0A06120606610140\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  \"0A06120606610150\",\"0A06120606610140\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\"\",\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\"\",\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"0A06120606610140\",\"0A06120606610140\",\"0A06120606610140\",\"\",\"\",\"0A06120606610150\",\"0A06120606610140\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"0A06120606610140\",\"0A06120606610140\",\"\",\n  [\"0A06120606610150\",\"0A06120606610150\",\"\",\"\"],\"0A06120606610140\",\"0A06120606610140\",\"\",\n  [\"\",\"0A0606610152\",\"\",\"\"],[\"0A0606610153\",\"0A0606610152\",\"\",\"\"],[\"0A0606610153\",\"0A0606610152\",\"\",\"\"],\"\",\n  [\"\",\"0A0606610158\",\"\",\"\"],[\"0A0606610141\",\"0A0606610148\",\"\",\"\"],[\"0A0606610141\",\"0A0606610148\",\"\",\"\"],\"\",\n  \"0A0606610153\",\"\",\"0A0606610150\",\"0A0606610152\",\"\",\"0A0606610150\",\"0A0606610150\",\"\",\n  \"0A06120606610140\",\"0A06120606610140\",\"0A06120606610140\",\"\",\n  [\"0A06120606610140\",\"0A06120606610140\",\"\",\"\"],[\"0A06120606610140\",\"0A06120606610140\",\"\",\"\"],\n  [\"0A06120606610140\",\"0A06120606610140\",\"\",\"\"],[\"0A06120606610140\",\"0A06120606610140\",\"\",\"\"],\n  \"0A06120606610140\",\"0A06120606610140\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A0606610140\",\"0A0606610150\",\"0A0606610150\",\"\",\"0A0606610150\",\"\",\"\",\"\",\n  \"0A06120606610140\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A0606610150\",\"\",\"0A06120606610150\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"0A0606610C010150\",\"0A0606610C000C00\",\"0A06120606610C010140\",\"0A0606610C010140\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  L1OM Mask, Mem, and bit opcodes.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  [\"\",\"0B0E070E0C010C000C00\"],[\"\",\"0B0E070E0C010C000C00\"],[\"\",\"0B0E070E0C010C000C00\"],[\"\",\"0B0E070E0C010C000C00\"],\n  [\"\",\"0B0E070E0C010C000C00\"],[\"\",\"0B0E070E0C010C000C00\"],[\"\",\"0B0E070E0C010C000C00\"],[\"\",\"0B0E070E0C010C000C00\"],\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  [\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],[\"\",\"0B0E070E\"],\n  \"\",\"\",\"\",\"\",\n  \"06FF0A0F\",\n  [[\"0601\",\"0601\",\"0604\",\"0604\",\"\",\"\",\"\",\"\"],\"\"],\n  [[\"0601\",\"0601\",\"\",\"\",\"\",\"\",\"\",\"\"],\"\"],\n  [[\"0601\",\"0601\",\"\",\"\",\"\",\"\",\"\",\"\"],\"\"],\n  \"06FF0A0F\",\"06FF0B06\",\"07060A0F\",\"06FF0B06\",\n  \"06FF0A0F\",\"06FF0A0F\",\"06FF0A0F\",\"06FF0A0F\",\n  \"06FF0A0F\",\"06FF0A0F\",\"06FF0A0F\",\"06FF0A0F\",\n  \"\",\"06FF0A0F\",\n  [\"\",[\"0B07\",\"0B07\",\"\",\"\",\"\",\"\",\"\",\"\"]],\n  [\"\",[\"0B07\",\"0B07\",\"\",\"\",\"\",\"\",\"\",\"\"]]\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\n3DNow uses the byte after the operands as the select instruction code, so in the Mnemonics there is no instruction name, but\nin the Operands array the operation code 0F0F which is two byte opcode 0x10F (using the disassemblers opcode value system)\nautomatically takes operands ModR/M, and MM register. Once the operands are decoded the byte value after the operands is\nthe selected instruction code for 3DNow. The byte value is an 0 to 255 value so the listing is 0 to 255.\n---------------------------------------------------------------------------------------------------------------------------\nAt the very end of the function ^DecodeInstruction()^ an undefined instruction name with the operands MM, and MM/MMWORD is\ncompared for if the operation code is 0x10F then the next byte is read and is used as the selected 3DNow instruction.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst M3DNow = [\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"PI2FW\",\"PI2FD\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"PFNACC\",\"\",\"\",\"\",\"PFPNACC\",\"\",\n  \"PFCMPGE\",\"\",\"\",\"\",\"PFMIN\",\"\",\"PFRCP\",\"PFRSQRT\",\"\",\"\",\"FPSUB\",\"\",\"\",\"\",\"FPADD\",\"\",\n  \"PFCMPGT\",\"\",\"\",\"\",\"PFMAX\",\"\",\"PFRCPIT1\",\"PFRSQIT1\",\"\",\"\",\"PFSUBR\",\"\",\"\",\"\",\"PFACC\",\"\",\n  \"PFCMPEQ\",\"\",\"\",\"\",\"PFMUL\",\"\",\"PFRCPIT2\",\"PMULHRW\",\"\",\"\",\"\",\"PSWAPD\",\"\",\"\",\"\",\"PAVGUSB\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nVirtual machine synthetic operation codes is under two byte operation code 0FC7 which is opcode 0x1C7 using the disassemblers\nopcode value system. The operation code 0x1C7 is an group opcode containing 3 operation codes, but only one of the codes\nis used in the ModR/M grouped opcode for synthetic virtual machine operation codes. The ModR/M byte has to be in register mode\nusing register code 001 for the virtual machine synthetic operation codes. The effective address has to be set 000 which uses\nthe full ModR/M byte as an static opcode encoding under the group opcode 001. This makes the operation code 0F C7 C8.\nThe resulting instruction name in the Mnemonics map is \"SSS\", and takes no Operands in the Operands array. The two bytes after\n0F C7 C8 are used as the select synthetic operation code. Only the first 4 values of both bytes have an select operation code,\nso an 5x5 map is used to keep the mapping small.\n---------------------------------------------------------------------------------------------------------------------------\nWhen the operation code is 0F C7 and takes the ModR/M byte value C8 the operation code is \"SSS\" with no operands.\nAt the very end of the function ^DecodeInstruction()^ an instruction that is \"SSS\" is compared if it is instruction \"SSS\".\nIf it is operation \"SSS\" then the two bytes are then read as two codes which are used as the selected operation code in the 5x5 map.\n---------------------------------------------------------------------------------------------------------------------------\nlink to the patent https://www.google.com/patents/US7552426\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst MSynthetic = [\n  \"VMGETINFO\",\"VMSETINFO\",\"VMDXDSBL\",\"VMDXENBL\",\"\",\n  \"VMCPUID\",\"VMHLT\",\"VMSPLAF\",\"\",\"\",\n  \"VMPUSHFD\",\"VMPOPFD\",\"VMCLI\",\"VMSTI\",\"VMIRETD\",\n  \"VMSGDT\",\"VMSIDT\",\"VMSLDT\",\"VMSTR\",\"\",\n  \"VMSDTE\",\"\",\"\",\"\",\"\"\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nCondition codes Note that the SSE, and MVEX versions are limited to the first 7 condition codes.\nXOP condition codes map differently.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst ConditionCodes = [\n  \"EQ\",\"LT\",\"LE\",\"UNORD\",\"NEQ\",\"NLT\",\"NLE\",\"ORD\", //SSE/L1OM/MVEX.\n  \"EQ_UQ\",\"NGE\",\"NGT\",\"FALSE\",\"NEQ_OQ\",\"GE\",\"GT\",\"TRUE\", //VEX/EVEX.\n  \"EQ_OS\",\"LT_OQ\",\"LE_OQ\",\"UNORD_S\",\"NEQ_US\",\"NLT_UQ\",\"NLE_UQ\",\"ORD_S\", //VEX/EVEX.\n  \"EQ_US\",\"NGE_UQ\",\"NGT_UQ\",\"FALSE_OS\",\"NEQ_OS\",\"GE_OQ\",\"GT_OQ\",\"TRUE_US\", //VEX/EVEX.\n  \"LT\",\"LE\",\"GT\",\"GE\",\"EQ\",\"NEQ\",\"FALSE\",\"TRUE\" //XOP.\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Decoded operation name.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar Instruction = \"\";\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Instructions operands.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar InsOperands = \"\";\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThis object stores a single decoded Operand, and gives it an number in OperandNum (Operand Number) for the order they are\nread in the operand string. It also stores all of the Settings for the operand.\n---------------------------------------------------------------------------------------------------------------------------\nEach Operand is sorted into an decoder array in the order they are decoded by the CPU in series.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by function ^DecodeOperandString()^ Which sets the operands active and gives them there settings along the X86Decoder array.\n---------------------------------------------------------------------------------------------------------------------------\nThe following X86 patent link might help http://www.google.com/patents/US7640417\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar Operand = function(){\n  return(\n    {\n      Type:0, //The operand type some operands have different formats like DecodeImmediate() which has a type input.\n      BySizeAttrubute:false, //Effects how size is used depends on which operand type for which operand across the decoder array.\n      /*-------------------------------------------------------------------------------------------------------------------------\n      How Size is used depends on the operand it is along the decoder array for which function it uses to\n      decode Like DecodeRegValue(), or Decode_ModRM_SIB_Address(), and lastly DecodeImmediate() as they all take the BySize.\n      -------------------------------------------------------------------------------------------------------------------------*/\n      Size:0x00, //The Setting.\n      OperandNum:0, //The operand number basically the order each operand is read in the operand string.\n      Active:false, //This is set by the set function not all operand are used across the decoder array.\n      //set the operands attributes then set it active in the decoder array.\n      set:function(T, BySize, Settings, OperandNumber)\n      {\n        this.Type = T;\n        this.BySizeAttrubute = BySize;\n        this.Size = Settings;\n        this.OpNum = OperandNumber; //Give the operand the number it was read in the operand string.\n        this.Active = true; //set the operand active so it's settings are decoded by the ^DecodeOperands()^ function.\n      },\n      //Deactivates the operand after they are decoded by the ^DecodeOperands()^ function.\n      Deactivate:function(){ this.Active = false; }\n    }\n  );\n};\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Decoder array is the order each operand is decoded after the select opcode if used. They are set during the decoding of\nthe operand string using the function ^DecodeOperandString()^ which also gives each operand an number for the order they are\nread in. Then they are decoded by the Function ^DecodeOperands()^ which decodes each set operand across the X86Decoder in order.\nThe number the operands are set during the decoding of the operand string is the order they will be positioned after decoding.\nAs the operands are decoded they are also Deactivated so the next instruction can be decoded using different operands.\n---------------------------------------------------------------------------------------------------------------------------\nThe following X86 patent link might help http://www.google.com/patents/US7640417\n---------------------------------------------------------------------------------------------------------------------------\nUsed by functions ^DecodeOperandString()^, and ^DecodeOperands()^, after function ^DecodeOpcode()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar X86Decoder = [\n  /*-------------------------------------------------------------------------------------------------------------------------\n  First operand that is always decoded is \"Reg Opcode\" if used.\n  Uses the function ^DecodeRegValue()^ the input RValue is the three first bits of the opcode.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //Reg Opcode if used.\n  /*-------------------------------------------------------------------------------------------------------------------------\n  The Second operand that is decoded in series is the ModR/M address if used.\n  Reads a byte using function ^Decode_ModRM_SIB_Value()^ gives it to the function ^Decode_ModRM_SIB_Address()^ which only\n  reads the Mode, and Base register for the address, and then decodes the SIB byte if base register is \"100\" binary in value.\n  does not use the Register value in the ModR/M because the register can also be used as a group opcode used by the\n  function ^DecodeOpcode()^, or uses a different register in single size with a different address pointer.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //ModR/M address if used.\n  /*-------------------------------------------------------------------------------------------------------------------------\n  The third operand that is decoded if used is for the ModR/M reg bits.\n  Uses the already decoded byte from ^Decode_ModRM_SIB_Value()^ gives the three bit reg value to the function ^DecodeRegValue()^.\n  The ModR/M address, and reg are usually used together, but can also change direction in the encoding string.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //ModR/M reg bits if used.\n  /*-------------------------------------------------------------------------------------------------------------------------\n  The fourth operand that is decoded in sequence is the first Immediate input if used.\n  The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //First Immediate if used.\n  /*-------------------------------------------------------------------------------------------------------------------------\n  The fifth operand that is decoded in sequence is the second Immediate input if used.\n  The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //Second Immediate if used (Note that the instruction \"Enter\" uses two immediate inputs).\n  /*-------------------------------------------------------------------------------------------------------------------------\n  The sixth operand that is decoded in sequence is the third Immediate input if used.\n  The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //Third Immediate if used (Note that the Larrabee vector instructions can use three immediate inputs).\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Vector adjustment codes allow the selection of the vector register value that is stored into variable\n  VectorRegister that applies to the selected SSE instruction that is read after that uses it.\n  The adjusted vector value is given to the function ^DecodeRegValue()^.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //Vector register if used. And if vector adjustments are applied to the SSE instruction.\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Immediate Register encoding if used.\n  During the decoding of the immediate operands the ^DecodeImmediate()^ function stores the read IMM into an variable called\n  IMMValue. The upper four bits of IMMValue is given to the input RValue to the function ^DecodeRegValue()^.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //Immediate Register encoding if used.\n  /*-------------------------------------------------------------------------------------------------------------------------\n  It does not matter which order the explicit operands decode as they do not require reading another byte after the opcode.\n  Explicit operands are selected internally in the cpu for instruction codes that only use one register, or pointer, or number input.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  new Operand(), //Explicit Operand one.\n  new Operand(), //Explicit Operand two.\n  new Operand(), //Explicit Operand three.\n  new Operand()  //Explicit Operand four.\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nSizeAttrSelect controls the General arithmetic extended sizes \"8/16/32/64\", and SIMD Vector register extended sizes \"128/256/512/1024\".\n---------------------------------------------------------------------------------------------------------------------------\nGeneral arithmetic sizes \"8/16/32/64\" change by operand override which makes all operands go 16 bit.\nThe width bit which is in the REX prefix makes operands go all 64 bits the changes depend on the instructions adjustable size.\nThe value system goes as follows: 0=8, or 16, then 1=Default32, then 2=Max64. Smallest to largest in order.\nChangeable from prefixes. Code 66 hex is operand override, 48 hex is the REX.W setting. By default operands are 32 bit\nin size in both 32 bit mode, and 64 bit modes so by default the Size attribute setting is 1 in value so it lines up with 32.\nIn the case of fewer size settings the size system aligns in order to the correct prefix settings.\n---------------------------------------------------------------------------------------------------------------------------\nIf in 16 bit mode the 16 bit operand size trades places with 32, so when the operand override is used it goes from 16 to 32.\nAlso in 32 bit mode any size that is 64 changes to 32, but except for operands that do not use the BySize system.\n---------------------------------------------------------------------------------------------------------------------------\nDuring Vector instructions size settings \"128/256/512\" use the SizeAttrSelect as the vector length setting as a 0 to 3 value from\nsmallest to largest Note 1024 is Reserved the same system used for General arithmetic sizes \"8/16/32/64\" that go in order.\nIf an operand is used that is 32/64 in size the Width bit allows to move between Sizes 32/64 separately.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by the function ^GetOperandSize()^ which uses a fast base 2 logarithm system.\nThe function ^DecodeOpcode()^ also uses the current size setting for operation names that change name by size, Or\nIn vector instructions the select instruction by size is used to Add additional instructions between the width bit (W=0), and (W=1).\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar SizeAttrSelect = 1;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Width bit is used in combination with SizeAttrSelect only with Vector instructions.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar WidthBit = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nPointer size plus 16 bit's used by FAR JUMP and other instructions.\nFor example FAR JUMP is size attributes 16/32/64 normally 32 is the default size, but it is 32+16=48 FWORD PTR.\nIn 16 bit CPU mode the FAR jump defaults to 16 bits, but because it is a far jump it is 16+16=32 which is DWORD PTR.\nSet by the function ^DecodeOperandString()^ for if the ModR/M operand type is far pointer address.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by the function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar FarPointer = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nAddressOverride is hex opcode 67 then when used with any operation that uses the ModR/M in address mode the ram address\ngoes down one in bit mode. Switches 64 address mode to 32 bit address mode, and in 32 bit mode the address switches to\n16 bit address mode which uses a completely different ModR/M format. When in 16 bit mode the address switches to 32 bit.\nSet true when Opcode 67 is read by ^DecodePrefixAdjustments()^ which effects the next opcode that is not a prefix opcode\nthen is set false after instruction decodes.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by the function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar AddressOverride = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nExtended Register value changes by the \"R bit\" in the REX prefix, or by the \"Double R bit\" settings in EVEX Extension\nwhich makes the Register operand reach to a max value of 32 registers along the register array.\nNormally the Register selection in ModR/M, is limited to three bits in binary 000 = 0 to 111 = 7.\nRegExtend stores the two binary bits that are added onto the three bit register selection.\n---------------------------------------------------------------------------------------------------------------------------\nWhen RegExtend is 00,000 the added lower three bits is 00,000 = 0 to 00,111 = 7.\nWhen RegExtend is 01,000 the added lower three bits is 01,000 = 8 to 01,111 = 15.\nWhen RegExtend is 10,000 the added lower three bits is 10,000 = 16 to 10,111 = 23.\nWhen RegExtend is 11,000 the added lower three bits is 11,000 = 24 to 10,111 = 31.\n---------------------------------------------------------------------------------------------------------------------------\nThe Register expansion bits make the binary number from a 3 bit number to a 5 bit number by combining the EVEX.R'R bits.\nThe REX opcode, and EVEX opcode 62 hex are decoded with function ^DecodePrefixAdjustments()^ which contain R bit settings.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by function ^DecodeRegValue()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar RegExtend = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe base register is used in ModR/M address mode, and Register mode and can be extended to 8 using the \"B bit\" setting\nfrom the REX prefix, or VEX Extension, and EVEX Extension, however in EVEX the tow bits \"X, and B\" are used together to\nmake the base register reach 32 in register value if the ModR/M is in Register mode.\n---------------------------------------------------------------------------------------------------------------------------\nThe highest the Base Register can be extended is from a 3 bit number to a 5 bit number.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by the function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar BaseExtend = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe index register is used in ModR/M memory address mode if the base register is \"100\" bin in the ModR/M which sets SIB mode.\nThe Index register can be extended to 8 using the \"X bit\" setting when the Index register is used.\nThe X bit setting is used in the REX prefix settings, and also the VEX Extension, and EVEX Extension.\n---------------------------------------------------------------------------------------------------------------------------\nThe highest the Index Register can be extended is from a 3 bit number to a 4 bit number.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by the function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar IndexExtend = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nSegOverride is the bracket that is added onto the start of the decoded address it is designed this way so that if a segment\nOverride Prefix is used it is stored with the segment.\n---------------------------------------------------------------------------------------------------------------------------\nused by function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar SegOverride = \"[\";\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThis may seem confusing, but the 8 bit high low registers are used all in \"low order\" when any REX prefix is used.\nSet RexActive true when the REX Prefix is used, for the High, and low Register separation.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by function ^DecodeRegValue()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar RexActive = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe SIMD value is set according to SIMD MODE by prefixes (none, 66, F2, F3), or by the value of VEX.pp, and EVEX.pp.\nChanges the selected instruction in ^DecodeOpcode()^ only for SSE vector opcodes that have 4 possible instructions in\none instruction for the 4 modes otherwise 66 is Operand override, and F2 is REPNE, and F3 is REP prefix adjustments.\nBy reusing some of the already used Prefix adjustments more opcodes did not have to be sacrificed.\n---------------------------------------------------------------------------------------------------------------------------\nSIMD is set 00 in binary by default, SIMD is set 01 in binary when opcode 66 is read by ^DecodePrefixAdjustments()^,\nSIMD is set 10 in binary when opcode F2 is read by ^DecodePrefixAdjustments()^, and SIMD is set 11 in binary when F3 is read\nby ^DecodePrefixAdjustments()^.\n---------------------------------------------------------------------------------------------------------------------------\nThe VEX, and EVEX adjustment codes contain SIMD mode adjustment bits in which each code that is used to change the mode go\nin the same order as SIMD. This allows SIMD to be set directly by the VEX.pp, and EVEX.pp bit value.\n---------------------------------------------------------------------------------------------------------------------------\nVEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h)\nEVEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h)\n---------------------------------------------------------------------------------------------------------------------------\nUsed by the function ^DecodeOpcode()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar SIMD = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nVect is set true during the decoding of an instruction code. If the instruction is an Vector instruction 4 in length for\nthe four modes then Vect is set true. When Vect is set true the Function ^Decode_ModRM_SIB_Address()^ Will decode the\nModR/M as a Vector address.\n---------------------------------------------------------------------------------------------------------------------------\nSet By function ^DecodeOpcode()^, and used by function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar Vect = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nIn AVX512 The width bit can be ignored, or used. The width bit relates to the SIMD mode for size of the numbers in the vector.\nModes N/A, F3 are 32 bit, while 66, F2 are 64 bit. The width bit has to be set for the extend data size for\nmost AVX512 instructions unless the width bit is ignored. Some AVX512 vectors can also broadcast round to there extend data size\ncontrolled by the width bit extend size and SIMD mode.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar IgnoresWidthbit = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe VSIB setting is used for vectors that multiply the displacement by the Element size of the vectors, and use index as an vector pointer.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar VSIB = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nEVEX also has error suppression modes {ER} controlled by vector length, and if the broadcast round is active in register mode,\nor {SAE} suppresses all exceptions then it can not change rounding mode by vector length.\nMVEX also has error suppression modes {ER} controlled by conversion mode, and if the MVEX.E bit is set to round in register mode,\nor {SAE} suppresses all exceptions then it can not change rounding mode by vector length.\nL1OM vectors use {ER} as round control, and {SEA} as exponent adjustment.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar RoundingSetting = 0; //1 = SAE, and 2 = ER.\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe MVEX prefix can Integer convert, and Float convert, and Broadcast round using Swizzle.\nThe EVEX prefix can only Broadcast round using an \"b\" control which sets the Broadcast round option for Swizzle.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar Swizzle = false; //Swizzle based instruction. If false then Up, or Down conversion.\nvar Up = false; //Only used if Swizzle is false. If set false then it is an down conversion.\nvar Float = false; //If False Integer data is used.\nvar VectS = 0x00; //Stores the three vector settings Swizzle, Up, and Float, for faster comparison to special cases.\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Extension is set 2 during opcode 62 hex for EVEX in which the ^DecodePrefixAdjustments()^ decodes the settings, but if\nthe bit that must be set 0 for EVEX is set 1 then Extension is set 3 for MVEX.\nThe Extension is set 1 during opcodes C4, and C5 hex in which the ^DecodePrefixAdjustments()^ decodes the settings for the VEX prefixes.\n---------------------------------------------------------------------------------------------------------------------------\nAn instruction that has 4 opcode combinations based on SIMD can use another 4 in length separator in the select SIMD mode\nwhich selects the opcode based on extension used. This is used to separate codes that can be Vector adjusted, and not.\nSome codes can only be used in VEX, but not EVEX, and not all EVEX can be MVEX encoded as the EVEX versions were introduced after,\nalso MMX instruction can not be used with vector adjustments.\n---------------------------------------------------------------------------------------------------------------------------\nBy default Extension is 0 for decoding instructions normally.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by function ^DecodeOpcode()^ adds the letter \"V\" to the instruction name to show it uses Vector adjustments.\nWhen the Function ^DecodeOpcode()^ completes if Vect is not true and an Extension is active the instruction is invalid.\nUsed By function ^DecodeOperandString()^ which allows the Vector operand to be used if existent in the operand string.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar Extension = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nMVEX/EVEX conversion modes. MVEX can directly set the conversion mode between float, or integer, to broadcast round using option bits.\nThe EVEX Extension only has the broadcast rounding control. In which some instructions support \"]{1to16}\" (B32), or \"]{1to8}\" (B64)\nBased on the data size using the width bit setting. EVEX only can use the 1ToX broadcast round control.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar ConversionMode = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nMVEX/EVEX rounding modes. In EVEX if the ModR/M is used in register mode and Bround Is active.\nThe EVEX Error Suppression type is set by the RoundingSetting {ER}, and {SAE} settings for if the instruction supports it.\nThe MVEX version allows the use of both rounding modes. MVEX can select the rounding type using option bits if the\n\"MVEX.E\" control is set in an register to register operation.\n---------------------------------------------------------------------------------------------------------------------------\nThe function ^Decode_ModRM_SIB_Address()^ sets RoundMode.\nThe function DecodeInstruction() adds the error Suppression to the end of the instruction.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar RoundMode = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nMVEX/EVEX register round modes.\n---------------------------------------------------------------------------------------------------------------------------\nSome instructions use SAE which suppresses all errors, but if an instruction uses {er} the 4 others are used by vector length.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst RoundModes = [\n  \"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\", //First 8 No rounding mode.\n  /*-------------------------------------------------------------------------------------------------------------------------\n  MVEX/EVEX round Modes {SAE} Note MVEX (1xx) must be set 4 or higher, while EVEX uses upper 4 in rounding mode by vector length.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \", {Error}\", \", {Error}\", \", {Error}\", \", {Error}\", \", {SAE}\", \", {SAE}\", \", {SAE}\", \", {SAE}\",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  L1OM/MVEX/EVEX round modes {ER}. L1OM uses the first 4, and EVEX uses the upper 4, while MVEX can use all 8.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \", {RN}\", \", {RD}\", \", {RU}\", \", {RZ}\", \", {RN-SAE}\", \", {RD-SAE}\", \", {RU-SAE}\", \", {RZ-SAE}\",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  MVEX/EVEX round modes {SAE}, {ER} Both rounding modes can not possibly be set both at the same time.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"0B\", \"4B\", \"5B\", \"8B\", \"16B\", \"24B\", \"31B\", \"32B\" //L1OM exponent adjustments.\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nL1OM/MVEX register swizzle modes. When an swizzle operation is done register to register.\nNote L1OM skips swizzle type DACB thus the last swizzle type is an repeat of the DACB as the last L1OM swizzle.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst RegSwizzleModes = [ \"\", \"CDAB\", \"BADC\", \"DACB\", \"AAAA\", \"BBBB\", \"CCCC\", \"DDDD\", \"DACB\" ];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nEVEX does not support conversion modes. Only broadcast round of 1To16, or 1To8 controlled by the data size.\n---------------------------------------------------------------------------------------------------------------------------\nMVEX.sss permits the use of conversion types by value without relating to the Swizzle conversion type.\nHowever During Up, and Down conversion MVEX does not allow Broadcast round control.\n---------------------------------------------------------------------------------------------------------------------------\nL1OM.CCCCC can only be used with Up, and Down conversion data types, and L1OM.sss can only be used with broadcast round.\nL1OM.SSS can only be used with swizzle conversions.\n---------------------------------------------------------------------------------------------------------------------------\nThe Width bit relates to the data size of broadcast round as 32 bit it is X=16, and 64 bit number are larger and are X=8 in the \"(1, or 4)ToX\".\nThe Width bit also relates to the Up conversion, and down conversion data size.\nCurrently in K1OM, and L1OM there are no 64 bit Up, or Down conversions.\n---------------------------------------------------------------------------------------------------------------------------\nNote 66 hex is used as data size 64 in L1OM.\n---------------------------------------------------------------------------------------------------------------------------\nThe element to grab from the array bellow is calculated mathematically.\nNote each element is an multiple of 2 in which the first element is the 32 size, and second element is 64 size.\nLastly the elements are in order to the \"CCCCC\" value, and \"SSS\" value times 2, and plus 1 if 64 data size.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst ConversionModes = [\n  //------------------------------------------------------------------------\n  \"\", \"\", //Not used.\n  //------------------------------------------------------------------------\n  \"1To16\", \"1To8\", //Settable as L1OM.sss/MVEX.sss = 001. Settable using EVEX broadcast round.\n  \"4To16\", \"4To8\", //Settable as L1OM.sss/MVEX.sss = 010. Settable using EVEX broadcast round.\n  //------------------------------------------------------------------------\n  \"Float16\", \"Error\", //Settable as \"MVEX.sss = 011\", and \"L1OM.sss = 110 , L1OM.CCCCC = 00001\".\n  //------------------------------------------------------------------------\n  \"Float16RZ\", \"Error\", //Settable only as L1OM.CCCCC = 00010.\n  //------------------------------------------------------------------------\n  \"SRGB8\", \"Error\", //Settable only as L1OM.CCCCC = 00011.\n  /*------------------------------------------------------------------------\n  MVEX/L1OM Up conversion, and down conversion types.\n  ------------------------------------------------------------------------*/\n  \"UInt8\", \"Error\", //Settable as L1OM.sss/MVEX.sss = 100, and L1OM.CCCCC = 00100.\n  \"SInt8\", \"Error\", //Settable as L1OM.sss/MVEX.sss = 101, and L1OM.CCCCC = 00101.\n  //------------------------------------------------------------------------\n  \"UNorm8\", \"Error\", //Settable as L1OM.sss = 101, or L1OM.CCCCC = 00110.\n  \"SNorm8\", \"Error\", //Settable as L1OM.CCCCC = 00111.\n  //------------------------------------------------------------------------\n  \"UInt16\", \"Error\", //Settable as L1OM.sss/MVEX.sss = 110, and L1OM.CCCCC = 01000\n  \"SInt16\", \"Error\", //Settable as L1OM.sss/MVEX.sss = 111, and L1OM.CCCCC = 01001\n  //------------------------------------------------------------------------\n  \"UNorm16\", \"Error\", //Settable as L1OM.CCCCC = 01010.\n  \"SNorm16\", \"Error\", //Settable as L1OM.CCCCC = 01011.\n  \"UInt8I\", \"Error\", //Settable as L1OM.CCCCC = 01100.\n  \"SInt8I\", \"Error\", //Settable as L1OM.CCCCC = 01101.\n  \"UInt16I\", \"Error\", //Settable as L1OM.CCCCC = 01110.\n  \"SInt16I\", \"Error\", //Settable as L1OM.CCCCC = 01111.\n  /*------------------------------------------------------------------------\n  L1OM Up conversion, and field conversion.\n  ------------------------------------------------------------------------*/\n  \"UNorm10A\", \"Error\", //Settable as L1OM.CCCCC = 10000. Also Usable as Integer Field control.\n  \"UNorm10B\", \"Error\", //Settable as L1OM.CCCCC = 10001. Also Usable as Integer Field control.\n  \"UNorm10C\", \"Error\", //Settable as L1OM.CCCCC = 10010. Also Usable as Integer Field control.\n  \"UNorm2D\", \"Error\", //Settable as L1OM.CCCCC = 10011. Also Usable as Integer Field control.\n  //------------------------------------------------------------------------\n  \"Float11A\", \"Error\", //Settable as L1OM.CCCCC = 10100. Also Usable as Float Field control.\n  \"Float11B\", \"Error\", //Settable as L1OM.CCCCC = 10101. Also Usable as Float Field control.\n  \"Float10C\", \"Error\", //Settable as L1OM.CCCCC = 10110. Also Usable as Float Field control.\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 10111. Also Usable as Float Field control.\n  /*------------------------------------------------------------------------\n  Unused Conversion modes.\n  ------------------------------------------------------------------------*/\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 11000.\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 11001.\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 11010.\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 11011.\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 11100.\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 11101.\n  \"Error\", \"Error\", //Settable as L1OM.CCCCC = 11110.\n  \"Error\", \"Error\"  //Settable as L1OM.CCCCC = 11111.\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe VEX Extension, and MVEX/EVEX Extension have an Vector register selection built in for Vector operation codes that use the\nvector register. This operand is only read in the \"operand string\" if an VEX, or EVEX prefix was decoded by the\nfunction ^DecodePrefixAdjustments()^, and making Extension 1 for VEX, or 2 for EVEX instead of 0 by default.\nDuring a VEX, or EVEX version of the SSE instruction the vector bits are a 4 bit binary value of 0 to 15, and are extended\nin EVEX and MVEX to 32 by adding the EVEX.V, or MVEX.V bit to the vector register value.\n---------------------------------------------------------------------------------------------------------------------------\nUsed with the function ^DecodeRegValue()^ to decode the Register value.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar VectorRegister = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe MVEX/EVEX Extension has an mask Register value selection for {K0-K7} mask to destination operand.\nThe K mask register is always displayed to the destination operand in any Vector instruction used with MVEX/EVEX settings.\n---------------------------------------------------------------------------------------------------------------------------\nThe {K} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar MaskRegister = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe EVEX Extension has an zero mask bit setting for {z} zeroing off the registers.\n---------------------------------------------------------------------------------------------------------------------------\nThe {z} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^.\n---------------------------------------------------------------------------------------------------------------------------\nIn L1OM/MVEX this is used as the {NT}/{EH} control which when used with an memory address that supports it will prevent\nthe data from going into the cache memory. Used as Hint control in the function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar HInt_ZeroMerg = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nSome operands use the value of the Immediate operand as an opcode, or upper 4 bits as Another register, or condition codes.\nThe Immediate is decoded normally, but this variable stores the integer value of the first IMM byte for the other byte\nencodings if used.\n---------------------------------------------------------------------------------------------------------------------------\nUsed By the function ^DecodeOpcode()^ for condition codes, and by ^DecodeOperands()^ using the upper four bits as a register.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar IMMValue = 0;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nPrefix G1, and G2 are used with Intel HLE, and other prefix codes such as repeat the instruction Codes F2, F3 which can be\napplied to any instruction unless it is an SIMD instruction which uses F2, and F3 as the SIMD Mode.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar PrefixG1 = \"\", PrefixG2 = \"\";\n\n/*-------------------------------------------------------------------------------------------------------------------------\nIntel HLE is used with basic arithmetic instructions like Add, and subtract, and shift operations.\nIntel HLE instructions replace the Repeat F2, and F3, also lock F0 with XACQUIRE, and XRELEASE.\n---------------------------------------------------------------------------------------------------------------------------\nThis is used by function ^DecodeInstruction()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar XRelease = false, XAcquire = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nIntel HLE flip \"G1 is used as = REP (XACQUIRE), or RENP (XRELEASE)\", and \"G2 is used as = LOCK\" if the lock prefix was\nnot read first then G1, and G2 flip. Also XACQUIRE, and XRELEASE replace REP, and REPNE if the LOCK prefix is used with\nREP, or REPNE if the instruction supports Intel HLE.\n---------------------------------------------------------------------------------------------------------------------------\nThis is used by function ^DecodeInstruction()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar HLEFlipG1G2 = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nReplaces segment overrides CS, and DS with HT, and HNT prefix for Branch taken and not taken used by jump instructions.\n---------------------------------------------------------------------------------------------------------------------------\nThis is used by functions ^Decode_ModRM_SIB_Address()^, and ^DecodeInstruction()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar HT = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nInstruction that support MPX replace the REPNE prefix with BND if operation is a MPX instruction.\n---------------------------------------------------------------------------------------------------------------------------\nThis is used by function ^DecodeInstruction()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar BND = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Invalid Instruction variable is very important as some bit settings in vector extensions create invalid operation codes.\nAlso some opcodes are invalid in different cpu bit modes.\n---------------------------------------------------------------------------------------------------------------------------\nFunction ^DecodePrefixAdjustments()^ Set the Invalid Opcode if an instruction or prefix is compared that is invalid for CPU bit mode.\nThe function ^DecodeInstruction()^ returns an invalid instruction if Invalid Operation is used for CPU bit mode.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nvar InvalidOp = false;\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Register array holds arrays in order from 0 though 7 for the GetOperandSize function Which goes by Prefix size settings,\nand SIMD Vector length instructions using the adjusted variable SizeAttrSelect.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by functions ^DecodeRegValue()^, ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst REG = [\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 0 Is used only if the value returned from the GetOperandSize is 0 in value which is the 8 bit general use\n  Arithmetic registers names. Note that these same registers can be made 16 bit across instead of using just the first 8 bit\n  in size it depends on the instruction codes extension size.\n  ---------------------------------------------------------------------------------------------------------------------------\n  The function ^GetOperandSize()^ takes the size value the instruction uses for it's register selection by looking up binary\n  bit positions in the size value in log 2. Different instructions can be adjusted to different sizes using the operand size\n  override adjustment code, or width bit to adjust instructions to 64 in size introduced by AMD64, and EM64T in 64 bit computers.\n  ---------------------------------------------------------------------------------------------------------------------------\n  REG array Index 0 is the first 8 bit's of Arithmetic registers, however they can be used in both high, and low order in\n  which the upper 16 bit's is used as 8 bit's for H (High part), and the first 8 bits is L (LOW part) unless the rex prefix is\n  used then the first 8 bit's is used by all general use arithmetic registers. Because of this the array is broken into two\n  name listings that is used with the \"RValue\" number given to the function ^DecodeRegValue()^.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    /*-------------------------------------------------------------------------------------------------------------------------\n    8 bit registers without any rex prefix active is the normal low byte to high byte order of the\n    first 4 general use registers \"A, C, D, and B\" using 8 bits.\n    -------------------------------------------------------------------------------------------------------------------------*/\n    [\n      //Registers 8 bit names without any rex prefix index 0 to 7.\n      \"AL\", \"CL\", \"DL\", \"BL\", \"AH\", \"CH\", \"DH\", \"BH\"\n    ],\n    /*-------------------------------------------------------------------------------------------------------------------------\n    8 bit registers with any rex prefix active uses all 15 registers in low byte order.\n    -------------------------------------------------------------------------------------------------------------------------*/\n    [\n      //Registers 8 bit names with any rex prefix index 0 to 7.\n      \"AL\", \"CL\", \"DL\", \"BL\", \"SPL\", \"BPL\", \"SIL\", \"DIL\",\n      /*-------------------------------------------------------------------------------------------------------------------------\n      Registers 8 bit names Extended using the REX.R extend setting in the Rex prefix, or VEX.R bit, or EVEX.R.\n      What ever RegExtend is set based on prefix settings is added to the select Reg Index\n      -------------------------------------------------------------------------------------------------------------------------*/\n      \"R8B\", \"R9B\", \"R10B\", \"R11B\", \"R12B\", \"R13B\", \"R14B\", \"R15B\"\n    ]\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 1 Is used only if the value returned from the GetOperandSize function is 1 in value in which bellow is the\n  general use Arithmetic register names 16 in size.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Registers 16 bit names index 0 to 15.\n    \"AX\", \"CX\", \"DX\", \"BX\", \"SP\", \"BP\", \"SI\", \"DI\", \"R8W\", \"R9W\", \"R10W\", \"R11W\", \"R12W\", \"R13W\", \"R14W\", \"R15W\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 2 Is used only if the value from the GetOperandSize function is 2 in value in which bellow is the\n  general use Arithmetic register names 32 in size.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Registers 32 bit names index 0 to 15.\n    \"EAX\", \"ECX\", \"EDX\", \"EBX\", \"ESP\", \"EBP\", \"ESI\", \"EDI\", \"R8D\", \"R9D\", \"R10D\", \"R11D\", \"R12D\", \"R13D\", \"R14D\", \"R15D\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 3 Is used only if the value returned from the GetOperandSize function is 3 in value in which bellow is the\n  general use Arithmetic register names 64 in size.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //general use Arithmetic registers 64 names index 0 to 15.\n    \"RAX\", \"RCX\", \"RDX\", \"RBX\", \"RSP\", \"RBP\", \"RSI\", \"RDI\", \"R8\", \"R9\", \"R10\", \"R11\", \"R12\", \"R13\", \"R14\", \"R15\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 4 SIMD registers 128 across in size names. The SIMD registers are used by the SIMD Vector math unit.\n  Used only if the value from the GetOperandSize function is 4 in value.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Register XMM names index 0 to 15.\n    \"XMM0\", \"XMM1\", \"XMM2\", \"XMM3\", \"XMM4\", \"XMM5\", \"XMM6\", \"XMM7\", \"XMM8\", \"XMM9\", \"XMM10\", \"XMM11\", \"XMM12\", \"XMM13\", \"XMM14\", \"XMM15\",\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Register XMM names index 16 to 31.\n    Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables.\n    -------------------------------------------------------------------------------------------------------------------------*/\n    \"XMM16\", \"XMM17\", \"XMM18\", \"XMM19\", \"XMM20\", \"XMM21\", \"XMM22\", \"XMM23\", \"XMM24\", \"XMM25\", \"XMM26\", \"XMM27\", \"XMM28\", \"XMM29\", \"XMM30\", \"XMM31\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 5 SIMD registers 256 across in size names.\n  Used only if the value from the GetOperandSize function is 5 in value. Set by vector length setting.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Register YMM names index 0 to 15.\n    \"YMM0\", \"YMM1\", \"YMM2\", \"YMM3\", \"YMM4\", \"YMM5\", \"YMM6\", \"YMM7\", \"YMM8\", \"YMM9\", \"YMM10\", \"YMM11\", \"YMM12\", \"YMM13\", \"YMM14\", \"YMM15\",\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Register YMM names index 16 to 31.\n    Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables.\n    -------------------------------------------------------------------------------------------------------------------------*/\n    \"YMM16\", \"YMM17\", \"YMM18\", \"YMM19\", \"YMM20\", \"YMM21\", \"YMM22\", \"YMM23\", \"YMM24\", \"YMM25\", \"YMM26\", \"YMM27\", \"YMM28\", \"YMM29\", \"YMM30\", \"YMM31\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 6 SIMD registers 512 across in size names.\n  Used only if the value from the GetOperandSize function is 6 in value. Set by Vector length setting.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Register ZMM names index 0 to 15.\n    \"ZMM0\", \"ZMM1\", \"ZMM2\", \"ZMM3\", \"ZMM4\", \"ZMM5\", \"ZMM6\", \"ZMM7\", \"ZMM8\", \"ZMM9\", \"ZMM10\", \"ZMM11\", \"ZMM12\", \"ZMM13\", \"ZMM14\", \"ZMM15\",\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Register ZMM names index 16 to 31.\n    Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables.\n    -------------------------------------------------------------------------------------------------------------------------*/\n    \"ZMM16\", \"ZMM17\", \"ZMM18\", \"ZMM19\", \"ZMM20\", \"ZMM21\", \"ZMM22\", \"ZMM23\", \"ZMM24\", \"ZMM25\", \"ZMM26\", \"ZMM27\", \"ZMM28\", \"ZMM29\", \"ZMM30\", \"ZMM31\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 7 SIMD registers 1024 bit. The SIMD registers have not been made this long yet.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Register unknowable names index 0 to 15.\n    \"?MM0\", \"?MM1\", \"?MM2\", \"?MM3\", \"?MM4\", \"?MM5\", \"?MM6\", \"?MM7\", \"?MM8\", \"?MM9\", \"?MM10\", \"?MM11\", \"?MM12\", \"?MM13\", \"?MM14\", \"?MM15\",\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Register unknowable names index 16 to 31.\n    Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables.\n    -------------------------------------------------------------------------------------------------------------------------*/\n    \"?MM16\", \"?MM17\", \"?MM18\", \"?MM19\", \"?MM20\", \"?MM21\", \"?MM22\", \"?MM23\", \"?MM24\", \"?MM25\", \"?MM26\", \"?MM27\", \"?MM28\", \"?MM29\", \"?MM30\", \"?MM31\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  The Registers bellow do not change size they are completely separate, thus are used for special purposes. These registers\n  are selected by using size as a value for the index instead instead of giving size to the function ^GetOperandSize()^.\n  ---------------------------------------------------------------------------------------------------------------------------\n  REG array Index 8 Segment Registers.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Segment Registers names index 0 to 7\n    \"ES\", \"CS\", \"SS\", \"DS\", \"FS\", \"GS\", \"ST(-2)\", \"ST(-1)\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 9 Stack, and MM registers used by the X87 Float point unit.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //ST registers Names index 0 to 7\n    //note these are used with the X87 FPU, but are aliased to MM in MMX SSE.\n    \"ST(0)\", \"ST(1)\", \"ST(2)\", \"ST(3)\", \"ST(4)\", \"ST(5)\", \"ST(6)\", \"ST(7)\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG index 10 Intel MM qword technology MMX vector instructions.\n  ---------------------------------------------------------------------------------------------------------------------------\n  These can not be used with Vector length adjustment used in vector extensions. The MM register are the ST registers aliased\n  to MM register. Instructions that use these registers use the SIMD vector unit registers (MM), these are called the old\n  MMX vector instructions. When Intel added the SSE instructions to the SIMD math vector unit the new 128 bit XMM registers,\n  are added into the SIMD unit then they ware made longer in size 256, then 512 across in length, with 1024 (?MM Reserved)\n  In which the vector length setting was added to control there size though vector setting adjustment codes. Instruction\n  that can be adjusted by vector length are separate from the MM registers, but still use the same SIMD unit. Because of this\n  some Vector instruction codes can not be used with vector extension setting codes.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //Register MM names index 0 to 7\n    \"MM0\", \"MM1\", \"MM2\", \"MM3\", \"MM4\", \"MM5\", \"MM6\", \"MM7\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG Array Index 11 bound registers introduced with MPX instructions.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //BND0 to BND3,and CR0 to CR3 for two byte opcodes 0x0F1A,and 0x0F1B register index 0 to 7\n    \"BND0\", \"BND1\", \"BND2\", \"BND3\", \"CR0\", \"CR1\", \"CR2\", \"CR3\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 12 control registers depending on the values they are set changes the modes of the CPU.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //control Registers index 0 to 15\n    \"CR0\", \"CR1\", \"CR2\", \"CR3\", \"CR4\", \"CR5\", \"CR6\", \"CR7\", \"CR8\", \"CR9\", \"CR10\", \"CR11\", \"CR12\", \"CR13\", \"CR14\", \"CR15\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 13 Debug mode registers.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //debug registers index 0 to 15\n    \"DR0\", \"DR1\", \"DR2\", \"DR3\", \"DR4\", \"DR5\", \"DR6\", \"DR7\", \"DR8\", \"DR9\", \"DR10\", \"DR11\", \"DR12\", \"DR13\", \"DR14\", \"DR15\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG array Index 14 test registers.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //TR registers index 0 to 7\n    \"TR0\", \"TR1\", \"TR2\", \"TR3\", \"TR4\", \"TR5\", \"TR6\", \"TR7\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG Array Index 15 SIMD vector mask registers.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    //K registers index 0 to 7, because of vector extensions it is repeated till last extension.\n    \"K0\", \"K1\", \"K2\", \"K3\", \"K4\", \"K5\", \"K6\", \"K7\",\"K0\", \"K1\", \"K2\", \"K3\", \"K4\", \"K5\", \"K6\", \"K7\",\n    \"K0\", \"K1\", \"K2\", \"K3\", \"K4\", \"K5\", \"K6\", \"K7\",\"K0\", \"K1\", \"K2\", \"K3\", \"K4\", \"K5\", \"K6\", \"K7\"\n  ],\n  /*-------------------------------------------------------------------------------------------------------------------------\n  REG Array Index 16 SIMD L1OM vector registers.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  [\n    \"V0\", \"V1\", \"V2\", \"V3\", \"V4\", \"V5\", \"V6\", \"V7\", \"V8\", \"V9\", \"V10\", \"V11\", \"V12\", \"V13\", \"V14\", \"V15\",\n    \"V16\", \"V17\", \"V18\", \"V19\", \"V20\", \"V21\", \"V22\", \"V23\", \"V24\", \"V25\", \"V26\", \"V27\", \"V28\", \"V29\", \"V30\", \"V31\"\n  ]\n];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nRAM Pointer sizes are controlled by the GetOperandSize function which uses the Size Setting attributes for\nthe select pointer in the PTR array alignment. The REG array above uses the same alignment to the returned\nsize attribute except address pointers have far address pointers which are 16 bits plus there (8, or 16)/32/64 size attribute.\n---------------------------------------------------------------------------------------------------------------------------\nFar pointers add 16 bits to the default pointer sizes.\n16 bits become 16+16=32 DWORD, 32 bits becomes 32+16=48 FWORD, and 64+16=80 TBYTE.\nThe function GetOperandSize goes 0=8 bit, 1=16 bit, 2=32 bit, 3=64 bit, 4=128, 5=256, 6=512, 7=1024.\n---------------------------------------------------------------------------------------------------------------------------\nThe pointers are stored in doubles this is so every second position is each size setting.\nSo the Returned size attribute has to be in multiples of 2 each size multiplied by 2 looks like this.\n(0*2=0)=8 bit, (1*2=2)=16 bit, (2*2=4)=32 bit, (3*2=6)=64 bit, (4*2=8)=128, (5*2=10)=256, (6*2=12)=512.\nThis is the same as moving by 2 this is why each pointer is in groups of two before the next line.\nWhen the 16 bit shift is used for far pointers only plus one is added for the 16 bit shifted name of the pointer.\n---------------------------------------------------------------------------------------------------------------------------\nUsed by the function ^Decode_ModRM_SIB_Address()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst PTR = [\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 0 when GetOperandSize returns size 0 then times 2 for 8 bit pointer.\n  In plus 16 bit shift array index 0 is added by 1 making 0+1=1 no pointer name is used.\n  The blank pointer is used for instructions like LEA which loads the effective address.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"BYTE PTR \",\"\",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 2 when GetOperandSize returns size 1 then times 2 for 16 bit pointer alignment.\n  In plus 16 bit shift index 2 is added by 1 making 2+1=3 The 32 bit pointer name is used (mathematically 16+16=32).\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"WORD PTR \",\"DWORD PTR \",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 4 when GetOperandSize returns size 2 then multiply by 2 for index 4 for the 32 bit pointer.\n  In plus 16 bit shift index 4 is added by 1 making 4+1=5 the 48 bit Far pointer name is used (mathematically 32+16=48).\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"DWORD PTR \",\"FWORD PTR \",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 6 when GetOperandSize returns size 3 then multiply by 2 gives index 6 for the 64 bit pointer.\n  The Non shifted 64 bit pointer has two types the 64 bit vector \"MM\", and regular \"QWORD\" the same as the REG array.\n  In plus 16 bit shift index 6 is added by 1 making 6+1=7 the 80 bit TBYTE pointer name is used (mathematically 64+16=80).\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"QWORD PTR \",\"TBYTE PTR \",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 8 when GetOperandSize returns size 4 then multiply by 2 gives index 8 for the 128 bit Vector pointer.\n  In far pointer shift the MMX vector pointer is used.\n  MM is designed to be used when the by size system is false using index 9 for Pointer, and index 10 for Reg.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"XMMWORD PTR \",\"MMWORD PTR \",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 10 when GetOperandSize returns size 5 then multiply by 2 gives index 10 for the 256 bit SIMD pointer.\n  In far pointer shift the OWORD pointer is used with the bounds instructions it is also designed to be used when the by size is set false same as MM.\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"YMMWORD PTR \",\"OWORD PTR \",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 12 when GetOperandSize returns size 6 then multiply by 2 gives index 12 for the 512 bit pointer.\n  In plus 16 bit shift index 12 is added by 1 making 12+1=13 there is no 528 bit pointer name (mathematically 5126+16=528).\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"ZMMWORD PTR \",\"ERROR PTR \",\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Pointer array index 14 when GetOperandSize returns size 7 then multiply by 2 gives index 12 for the 1024 bit pointer.\n  In plus 16 bit shift index 14 is added by 1 making 12+1=13 there is no 1 bit pointer name (mathematically 5126+16=528).\n  -------------------------------------------------------------------------------------------------------------------------*/\n  \"?MMWORD PTR \",\"ERROR PTR \"];\n\n/*-------------------------------------------------------------------------------------------------------------------------\nSIB byte scale Note the Scale bits value is the selected index of the array bellow only used under\na Memory address that uses the SIB Address mode which uses another byte for the address selection.\n---------------------------------------------------------------------------------------------------------------------------\nused by the ^Decode_ModRM_SIB_Address function()^.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nconst scale = [\n \"\", //when scale bits are 0 in value no scale multiple is used\n \"*2\", //when scale bits are 1 in value a scale multiple of times two is used\n \"*4\", //when scale bits are 2 in value a scale multiple of times four is used\n \"*8\"  //when scale bits are 3 in value a scale multiple of times eight is used\n ];\n \n/*-------------------------------------------------------------------------------------------------------------------------\nThis function changes the Mnemonics array, for older instruction codes used by specific X86 cores that are under the same instruction codes.\n---------------------------------------------------------------------------------------------------------------------------\nInput \"type\" can be any number 0 to 6. If the input is 0 it sets the mnemonics back to normal.\nIf input \"type\" is set 1 it will adjust the few conflicting mask instructions to the K1OM instruction names used by the knights corner processor.\nIf input \"type\" is set 2 it will adjust the mnemonic array to decode Larrabee instructions.\nIf input \"type\" is set 3 it will adjust the mnemonic array to decode Cyrix instructions which are now deprecated from the architecture.\nIf input \"type\" is set 4 it will adjust the mnemonic array to decode Geode instructions which are now deprecated from the architecture.\nIf input \"type\" is set 5 it will adjust the mnemonic array to decode Centaur instructions which are now deprecated from the architecture.\nIf input \"type\" is set 6 it will adjust the mnemonic array to decode instruction for the X86/486 CPU which conflict with the vector unit instructions with UMOV.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nexport function CompatibilityMode( type )\n{\n  //Reset the changeable sections of the Mnemonics array, and operand encoding array.\n  \n  Mnemonics[0x062] = [\"BOUND\",\"BOUND\",\"\"];\n  Mnemonics[0x110] = [[\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"],[\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"]];\n  Mnemonics[0x111] = [[\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"],[\"MOVUPS\",\"MOVUPD\",\"MOVSS\",\"MOVSD\"]];\n  Mnemonics[0x112] = [[\"MOVLPS\",\"MOVLPD\",\"MOVSLDUP\",\"MOVDDUP\"],[\"MOVHLPS\",\"???\",\"MOVSLDUP\",\"MOVDDUP\"]];\n  Mnemonics[0x113] = [[\"MOVLPS\",\"MOVLPD\",\"???\",\"???\"],\"???\"];\n  Mnemonics[0x138] = \"\"; Mnemonics[0x139] = \"???\"; Mnemonics[0x13A] = \"\"; Mnemonics[0x13B] = \"???\"; Mnemonics[0x13C] = \"???\"; Mnemonics[0x13D] = \"???\"; Mnemonics[0x13F] = \"???\";\n  Mnemonics[0x141] = [[\"CMOVNO\",[\"KANDW\",\"\",\"KANDQ\"],\"\",\"\"],[\"CMOVNO\",[\"KANDB\",\"\",\"KANDD\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x142] = [[\"CMOVB\",[\"KANDNW\",\"\",\"KANDNQ\"],\"\",\"\"],[\"CMOVB\",[\"KANDNB\",\"\",\"KANDND\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x144] = [[\"CMOVE\",[\"KNOTW\",\"\",\"KNOTQ\"],\"\",\"\"],[\"CMOVE\",[\"KNOTB\",\"\",\"KNOTD\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x145] = [[\"CMOVNE\",[\"KORW\",\"\",\"KORQ\"],\"\",\"\"],[\"CMOVNE\",[\"KORB\",\"\",\"KORD\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x146] = [[\"CMOVBE\",[\"KXNORW\",\"\",\"KXNORQ\"],\"\",\"\"],[\"CMOVBE\",[\"KXNORB\",\"\",\"KXNORD\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x147] = [[\"CMOVA\",[\"KXORW\",\"\",\"KXORQ\"],\"\",\"\"],[\"CMOVA\",[\"KXORB\",\"\",\"KXORD\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x150] = [\"???\",[[\"MOVMSKPS\",\"MOVMSKPS\",\"\",\"\"],[\"MOVMSKPD\",\"MOVMSKPD\",\"\",\"\"],\"???\",\"???\"]];\n  Mnemonics[0x151] = [\"SQRTPS\",\"SQRTPD\",\"SQRTSS\",\"SQRTSD\"];\n  Mnemonics[0x152] = [[\"RSQRTPS\",\"RSQRTPS\",\"\",\"\"],\"???\",[\"RSQRTSS\",\"RSQRTSS\",\"\",\"\"],\"???\"];\n  Mnemonics[0x154] = [\"ANDPS\",\"ANDPD\",\"???\",\"???\"];\n  Mnemonics[0x155] = [\"ANDNPS\",\"ANDNPD\",\"???\",\"???\"];\n  Mnemonics[0x158] = [[\"ADDPS\",\"ADDPS\",\"ADDPS\",\"ADDPS\"],[\"ADDPD\",\"ADDPD\",\"ADDPD\",\"ADDPD\"],\"ADDSS\",\"ADDSD\"];\n  Mnemonics[0x159] = [[\"MULPS\",\"MULPS\",\"MULPS\",\"MULPS\"],[\"MULPD\",\"MULPD\",\"MULPD\",\"MULPD\"],\"MULSS\",\"MULSD\"];\n  Mnemonics[0x15A] = [[\"CVTPS2PD\",\"CVTPS2PD\",\"CVTPS2PD\",\"CVTPS2PD\"],[\"CVTPD2PS\",\"CVTPD2PS\",\"CVTPD2PS\",\"CVTPD2PS\"],\"CVTSS2SD\",\"CVTSD2SS\"];\n  Mnemonics[0x15B] = [[[\"CVTDQ2PS\",\"\",\"CVTQQ2PS\"],\"CVTPS2DQ\",\"\"],\"???\",\"CVTTPS2DQ\",\"???\"];\n  Mnemonics[0x15C] = [[\"SUBPS\",\"SUBPS\",\"SUBPS\",\"SUBPS\"],[\"SUBPD\",\"SUBPD\",\"SUBPD\",\"SUBPD\"],\"SUBSS\",\"SUBSD\"];\n  Mnemonics[0x15D] = [\"MINPS\",\"MINPD\",\"MINSS\",\"MINSD\"];\n  Mnemonics[0x15E] = [\"DIVPS\",\"DIVPD\",\"DIVSS\",\"DIVSD\"];\n  Mnemonics[0x178] = [[\"VMREAD\",\"\",[\"CVTTPS2UDQ\",\"\",\"CVTTPD2UDQ\"],\"\"],[\"EXTRQ\",\"\",[\"CVTTPS2UQQ\",\"\",\"CVTTPD2UQQ\"],\"\"],[\"???\",\"\",\"CVTTSS2USI\",\"\"],[\"INSERTQ\",\"\",\"CVTTSD2USI\",\"\"]];\n  Mnemonics[0x179] = [[\"VMWRITE\",\"\",[\"CVTPS2UDQ\",\"\",\"CVTPD2UDQ\"],\"\"],[\"EXTRQ\",\"\",[\"CVTPS2UQQ\",\"\",\"CVTPD2UQQ\"],\"\"],[\"???\",\"\",\"CVTSS2USI\",\"\"],[\"INSERTQ\",\"\",\"CVTSD2USI\",\"\"]];\n  Mnemonics[0x17A] = [\"???\",[\"\",\"\",[\"CVTTPS2QQ\",\"\",\"CVTTPD2QQ\"],\"\"],[\"\",\"\",[\"CVTUDQ2PD\",\"\",\"CVTUQQ2PD\"],\"CVTUDQ2PD\"],[\"\",\"\",[\"CVTUDQ2PS\",\"\",\"CVTUQQ2PS\"],\"\"]];\n  Mnemonics[0x17B] = [\"???\",[\"\",\"\",[\"CVTPS2QQ\",\"\",\"CVTPD2QQ\"],\"\"],[\"\",\"\",\"CVTUSI2SS\",\"\"],[\"\",\"\",\"CVTUSI2SD\",\"\"]];\n  Mnemonics[0x17C] = [\"???\",[\"HADDPD\",\"HADDPD\",\"\",\"\"],\"???\",[\"HADDPS\",\"HADDPS\",\"\",\"\"]];\n  Mnemonics[0x17D] = [\"???\",[\"HSUBPD\",\"HSUBPD\",\"\",\"\"],\"???\",[\"HSUBPS\",\"HSUBPS\",\"\",\"\"]];\n  Mnemonics[0x17E] = [[\"MOVD\",\"\",\"\",\"\"],[\"MOVD\",\"\",\"MOVQ\"],[\"MOVQ\",\"MOVQ\",[\"???\",\"\",\"MOVQ\"],\"\"],\"???\"],\n  Mnemonics[0x190] = [[\"SETO\",[\"KMOVW\",\"\",\"KMOVQ\"],\"\",\"\"],[\"SETO\",[\"KMOVB\",\"\",\"KMOVD\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x192] = [[\"SETB\",[\"KMOVW\",\"\",\"???\"],\"\",\"\"],[\"SETB\",[\"KMOVB\",\"\",\"???\"],\"\",\"\"],\"\",[\"SETB\",[\"KMOVD\",\"\",\"KMOVQ\"],\"\",\"\"]];\n  Mnemonics[0x193] = [[\"SETAE\",[\"KMOVW\",\"\",\"???\"],\"\",\"\"],[\"SETAE\",[\"KMOVB\",\"\",\"???\"],\"\",\"\"],\"\",[\"SETAE\",[\"KMOVD\",\"\",\"KMOVQ\"],\"\",\"\"]];\n  Mnemonics[0x198] = [[\"SETS\",[\"KORTESTW\",\"\",\"KORTESTQ\"],\"\",\"\"],[\"SETS\",[\"KORTESTB\",\"\",\"KORTESTD\"],\"\",\"\"],\"\",\"\"];\n  Mnemonics[0x1A6] = \"XBTS\";\n  Mnemonics[0x1A7] = \"IBTS\";\n\n  Operands[0x110] = [[\"0B700770\",\"0B700770\",\"0A040603\",\"0A040609\"],[\"0B700770\",\"0B700770\",\"0A0412040604\",\"0A0412040604\"]];\n  Operands[0x111] = [[\"07700B70\",\"07700B70\",\"06030A04\",\"06090A04\"],[\"07700B70\",\"07700B70\",\"060412040A04\",\"060412040A04\"]];\n  Operands[0x112] = [[\"0A0412040606\",\"0A0412040606\",\"0B700770\",\"0B700768\"],[\"0A0412040604\",\"\",\"0B700770\",\"0B700770\"]];\n  Operands[0x113] = [[\"06060A04\",\"06060A04\",\"\",\"\"],\"\"];\n  Operands[0x141] = [[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x142] = [[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x144] = [[\"0B0E070E0180\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],[\"0B0E070E0180\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x145] = [[\"0A02070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],[\"0A02070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x146] = [[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x147] = [[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"0A0F120F06FF\"],\"\",\"\"],[\"0B0E070E0180\",[\"0A0F120F06FF\",\"\",\"\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x150] = [\"\",[[\"0B0C0648\",\"0B0C0730\",\"\",\"\"],[\"0B0C0648\",\"0B0C0730\",\"\",\"\"],\"\",\"\"]];\n  Operands[0x151] = [\"0B7007700112\",\"0B7007700112\",\"0A04120406430102\",\"0A04120406490102\"];\n  Operands[0x152] = [[\"0A040648\",\"0A040648\",\"\",\"\"],\"\",[\"0A040643\",\"0A0412040643\",\"\",\"\"],\"\"];\n  Operands[0x154] = [\"0B70137007700110\",\"0B70137007700110\",\"\",\"\"];\n  Operands[0x155] = [\"0B70137007700110\",\"0B70137007700110\",\"\",\"\"];\n  Operands[0x158] = [[\"0A040648\",\"0B3013300730\",\"0B70137007700112\",\"0A061206066C0172\"],[\"0A040648\",\"0B3013300730\",\"0B70137007700112\",\"0A061206066C0112\"],\"0A04120406430102\",\"0A04120406460102\"];\n  Operands[0x159] = [[\"0A040648\",\"0B3013300730\",\"0B70137007700112\",\"0A061206066C0172\"],[\"0A040648\",\"0B3013300730\",\"0B70137007700112\",\"0A061206066C0112\"],\"0A04120406430102\",\"0A04120406460102\"];\n  Operands[0x15A] = [[\"0A040648\",\"0B300718\",\"0B7007380111\",\"0A06065A0111\"],[\"0A040648\",\"0B180730\",\"0B3807700112\",\"0A05066C0112\"],\"0A04120406430101\",\"0A04120406460102\"];\n  Operands[0x15B] = [[[\"0B7007700112\",\"\",\"0B380770011A\"],\"0B700770011A\",\"\",\"\"],\"\",\"0B7007700111\",\"\"];\n  Operands[0x15C] = [[\"0A060648\",\"0B3013300730\",\"0B70137007700112\",\"0A061206066C0172\"],[\"0A060648\",\"0B3013300730\",\"0B70137007700112\",\"0A061206066C0112\"],\"0A04120406430102\",\"0A04120406460102\"];\n  Operands[0x15D] = [\"0B70137007700111\",\"0B70137007700111\",\"0A04120406430101\",\"0A04120406460101\"];\n  Operands[0x15E] = [\"0B70137007700112\",\"0B70137007700112\",\"0A04120406430102\",\"0A04120406460102\"];\n  Operands[0x178] = [[\"07080B080180\",\"\",[\"0B7007700111\",\"\",\"0B3807700119\"],\"\"],[\"064F0C000C00\",\"\",[\"0B7007380119\",\"\",\"0B7007700111\"],\"\"],[\"\",\"\",\"0B0C06440109\",\"\"],[\"0A04064F0C000C00\",\"\",\"0B0C06460109\",\"\"]];\n  Operands[0x179] = [[\"0B0807080180\",\"\",[\"0B7007700112\",\"\",\"0B380770011A\"],\"\"],[\"0A04064F\",\"\",[\"0B700738011A\",\"\",\"0B7007700112\"],\"\"],[\"\",\"\",\"0B0C0644010A\",\"\"],[\"0A04064F\",\"\",\"0B0C0646010A\",\"\"]];\n  Operands[0x17A] = [\"\",[\"\",\"\",[\"0B7007380119\",\"\",\"0B7007700111\"],\"\"],[\"\",\"\",[\"0B7007380112\",\"\",\"0B700770011A\"],\"0A06065A0112\"],[\"\",\"\",[\"0B700770011A\",\"\",\"0B3807700112\"],\"\"]];\n  Operands[0x17B] = [\"\",[\"\",\"\",[\"0B700738011A\",\"\",\"0B7007700112\"],\"\"],[\"\",\"\",\"0A041204070C010A\",\"\"],[\"\",\"\",\"0A041204070C010A\",\"\"]];\n  Operands[0x17C] = [\"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"],\"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"]];\n  Operands[0x17D] = [\"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"],\"\",[\"0A040604\",\"0B7013700770\",\"\",\"\"]];\n  Operands[0x17E] = [[\"070C0A0A\",\"\",\"\",\"\"],[\"06240A040108\",\"\",\"06360A040108\"],[\"0A040646\",\"0A040646\",[\"\",\"\",\"0A0406460108\"],\"\"],\"\"];\n  Operands[0x190] = [[\"0600\",[\"0A0F0612\",\"\",\"0A0F0636\"],\"\",\"\"],[\"0600\",[\"0A0F0600\",\"\",\"0A0F0624\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x192] = [[\"0600\",[\"0A0F06F4\",\"\",\"\"],\"\",\"\"],[\"0600\",[\"0A0F06F4\",\"\",\"\"],\"\",\"\"],\"\",[\"0600\",[\"0A0F06F6\",\"\",\"0A0F06F6\"],\"\",\"\"]];\n  Operands[0x193] = [[\"0600\",[\"06F40A0F\",\"\",\"\"],\"\",\"\"],[\"0600\",[\"06F40A0F\",\"\",\"\"],\"\",\"\"],\"\",[\"0600\",[\"06F60A0F\",\"\",\"06F60A0F\"],\"\",\"\"]];\n  Operands[0x198] = [[\"0600\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],[\"0600\",[\"0A0F06FF\",\"\",\"0A0F06FF\"],\"\",\"\"],\"\",\"\"];\n  Operands[0x1A6] = \"0B0E070E\";\n  Operands[0x1A7] = \"070E0B0E\";\n  \n  //Adjust the VEX mask instructions for K1OM (Knights corner) which conflict with the enhanced AVX512 versions.\n\t\n  if( type === 1 )\n  {\n    Mnemonics[0x141] = [[\"CMOVNO\",\"KAND\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x142] = [[\"CMOVB\",\"KANDN\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x144] = [[\"CMOVE\",\"KNOT\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x145] = [[\"CMOVNE\",\"KOR\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x146] = [[\"CMOVBE\",\"KXNOR\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x147] = [[\"CMOVA\",\"KXOR\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x190] = [[\"SETO\",\"KMOV\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x192] = [[\"SETB\",\"KMOV\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x193] = [[\"SETAE\",\"KMOV\",\"\",\"\"],\"\",\"\",\"\"];\n    Mnemonics[0x198] = [[\"SETS\",\"KORTEST\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x141] = [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x142] = [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x144] = [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x145] = [[\"0A02070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x146] = [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x147] = [[\"0B0E070E0180\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x190] = [[\"0600\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x192] = [[\"0600\",\"06FF0B06\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x193] = [[\"0600\",\"07060A0F\",\"\",\"\"],\"\",\"\",\"\"];\n    Operands[0x198] = [[\"0600\",\"0A0F06FF\",\"\",\"\"],\"\",\"\",\"\"];\n  }\n  \n  //Disable Knights corner, and AVX512, for L1OM (Intel Larrabee).\n  \n  if( type === 2 )\n  {\n    Mnemonics[0x62] = \"\";\n  }\n\n  //Adjust the Mnemonics, and Operand encoding, for the Cyrix processors.\n\n  if( type === 3 )\n  {\n    Mnemonics[0x138] = \"SMINT\"; Mnemonics[0x13A] = \"BB0_RESET\"; Mnemonics[0x13B] = \"BB1_RESET\"; Mnemonics[0x13C] = \"CPU_WRITE\"; Mnemonics[0x13D] = \"CPU_READ\";\n    Mnemonics[0x150] = \"PAVEB\"; Mnemonics[0x151] = \"PADDSIW\"; Mnemonics[0x152] = \"PMAGW\";\n    Mnemonics[0x154] = \"PDISTIB\"; Mnemonics[0x155] = \"PSUBSIW\";\n    Mnemonics[0x158] = \"PMVZB\"; Mnemonics[0x159] = \"PMULHRW\"; Mnemonics[0x15A] = \"PMVNZB\";\n    Mnemonics[0x15B] = \"PMVLZB\"; Mnemonics[0x15C] = \"PMVGEZB\"; Mnemonics[0x15D] = \"PMULHRIW\";\n    Mnemonics[0x15E] = \"PMACHRIW\";\n    Mnemonics[0x178] = \"SVDC\"; Mnemonics[0x179] = \"RSDC\"; Mnemonics[0x17A] = \"SVLDT\";\n    Mnemonics[0x17B] = \"RSLDT\"; Mnemonics[0x17C] = \"SVTS\"; Mnemonics[0x17D] = \"RSTS\";\n    Mnemonics[0x17E] = \"SMINT\";\n    Operands[0x150] = \"0A0A06A9\"; Operands[0x151] = \"0A0A06A9\"; Mnemonics[0x152] = \"0A0A06A9\";\n    Operands[0x154] = \"0A0A06AF\"; Operands[0x155] = \"0A0A06A9\";\n    Operands[0x158] = \"0A0A06AF\"; Operands[0x159] = \"0A0A06A9\"; Mnemonics[0x15A] = \"0A0A06AF\";\n    Operands[0x15B] = \"0A0A06AF\"; Operands[0x15C] = \"0A0A06AF\"; Mnemonics[0x15D] = \"0A0A06A9\";\n    Operands[0x15E] = \"0A0A06AF\";\n    Operands[0x178] = \"30000A08\"; Operands[0x179] = \"0A083000\"; Operands[0x17A] = \"3000\";\n    Operands[0x17B] = \"3000\"; Operands[0x17C] = \"3000\"; Operands[0x17D] = \"3000\";\n    Operands[0x17E] = \"\";\n  }\n  \n  //Adjust the Mnemonics, and Operand encoding, for the Geode processor.\n  \n  if( type === 4 )\n  {\n    Mnemonics[0x138] = \"SMINT\"; Mnemonics[0x139] = \"DMINT\"; Mnemonics[0x13A] = \"RDM\";\n  }\n  \n  //Adjust the Mnemonics, for the Centaur processor.\n\n  if( type === 5 )\n  {\n    Mnemonics[0x13F] = \"ALTINST\";\n    Mnemonics[0x1A6] = [\"???\",[\"MONTMUL\",\"XSA1\",\"XSA256\",\"???\",\"???\",\"???\",\"???\",\"???\"]];\n    Mnemonics[0x1A7] = [\n      \"???\",\n      [\n        \"XSTORE\",\n        [\"???\",\"???\",\"XCRYPT-ECB\",\"???\"],\n        [\"???\",\"???\",\"XCRYPT-CBC\",\"???\"],\n        [\"???\",\"???\",\"XCRYPT-CTR\",\"???\"],\n        [\"???\",\"???\",\"XCRYPT-CFB\",\"???\"],\n        [\"???\",\"???\",\"XCRYPT-OFB\",\"???\"],\n        \"???\",\n        \"???\"\n      ]\n    ];\n    Operands[0x1A6] = [\"\",[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]];\n    Operands[0x1A7] = [\n      \"\",\n      [\n        \"\",\n        [\"\",\"\",\"\",\"\"],\n        [\"\",\"\",\"\",\"\"],\n        [\"\",\"\",\"\",\"\"],\n        [\"\",\"\",\"\",\"\"],\n        [\"\",\"\",\"\",\"\"],\n        \"\",\n        \"\"\n      ]\n    ];\n  }\n  \n  //Adjust the Mnemonics, for the X86/486 processor and older.\n  \n  if( type === 6 )\n  {\n    Mnemonics[0x110] = \"UMOV\"; Mnemonics[0x111] = \"UMOV\"; Mnemonics[0x112] = \"UMOV\"; Mnemonics[0x113] = \"UMOV\";\n    Mnemonics[0x1A6] = \"CMPXCHG\"; Mnemonics[0x1A7] = \"CMPXCHG\";\n    Operands[0x110] = \"06000A00\"; Operands[0x111] = \"070E0B0E\"; Operands[0x112] = \"0A000600\"; Operands[0x113] = \"0B0E070E\";\n    Operands[0x1A6] = \"\"; Operands[0x1A7] = \"\";\n  }\n  \n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThis function loads the BinCode array using an hex string as input, and Resets the Code position along the array, but does not\nreset the base address. This allows programs to be decoded in sections well maintaining the accurate 64 bit base address.\n---------------------------------------------------------------------------------------------------------------------------\nThe function \"SetBasePosition()\" sets the location that the Code is from in memory.\nThe function \"GotoPosition()\" tests if the address is within rage of the current loaded binary.\nThe function \"GetPosition()\" Gives back the current base address in it's proper format for the current BitMode.\n---------------------------------------------------------------------------------------------------------------------------\nIf the hex input is invalid returns false.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nexport function LoadBinCode( HexStr )\n{\n  //Clear BinCode, and Reset Code Position in Bin Code array.\n\n  BinCode = []; CodePos = 0;\n\n  //Iterate though the hex string and covert to 0 to 255 byte values into the BinCode array.\n\n  var len = HexStr.length;\n\n  for( var i = 0, el = 0, Sign = 0, int32 = 0; i < len; i += 8 )\n  {\n    //It is faster to read 8 hex digits at a time if possible.\n\n    int32 = parseInt( HexStr.slice( i, i + 8 ), 16 );\n\n    //If input is invalid return false.\n\n    if( isNaN( int32 ) ){ return ( false ); }\n\n    //If the end of the Hex string is reached and is not 8 digits the number has to be lined up.\n\n    ( ( len - i ) < 8 ) && ( int32 <<= ( 8 - len - i ) << 2 );\n\n    //The variable sing corrects the unusable sing bits during the 4 byte rotation algorithm.\n\n    Sign = int32;\n\n    //Remove the Sign bit value if active for when the number is changed to int32 during rotation.\n\n    int32 ^= int32 & 0x80000000;\n\n    //Rotate the 32 bit int so that each number is put in order in the BinCode array. Add the Sign Bit positions back though each rotation.\n\n    int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF );\n    BinCode[el++] = ( ( ( Sign >> 24 ) & 0x80 ) | int32 ) & 0xFF;\n    int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF );\n    BinCode[el++] = ( ( ( Sign >> 16 ) & 0x80 ) | int32 ) & 0xFF;\n    int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF );\n    BinCode[el++] = ( ( ( Sign >> 8 ) & 0x80 ) | int32 ) & 0xFF;\n    int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF );\n    BinCode[el++] = ( ( Sign & 0x80 ) | int32 ) & 0xFF;\n  }\n\n  //Remove elements past the Number of bytes in HexStr because int 32 is always 4 bytes it is possible to end in an uneven number.\n\n  len >>= 1;\n\n  for(; len < BinCode.length; BinCode.pop() );\n\n  //Return true for that the binary code loaded properly.\n\n  return ( true );\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThis function moves the address by one and caries to 64 section for the Base address. The BitMode settings limit how much of\nthe 64 bit address is used in functions \"GetPosition()\", and \"GotoPosition()\", for the type of binary being disassemble.\nThis function also moves the binary code array position CodePos by one basically this function is used to progress the\ndisassembler as it is decoding a sequence of bytes.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction NextByte()\n{\n  //Add the current byte as hex to InstructionHex which will be displayed beside the decoded instruction.\n  //After an instruction decodes InstructionHex is only added beside the instruction if ShowInstructionHex is active.\n  var t;\n  if ( CodePos < BinCode.length ) //If not out of bounds.\n  {\n    //Convert current byte to String, and pad.\n\n    ( ( t = BinCode[CodePos++].toString(16) ).length === 1) && ( t = \"0\" + t );\n\n    //Add it to the current bytes used for the decode instruction.\n\n    InstructionHex += t;\n\n    //Continue the Base address.\n\n    ( ( Pos32 += 1 ) > 0xFFFFFFFF ) && ( Pos32 = 0, ( ( Pos64 += 1 ) > 0xFFFFFFFF ) && ( Pos64 = 0 ) );\n  }\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nTakes a 64/32/16 bit hex string and sets it as the address position depending on the address format it is split into an\nsegment, and offset address. Note that the Code Segment is used in 16 bit code. Also code segment is also used in 32 bit\nif set 36, or higher. Effects instruction location in memory when decoding a program.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nexport function SetBasePosition( Address )\n{\n  //Split the Segment:offset.\n\n  var t = Address.split(\":\");\n\n  //Set the 16 bit code segment position if there is one.\n\n  if ( typeof t[1] !== \"undefined\" ){ CodeSeg = parseInt( t[0].slice( t[0].length - 4 ), 16 ); Address = t[1]; }\n\n  //Adjust the Instruction pointer 16(IP)/32(EIP)/64(RIP). Also varies based on Bit Mode.\n\n  var Len = Address.length;\n\n  if( Len >= 9 && BitMode == 2 ){ Pos64 = parseInt( Address.slice( Len - 16, Len - 8 ), 16 ); }\n  if( Len >= 5 && BitMode >= 1 && !( BitMode == 1 & CodeSeg >= 36 ) ){ Pos32 = parseInt( Address.slice( Len - 8 ), 16 ); }\n  else if( Len >= 1 && BitMode >= 0 ){ Pos32 = ( Pos32 & 0xFFFF0000 ) | ( parseInt( Address.slice( Len - 4 ), 16 ) ); }\n\n  //Convert Pos32 to undignified integer.\n\n  if ( Pos32 < 0 ) { Pos32 += 0x100000000; }\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nGives back the current Instruction address position.\nIn 16 bit an instruction location is Bound to the code segment location in memory, and the first 16 bit of the instruction pointer 0 to 65535.\nIn 32 bit an instruction location uses the first 32 bit's of the instruction pointer.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction GetPosition()\n{\n  //If 16 bit Seg:Offset, or if 32 bit and CodeSeg is 36, or higher.\n\n  if( BitMode === 0 | ( BitMode === 1 & CodeSeg >= 36 ) )\n  {\n    for ( var S16 = ( Pos32 & 0xFFFF ).toString(16); S16.length < 4; S16 = \"0\" + S16 );\n    for ( var Seg = ( CodeSeg ).toString(16); Seg.length < 4; Seg = \"0\" + Seg );\n    return( ( Seg + \":\" + S16 ).toUpperCase() );\n  }\n\n  //32 bit, and 64 bit section.\n\n  var S64=\"\", S32=\"\";\n\n  //If 32 bit or higher.\n\n  if( BitMode >= 1 )\n  {\n    for ( S32 = Pos32.toString(16); S32.length < 8; S32 = \"0\" + S32 );\n  }\n\n  //If 64 bit.\n\n  if( BitMode === 2 )\n  {\n    for ( S64 = Pos64.toString(16); S64.length < 8; S64 = \"0\" + S64 );\n  }\n\n  //Return the 32/64 address.\n\n  return ( ( S64 + S32 ).toUpperCase() );\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nMoves the dissembler 64 bit address, and CodePos to correct address. Returns false if address location is out of bounds.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction GotoPosition( Address )\n{\n  //Current address location.\n\n  var LocPos32 = Pos32;\n  var LocPos64 = Pos64;\n  var LocCodeSeg = CodeSeg;\n\n  //Split the by Segment:offset address format.\n\n  var t = Address.split(\":\");\n\n  //Set the 16 bit code segment location if there is one.\n\n  if ( typeof t[1] !== \"undefined\" )\n  {\n    LocCodeSeg = parseInt(t[0].slice( t[0].length - 4 ), 16);\n    Address = t[1];\n  }\n\n  var len = Address.length;\n\n  //If the address is 64 bit's long, and bit mode is 64 bit adjust the 64 bit location.\n\n  if( len >= 9 && BitMode === 2 )\n  {\n    LocPos64 = parseInt( Address.slice( len - 16, len - 8 ), 16 );\n  }\n\n  //If the address is 32 bit's long, and bit mode is 32 bit, or higher adjust the 32 bit location.\n\n  if( len >= 5 && BitMode >= 1 & !( BitMode === 1 & CodeSeg >= 36 ) )\n  {\n    LocPos32 = parseInt( Address.slice( len - 8 ), 16 );\n  }\n\n  //Else If the address is 16 bit's long, and bit mode is 16 bit, or higher adjust the first 16 bit's in location 32.\n\n  else if( len >= 1 && BitMode >= 0 )\n  {\n    LocPos32 = ( LocPos32 - LocPos32 + parseInt( Address.slice( len - 4 ), 16 ) );\n  }\n\n  //Find the difference between the current base address and the selected address location.\n\n  var Dif32 = Pos32 - LocPos32, Dif64 = Pos64 - LocPos64;\n\n  //Only calculate the Code Segment location if The program uses 16 bit address mode otherwise the\n  //code segment does not affect the address location.\n\n  if( ( BitMode === 1 & CodeSeg >= 36 ) || BitMode === 0 )\n  {\n    Dif32 += ( CodeSeg - LocCodeSeg ) << 4;\n  }\n\n  //Before adjusting the Code Position Backup the Code Position in case that the address is out of bounds.\n\n  t = CodePos;\n\n  //Subtract the difference to the CodePos position.\n\n  CodePos -= Dif64 * 4294967296 + Dif32;\n\n  //If code position is out of bound for the loaded binary in the BinCode array, or\n  //is a negative index return false and reset CodePos.\n\n  if( CodePos < 0 || CodePos > BinCode.length )\n  {\n    CodePos = t; return ( false );\n  }\n\n  //Set the base address so that it matches the Selected address location that Code position is moved to in relative space in the BinCode Array.\n\n  CodeSeg = LocCodeSeg;\n  Pos32 = LocPos32\n  Pos64 = LocPos64;\n\n  //Return true for that the address Position is moved in range correctly.\n\n  return ( true );\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nFinds bit positions to the Size attribute indexes in REG array, and the Pointer Array. For the Size Attribute variations.\n---------------------------------------------------------------------------------------------------------------------------\nThe SizeAttribute settings is 8 digits big consisting of 1, or 0 to specify the extended size that an operand can be made.\nIn which an value of 01100100 is decoded as \"0 = 1024, 1 = 512, 1 = 256, 0 = 128, 0 = 64, 1 = 32, 0 = 16, 0 = 8\".\nIn which the largest bit position is 512, and is the 6th number \"0 = 7, 1 = 6, 1 = 5, 0 = 4, 0 = 3, 1 = 2, 0 = 1, 0 = 0\".\nIn which 6 is the bit position for 512 as the returned Size . Each size is in order from 0 to 7, thus the size given back\nfrom this function Lines up With the Pinter array, and Register array indexes for the register names by size, and Pointers.\n---------------------------------------------------------------------------------------------------------------------------\nThe variable SizeAttrSelect is separate from this function it is adjusted by prefixes that adjust Vector size, and General purpose registers.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction GetOperandSize( SizeAttribute )\n{\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  Each S value goes in order to the vector length value in EVEX, and VEX Smallest to biggest in perfect alignment.\n  SizeAttrSelect is set 1 by default, unless it is set 0 to 3 by the vector length bit's in the EVEX prefix, or 0 to 1 in the VEX prefix.\n  In which if it is not an Vector instruction S2 acts as the mid default size attribute in 32 bit mode, and 64 bit mode for all instructions.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  var S4 = 0, S3 = 0, S2 = 0, S1 = 0, S0 = -1; //Note S0 is Vector size 1024, which is unused.\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  Lookup the Highest active bit in the SizeAttribute value giving the position the bit is in the number. S1 will be the biggest size attribute.\n  In which this size attribute is only used when the extended size is active from the Rex prefix using the W (width) bit setting.\n  In which sets variable SizeAttrSelect to 2 in value when the Width bit prefix setting is decoded, or if it is an Vector this is the\n  Max vector size 512 in which when the EVEX.L'L bit's are set 10 = 2 sets SizeAttrSelect 2, note 11 = 3 is reserved for vectors 1024 in size.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  S1 = SizeAttribute; S1 = ( ( S1 & 0xF0 ) !== 0 ? ( S1 >>= 4, 4 ) : 0 ) | ( ( S1 & 0xC ) !== 0 ? ( S1 >>= 2, 2 ) : 0 ) | ( ( S1 >>= 1 ) !== 0 );\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  If there is no size attributes then set S1 to -1 then the rest are set to S1 as they should have no size setting.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  if( SizeAttribute === 0 ) { S1 = -1; }\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  Convert the Bit Position of S1 into it's value and remove it by subtracting it into the SizeAttribute settings.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  SizeAttribute -= ( 1 << S1 );\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  Lookup the Highest Second active bit in the SizeAttribute value giving the position the bit is in the number.\n  In which S2 will be the default size attribute when SizeAttrSelect is 1 and has not been changed by prefixes, or If this is an vector\n  SizeAttrSelect is set one by the EVEX.L'L bit's 01 = 1, or VEX.L is active 1 = 1 in which the Mid vector size is used.\n  In which 256 is the Mid vector size some vectors are smaller some go 64/128/256 in which the mid size is 128.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  S2 = SizeAttribute; S2 = ( ( S2 & 0xF0 ) !== 0 ? ( S2 >>= 4, 4 ) : 0 ) | ( ( S2 & 0xC ) !== 0 ? ( S2 >>= 2, 2 ) : 0 ) | ( ( S2 >>= 1 ) !== 0 );\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  Convert the Bit Position of S2 into it's value and remove it by subtracting it if it is not 0.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  if( S2 !== 0 ) { SizeAttribute -= ( 1 << S2 ); }\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  If it is 0 The highest size attribute is set as the default operand size. So S2 is aliased to S1, if there is no other Size setting attributes.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  else { S2 = S1; }\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  Lookup the Highest third active bit in the SizeAttribute value giving the position the bit is in the number.\n  The third Size is only used if the Operand override prefix is used setting SizeAttrSelect to 0, or if this is an vector the\n  EVEX.L'L bit's are 00 = 0 sets SizeAttrSelect 0, or VEX.L = 0 in which SizeAttrSelect is 0 using the smallest vector size.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  S3 = SizeAttribute; S3 = ( ( S3 & 0xF0 ) !== 0 ? ( S3 >>= 4, 4 ) : 0 ) | ( ( S3 & 0xC ) !== 0 ? ( S3 >>= 2, 2 ) : 0 ) | ( ( S3 >>= 1 ) !== 0 );\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  Convert the Bit Position of S3 into it's value and remove it by subtracting it if it is not 0.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  if( S3 !== 0 ) { SizeAttribute -= ( 1 << S3 ); }\n\n  /*----------------------------------------------------------------------------------------------------------------------------------------\n  If it is 0 The second size attribute is set as the operand size. So S3 is aliased to S2, if there is no other Size setting attributes.\n  ----------------------------------------------------------------------------------------------------------------------------------------*/\n\n  else { S3 = S2; if( S2 !== 2 ) { S2 = S1; } };\n\n  //In 32/16 bit mode the operand size must never exceed 32.\n\n  if ( BitMode <= 1 && S2 >= 3 && !Vect )\n  {\n    if( ( S1 | S2 | S3 ) === S3 ){ S1 = 2; S3 = 2; } //If single size all adjust 32.\n    S2 = 2; //Default operand size 32.\n  }\n\n  //In 16 bit mode The operand override is always active until used. This makes all operands 16 bit size.\n  //When Operand override is used it is the default 32 size. Flip S3 with S2.\n\n  if( BitMode === 0 && !Vect ) { var t = S3; S3 = S2; S2 = t; }\n\n  //If an Vect is active, then EVEX.W, VEX.W, or XOP.W bit acts as 32/64.\n\n  if( ( Vect || Extension > 0 ) && ( ( S1 + S2 + S3 ) === 7 | ( S1 + S2 + S3 ) === 5 ) ) { Vect = false; return( ( [ S2, S1 ] )[ WidthBit & 1 ] ); }\n\n  //If it is an vector, and Bround is active vector goes max size.\n\n  if( Vect && ConversionMode === 1 )\n  {\n    S0 = S1; S3 = S1; S2 = S1;\n  }\n\n  //Note the fourth size that is -1 in the returned size attribute is Vector length 11=3 which is invalid unless Intel decides to add 1024 bit vectors.\n  //The only time S0 is not negative one is if vector broadcast round is active.\n\n  return( ( [ S3, S2, S1, S0 ] )[ SizeAttrSelect ] );\n\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThis function returns an array with three numbers.\n---------------------------------------------------------------------------------------------------------------------------\nThe first element is the two bits for the ModR/M byte for Register mode, Memory mode, and Displacement settings, or the SIB byte\nscale as a number value 0 to 3 if it is not an ModR/M byte since they both use the same bit grouping.\nThe second element is the three bits for the ModR/M byte Opcode/Reg bits, or the SIB Index Register value as a number value 0 to 7.\nThe third element is the last three bits for the ModR/M byte the R/M bits, or the SIB Base Register value as a number value 0 to 7.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction Decode_ModRM_SIB_Value()\n{\n  //Get the current position byte value\n\n  var v = BinCode[CodePos];\n\n  //The first tow binary digits of the read byte is the Mode bits of the ModR/M byte or\n  //The first tow binary digits of the byte is the Scale bits of the SIB byte.\n\n  var ModeScale = (v >> 6) & 0x03; //value 0 to 3\n\n  //The three binary digits of the read byte after the first two digits is the OpcodeReg Value of the ModR/M byte or\n  //The three binary digits of the read byte after the first two digits is the Index Register value for the SIB byte.\n\n  var OpcodeRegIndex = (v >> 3) & 0x07; //value 0 to 7\n\n  //The three binary digits at the end of the read byte is the R/M (Register,or Memory) Value of the ModR/M byte or\n  //The three binary digits at the end of the read byte is the Base Register Value of the SIB byte.\n\n  var RMBase = v & 0x07; //value 0 to 7\n\n  //Put the array together containing the three indexes with the value\n  //Note both the ModR/M byte and SIB byte use the same bit value pattern\n\n  var ByteValueArray = [\n    ModeScale,//Index 0 is the first tow bits for the Mode, or Scale Depending on what the byte value is used for.\n    OpcodeRegIndex,//Index 1 is the three bits for the OpcodeReg, or Index Depending on what the byte value is used for.\n    RMBase //Index 2 is the three bits for the RM, or BASE bits Depending on what the byte value is used for.\n  ];\n\n  //Move the Decoders Position by one.\n  \n  NextByte();\n\n  //return the array containing the decoded values of the byte.\n\n  return (ByteValueArray);\n\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nWhen input type is value 0 decode the immediate input regularly to it's size setting for accumulator Arithmetic, and IO.\nWhen input type is value 1 decode the immediate input regularly, but zeros out the upper 4 bits for Register encoding.\nWhen input type is value 2 decode the immediate as a relative address used by jumps, and function calls.\nWhen input type is value 3 decode the immediate as a Integer Used by Displacements.\n---------------------------------------------------------------------------------------------------------------------------\nThe function argument SizeSetting is the size attributes of the IMM that is decoded using the GetOperandSize function.\nThe Imm uses two size setting, the first 4 bits are used for the Immediate actual adjustable sizes 8,16,32,64.\n---------------------------------------------------------------------------------------------------------------------------\nIf BySize is false the SizeSetting is used numerically as a single size selection as\n0=8,1=16,2=32,3=64 by size setting value.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction DecodeImmediate( type, BySize, SizeSetting )\n{\n\n  /*-------------------------------------------------------------------------------------------------------------------------\n  Initialize V32, and V64 which will store the Immediate value.\n  JavaScript Float64 numbers can not accurately work with numbers 64 bit's long.\n  So numbers are split into two numbers that should never exceed an 32 bit value though calculation.\n  Numbers that are too big for the first 32 bit's are stored as the next 32 bit's in V64.\n  -------------------------------------------------------------------------------------------------------------------------*/\n\n  var V32 = 0, V64 = 0;\n\n  //*Initialize the Pad Size for V32, and V64 depending On the Immediate type Calculation they use.\n\n  var Pad32 = 0, Pad64 = 0;\n\n  //*Initialize the Sign value that is only set for Negative, or Positive Relative displacements.\n\n  var Sign = 0;\n\n  //*Initialize the Sign Extend variable size as 0 Some Immediate numbers Sign extend.\n\n  var Extend = 0;\n\n  //*The variable S is the size of the Immediate.\n\n  var S = SizeSetting & 0x0F;\n\n  //*Extend size.\n\n  Extend = SizeSetting >> 4;\n\n  //*If by Size attributes is set true.\n\n  if ( BySize )\n  {\n    S = GetOperandSize( S );\n\n    if ( Extend > 0 )\n    {\n      Extend = GetOperandSize( Extend );\n    }\n  }\n\n  /*-------------------------------------------------------------------------------------------------------------------------\n  The possible values of S (Calculated Size) are S=0 is IMM8, S=1 is IMM16, S=2 is IMM32, S=3 is IMM64.\n  Calculate how many bytes that are going to have to be read based on the value of S.\n  S=0 is 1 byte, S=1 is 2 bytes, S=2 is 4 bytes, S=3 is 8 bytes.\n  The Number of bytes to read is 2 to the power of S.\n  -------------------------------------------------------------------------------------------------------------------------*/\n\n  var n = 1 << S;\n\n  //Adjust Pad32, and Pad64.\n\n  Pad32 = Math.min( n, 4 ); ( n >= 8 ) && ( Pad64 = 8 );\n\n  //Store the first byte of the immediate because IMM8 can use different encodings.\n\n  IMMValue = BinCode[CodePos];\n\n  //*Loop and Move the Decoder to the next byte Code position to the number of bytes to read for V32, and V64.\n\n  for ( var i = 0, v = 1; i < Pad32; V32 += BinCode[CodePos] * v, i++, v *= 256, NextByte() );\n  for ( v = 1; i < Pad64; V64 += BinCode[CodePos] * v, i++, v *= 256, NextByte() );\n\n  //*Adjust Pad32 so it matches the length the Immediate should be in hex for number of bytes read.\n\n  Pad32 <<= 1; Pad64 <<= 1;\n\n  /*---------------------------------------------------------------------------------------------------------------------------\n  If the IMM type is used with an register operand on the upper four bit's then the IMM byte does not use the upper 4 bit's.\n  ---------------------------------------------------------------------------------------------------------------------------*/\n\n  if( type === 1 ) { V32 &= ( 1 << ( ( n << 3 ) - 4 ) ) - 1; }\n\n  /*---------------------------------------------------------------------------------------------------------------------------\n  If the Immediate is an relative address calculation.\n  ---------------------------------------------------------------------------------------------------------------------------*/\n\n  if ( type === 2 )\n  {\n    //Calculate the Padded size for at the end of the function an Relative is padded to the size of the address based on bit mode.\n\n    Pad32 = ( Math.min( BitMode, 1 ) << 2 ) + 4; Pad64 = Math.max( Math.min( BitMode, 2 ), 1 ) << 3;\n\n    //Carry bit to 64 bit section.\n    \n    var C64 = 0;\n    \n    //Relative size.\n    \n    var n = Math.min( 0x100000000, Math.pow( 2, 4 << ( S + 1 ) ) );\n    \n    //Sign bit adjust.\n    \n    if( V32 >= ( n / 2 ) ) { V32 -= n; }\n    \n    //Add position.\n    \n    V32 += Pos32;\n    \n    //Remove carry bit and add it to C64.\n\n    ( C64 = ( ( V32 ) >= 0x100000000 ) ) && ( V32 -= 0x100000000 );\n    \n    //Do not carry to 64 if address is 32, and below.\n    \n    if ( S <= 2 ) { C64 = false; }\n\n    //Add the 64 bit position plus carry.\n\n    ( ( V64 += Pos64 + C64 ) > 0xFFFFFFFF ) && ( V64 -= 0x100000000 );\n  }\n\n  /*---------------------------------------------------------------------------------------------------------------------------\n  If the Immediate is an displacement calculation.\n  ---------------------------------------------------------------------------------------------------------------------------*/\n\n  if ( type === 3 )\n  {\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Calculate the displacement center point based on Immediate size.\n    -------------------------------------------------------------------------------------------------------------------------*/\n\n    //An displacement can not be bigger than 32 bit's, so Pad64 is set 0.\n\n    Pad64 = 0;\n\n    //Now calculate the Center Point.\n\n    var Center = 2 * ( 1 << ( n << 3 ) - 2 );\n\n    //By default the Sign is Positive.\n\n    Sign = 1;\n\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Calculate the VSIB displacement size if it is a VSIB Disp8.\n    -------------------------------------------------------------------------------------------------------------------------*/\n\n    if ( VSIB && S === 0 )\n    {\n      var VScale = WidthBit | 2;\n      Center <<= VScale; V32 <<= VScale;\n    }\n\n    //When the value is higher than the center it is negative.\n\n    if ( V32 >= Center )\n    {\n      //Convert the number to the negative side of the center point.\n\n      V32 = Center * 2 - V32;\n\n      //The Sign is negative.\n\n      Sign = 2;\n    }\n  }\n\n  /*---------------------------------------------------------------------------------------------------------------------------\n  Pad Imm based on the calculated Immediate size, because when an value is converted to an number as text that can be displayed\n  the 0 digits to the left are removed. Think of this as like the number 000179 the actual length of the number is 6 digits,\n  but is displayed as 179, because the unused digits are not displayed, but they still exist in the memory.\n  ---------------------------------------------------------------------------------------------------------------------------*/\n\n  for( var Imm = V32.toString(16), L = Pad32; Imm.length < L; Imm = \"0\" + Imm );\n  if( Pad64 > 8 ) { for( Imm = V64.toString(16) + Imm, L = Pad64; Imm.length < L; Imm = \"0\" + Imm ); }\n\n  /*---------------------------------------------------------------------------------------------------------------------------\n  Extend Imm if it's extend size is bigger than the Current Imm size.\n  ---------------------------------------------------------------------------------------------------------------------------*/\n\n  if ( Extend !== S )\n  {\n    //Calculate number of bytes to Extend till by size.\n\n    Extend = Math.pow( 2, Extend ) * 2;\n\n    //Setup the Signified pad value.\n\n    var spd = \"00\"; ( ( ( parseInt( Imm.substring(0, 1), 16) & 8 ) >> 3 ) ) && ( spd = \"FF\" );\n\n    //Start padding.\n\n    for (; Imm.length < Extend; Imm = spd + Imm);\n  }\n\n  //*Return the Imm.\n\n  return ( ( Sign > 0 ? ( Sign > 1 ? \"-\" : \"+\" ) : \"\" ) + Imm.toUpperCase() );\n\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nDecode registers by Size attributes, or a select register index.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction DecodeRegValue( RValue, BySize, Setting )\n{\n  //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size.\n\n  if( Vect && Extension === 0 )\n  {\n    SizeAttrSelect = 0;\n  }\n\n  //If By size is true Use the Setting with the GetOperandSize\n\n  if ( BySize )\n  {\n    Setting = GetOperandSize( Setting ); //get decoded size value.\n\n    //Any Vector register smaller than 128 has to XMM because XMM is the smallest SIMD Vector register.\n\n    if( Vect && Setting < 4 ) { Setting = 4; }\n  }\n\n  //If XOP only vector 0 to 15 are usable.\n      \n  if( Opcode >= 0x400 ) { RValue &= 15; }\n\n  //Else If 16/32 bit mode in VEX/EVEX/MVEX vctor register can only go 0 though 7.\n\n  else if( BitMode <= 1 && Extension >= 1 ) { RValue &= 7; }\n\n  //If L1OM ZMM to V reg.\n\n  if ( Opcode >= 0x700 && Setting === 6 )\n  {\n    Setting = 16;\n  }\n\n  //Else if 8 bit high/low Registers\n\n  else if ( Setting === 0 )\n  {\n    return (REG[0][RexActive][ RValue ]);\n  }\n\n  //Return the Register.\n\n  return (REG[Setting][ RValue ]);\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nDecode the ModR/M pointer, and Optional SIB if used.\nNote if by size attributes is false the lower four bits is the selected Memory pointer,\nand the higher four bits is the selected register.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction Decode_ModRM_SIB_Address( ModRM, BySize, Setting )\n{\n  var out = \"\"; //the variable out is what stores the decoded address pointer, or Register if Register mode.\n  var S_C = \"{\"; //L1OM, and K1OM {SSS,CCCCC} setting decoding, or EVEX broadcast round.\n\n  //-------------------------------------------------------------------------------------------------------------------------\n  //If the ModR/M is not in register mode decode it as an Effective address.\n  //-------------------------------------------------------------------------------------------------------------------------\n\n  if( ModRM[0] !== 3 )\n  {\n\n    //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size.\n\n    if( Vect && Extension === 0 )\n    {\n      SizeAttrSelect = 0;\n    }\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    //The Selected Size is setting unless BySize attribute is true.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    if ( BySize )\n    {\n      //-------------------------------------------------------------------------------------------------------------------------\n      //Check if it is not the non vectorized 128 which uses \"Oword ptr\".\n      //-------------------------------------------------------------------------------------------------------------------------\n\n      if ( Setting !== 16 || Vect )\n      {\n        Setting = ( GetOperandSize( Setting ) << 1 ) | FarPointer;\n      }\n\n      //-------------------------------------------------------------------------------------------------------------------------\n      //Non vectorized 128 uses \"Oword ptr\" alaises to \"QWord ptr\" in 32 bit mode, or lower.\n      //-------------------------------------------------------------------------------------------------------------------------\n\n      else if ( !Vect ) { Setting = 11 - ( ( BitMode <= 1 ) * 5 ); }\n    }\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    //If By size attributes is false the selected Memory pointer is the first four bits of the size setting for all pointer indexes 0 to 15.\n    //Also if By size attribute is also true the selected by size index can not exceed 15 anyways which is the max combination the first four bits.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    Setting = Setting & 0x0F;\n\n    //If Vector extended then MM is changed to QWORD.\n\n    if( Extension !== 0 && Setting === 9 ){ Setting = 6; }\n\n    //Bround control, or 32/64 VSIB.\n\n    if ( ConversionMode === 1 || ConversionMode === 2 || VSIB ) { out += PTR[WidthBit > 0 ? 6 : 4]; }\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    //Get the pointer size by Size setting.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    else{ out = PTR[Setting]; }\n\n    //Add the Segment override left address bracket if any segment override was used otherwise the SegOverride string should be just a normal left bracket.\n\n    out += SegOverride;\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    //calculate the actual address size according to the Address override and the CPU bit mode.\n    //-------------------------------------------------------------------------------------------------------------------------\n    //AddressSize 1 is 16, AddressSize 2 is 32, AddressSize 3 is 64.\n    //The Bit mode is the address size except AddressOverride reacts differently in different bit modes.\n    //In 16 bit AddressOverride switches to the 32 bit ModR/M effective address system.\n    //In both 32/64 the Address size goes down by one is size.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    var AddressSize = BitMode + 1;\n\n    if (AddressOverride)\n    {\n      AddressSize = AddressSize - 1;\n\n      //the only time the address size is 0 is if the BitMode is 16 bit's and is subtracted by one resulting in 0.\n\n      if(AddressSize === 0)\n      {\n        AddressSize = 2; //set the address size to 32 bit from the 16 bit address mode.\n      }\n    }\n\n    /*-------------------------------------------------------------------------------------------------------------------------\n    The displacement size calculation.\n    ---------------------------------------------------------------------------------------------------------------------------\n    In 16/32/64 the mode setting 1 will always add a Displacement of 8 to the address.\n    In 16 the Mode setting 2 adds a displacement of 16 to the address.\n    In 32/64 the Mode Setting 2 for the effective address adds an displacement of 32 to the effective address.\n    -------------------------------------------------------------------------------------------------------------------------*/\n\n    var Disp = ModRM[0] - 1; //Let disp relate size to mode value of the ModR/M.\n\n    //if 32 bit and above, and if Mode is 2 then disp size is disp32.\n\n    if(AddressSize >= 2 && ModRM[0] === 2)\n    {\n      Disp += 1; //Only one more higher in size is 32.\n    }\n\n    /*-------------------------------------------------------------------------------------------------------------------------\n    End of calculation.\n    -------------------------------------------------------------------------------------------------------------------------*/\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Normally the displacement type is an relative Immediate that is added (\"+\"),\n    or subtracted (\"-\") from the center point to the selected base register,\n    and the size depends on mode settings 1, and 2, and also Address bit mode (Displacement calculation).\n    Because the normal ModR/M format was limited to Relative addresses, and unfixed locations,\n    so some modes, and registers combinations where used for different Immediate displacements.\n    -------------------------------------------------------------------------------------------------------------------------*/\n\n    var DispType = 3; //by default the displacement size is added to the selected base register, or Index register if SIB byte combination is used.\n\n    //-------------------------------------------16 Bit ModR/M address decode logic-------------------------------------------\n\n    if( AddressSize === 1 )\n    {\n\n      //if ModR/M mode bits 0, and Base Register value is 6 then disp16 with DispType mode 0.\n\n      if(AddressSize === 1 && ModRM[0] === 0 && ModRM[2] === 6)\n      {\n        Disp = 1;\n        DispType = 0;\n      }\n\n      //BX , BP switch based on bit 2 of the Register value\n\n      if( ModRM[2] < 4 ){ out += REG[ AddressSize ][ 3 + ( ModRM[2] & 2 ) ] + \"+\"; }\n\n      //The first bit switches between Destination index, and source index\n\n      if( ModRM[2] < 6 ){ out += REG[ AddressSize ][ 6 + ( ModRM[2] & 1 ) ]; }\n\n      //[BP], and [BX] as long as Mode is not 0, and Register is not 6 which sets DispType 0.\n\n      else if ( DispType !== 0 ) { out += REG[ AddressSize ][ 17 - ( ModRM[2] << 1 ) ]; }\n    } //End of 16 bit ModR/M decode logic.\n\n    //-------------------------------------------Else 32/64 ModR/M-------------------------------------------\n\n    else\n    {\n\n      //if Mode is 0 and Base Register value is 5 then it uses an Relative (RIP) disp32.\n\n      if( ModRM[0] === 0 && ModRM[2] === 5 )\n      {\n        Disp = 2;\n        DispType = 2;\n      }\n\n      //check if Base Register is 4 which goes into the SIB address system\n\n      if( ModRM[2] === 4 )\n      {\n        //Decode the SIB byte.\n\n        var SIB = Decode_ModRM_SIB_Value();\n\n        //Calculate the Index register with it's Extended value because the index register will only cancel out if 4 in value.\n\n        var IndexReg = IndexExtend | SIB[1];\n\n        //check if the base register is 5 in value in the SIB without it's added extended value, and that the ModR/M Mode is 0 this activates Disp32\n\n        if ( ModRM[0] === 0 && SIB[2] === 5 && !VSIB )\n        {\n          Disp = 2; //Set Disp32\n\n          //check if the Index register is canceled out as well\n\n          if (IndexReg === 4) //if the Index is canceled out then\n          {\n            DispType = 0; //a regular IMM32 is used as the address.\n\n            //*if the Address size is 64 then the 32 bit Immediate must pad to the full 64 bit address.\n\n            if( AddressSize === 3 ) { Disp = 50; }\n          }\n        }\n\n        //Else Base register is not 5, and the Mode is not 0 then decode the base register normally.\n\n        else\n        {\n          out += REG[ AddressSize ][ BaseExtend & 8 | SIB[2] ];\n\n          //If the Index Register is not Canceled out (Note this is only reachable if base register was decoded and not canceled out)\n\n          if ( IndexReg !== 4 || VSIB )\n          {\n            out = out + \"+\"; //Then add the Plus in front of the Base register to add the index register\n          }\n        }\n\n        //if Index Register is not Canceled, and that it is not an Vector register then decode the Index with the possibility of the base register.\n\n        if ( IndexReg !== 4 && !VSIB )\n        {\n          out += REG[ AddressSize ][ IndexExtend | IndexReg ];\n\n          //add what the scale bits decode to the Index register by the value of the scale bits which select the name from the scale array.\n\n          out = out + scale[SIB[0]];\n        }\n        \n        //Else if it is an vector register.\n        \n        else if ( VSIB )\n        {\n          Setting = ( Setting < 8 ) ? 4 : Setting >> 1;\n\n          if( Opcode < 0x700 ) { IndexReg |= ( VectorRegister & 0x10 ); }\n\n          out += DecodeRegValue( IndexExtend | IndexReg, false, Setting ); //Decode Vector register by length setting and the V' extension.\n\n          //add what the scale bits decode to the Index register by the value of the scale bits which select the name from the scale array.\n\n          out = out + scale[SIB[0]];\n        }\n      } //END OF THE SIB BYTE ADDRESS DECODE.\n\n      //else Base register is not 4 and does not go into the SIB ADDRESS.\n      //Decode the Base register regularly plus it's Extended value if relative (RIP) disp32 is not used.\n\n      else if(DispType !== 2)\n      {\n        out += REG[ AddressSize ][ BaseExtend & 8 | ModRM[2] ];\n      }\n    }\n\n\n    //Finally the Immediate displacement is put into the Address last.\n\n    if( Disp >= 0 ) { out += DecodeImmediate( DispType, false, Disp ); }\n\n    //Put the right bracket on the address.\n\n    out += \"]\";\n\n    //----------------------L1OM/MVEX/EVEX memory conversion mode, or broadcast round-----------------------\n\n    if(\n        ( ConversionMode !== 0 ) && //Not used if set 0.\n       !(\n          ( ConversionMode === 3 && ( Opcode >= 0x700 || !( Opcode >= 0x700 ) && !Float ) ) || //If bad L1OM/K1OM float conversion.\n          ( !( Opcode >= 0x700 ) && ( VectS === 0 || ( ConversionMode === 5 && VectS === 5 ) || //If K1OM UNorm conversion L1OM only.\n          ( ConversionMode !== 1 && VectS === 1 ) ^ ( ConversionMode < 3 && !Swizzle ) ) ) //Or K1OM broadcast Swizzle, and special case {4to16} only.\n        )\n    )\n    {\n      //Calculate Conversion.\n\n      if( ConversionMode >= 4 ){ ConversionMode += 2; }\n      if( ConversionMode >= 8 ){ ConversionMode += 2; }\n\n      //If L1OM.\n\n      if( Opcode >= 0x700 )\n      {\n        //If L1OM without Swizzle.\n\n        if ( !Swizzle && ConversionMode > 2 ) { ConversionMode = 31; }\n\n        //L1OM Float adjust.\n\n        else if( Float )\n        {\n          if( ConversionMode === 7 ) { ConversionMode++; }\n          if( ConversionMode === 10 ) { ConversionMode = 3; }\n        }\n      }\n\n      //Set conversion. Note K1OM special case inverts width bit.\n\n      out += S_C + ConversionModes[ ( ConversionMode << 1 ) | ( WidthBit ^ ( !( Opcode >= 0x700 ) & VectS === 7 ) ) & 1 ]; S_C = \",\";\n    }\n\n    //Else bad Conversion setting.\n\n    else if( ConversionMode !== 0 ) { out += S_C + \"Error\"; S_C = \",\"; }\n    \n    //--------------------------------END of memory Conversion control logic--------------------------------\n\n  } //End of Memory address Modes 00, 01, 10 decode.\n\n  //-----------------------------else the ModR/M mode bits are 11 register Mode-----------------------------\n\n  else\n  {\n    //-------------------------------------------------------------------------------------------------------------------------\n    //The Selected Size is setting unless BySize attribute is true.\n    //-------------------------------------------------------------------------------------------------------------------------\n    \n    //MVEX/EVEX round mode.\n \n    if ( ( Extension === 3 && HInt_ZeroMerg ) || ( Extension === 2 && ConversionMode === 1 ) )\n    {\n      RoundMode |= RoundingSetting;\n    }\n\n    //If the upper 4 bits are defined and by size is false then the upper four bits is the selected register.\n\n    if( ( ( Setting & 0xF0 ) > 0 ) && !BySize ) { Setting >>= 4; }\n\n    //Decode the register with Base expansion.\n\n    out = DecodeRegValue( BaseExtend | ModRM[2], BySize, Setting );\n    \n    //L1OM/K1OM Register swizzle modes.\n    \n    if( Opcode >= 0x700 || ( Extension === 3 && !HInt_ZeroMerg && Swizzle ) )\n    {\n      if( Opcode >= 0x700 && ConversionMode >= 3 ){ ConversionMode++; } //L1OM skips swizzle type DACB.\n      if( ConversionMode !== 0 ){ out += S_C + RegSwizzleModes[ ConversionMode ]; S_C = \",\"; }\n    }\n    if( Extension !== 2 ){ HInt_ZeroMerg = false; } //Cache memory control is not possible in Register mode.\n  }\n\n  //--------------------------------------------------L1OM.CCCCC conversions-------------------------------------------------\n\n  if( Opcode >= 0x700 )\n  {\n    //Swizzle Field control Int/Float, or Exponent adjustment.\n\n    if(Swizzle)\n    {\n      if( Opcode === 0x79A ) { out += S_C + ConversionModes[ ( 18 | ( VectorRegister & 3 ) ) << 1 ]; S_C = \"}\"; }\n      else if( Opcode === 0x79B ) { out += S_C + ConversionModes[ ( 22 + ( VectorRegister & 3 ) ) << 1 ]; S_C = \"}\"; }\n      else if( ( RoundingSetting & 8 ) === 8 ) { out += S_C + RoundModes [ 24 | ( VectorRegister & 7 ) ]; S_C = \"}\"; }\n    }\n\n    //Up/Down Conversion.\n\n    else if( VectorRegister !== 0 )\n    {\n      if( ( ( Up && VectorRegister !== 2 ) || //Up conversion \"float16rz\" is bad.\n        ( !Up && VectorRegister !== 3 && VectorRegister <= 15 ) ) //Down conversion \"srgb8\", and field control is bad.\n      ) { out += S_C + ConversionModes[ ( ( VectorRegister + 2 ) << 1 ) | WidthBit ]; S_C = \"}\"; }\n      else { out += S_C + \"Error\"; S_C = \"}\"; } //Else Invalid setting.\n    }\n  }\n  if ( S_C === \",\" ) { S_C = \"}\"; }\n\n  //Right bracket if any SSS,CCCCC conversion mode setting.\n\n  if( S_C === \"}\" ) { out += S_C; }\n\n  //------------------------------------------L1OM/K1OM Hint cache memory control--------------------------------------------\n\n  if( HInt_ZeroMerg )\n  {\n    if ( Extension === 3 ) { out += \"{EH}\"; }\n    else if ( Opcode >= 0x700 ) { out += \"{NT}\"; }\n  }\n\n  //-------------------------------------------Return the Register/Memory address--------------------------------------------\n\n  return (out);\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nDecode Prefix Mnemonic codes. Prefixes are instruction codes that do not do an operation instead adjust\ncontrols in the CPU to be applied to an select instruction code that is not an Prefix instruction.\n---------------------------------------------------------------------------------------------------------------------------\nAt the end of this function \"Opcode\" should not hold any prefix code, so then Opcode contains an operation code.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction DecodePrefixAdjustments()\n{\n  //-------------------------------------------------------------------------------------------------------------------------\n  Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //Add 8 bit opcode while bits 9, and 10 are used for opcode map.\n  NextByte(); //Move to the next byte.\n  //-------------------------------------------------------------------------------------------------------------------------\n\n  //if 0F hex start at 256 for Opcode.\n\n  if(Opcode === 0x0F)\n  {\n    Opcode = 0x100; //By starting at 0x100 with binary bit 9 set one then adding the 8 bit opcode then Opcode goes 256 to 511.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //if 38 hex while using two byte opcode.\n\n  else if(Opcode === 0x138 && Mnemonics[0x138] === \"\")\n  {\n    Opcode = 0x200; //By starting at 0x200 with binary bit 10 set one then adding the 8 bit opcode then Opcode goes 512 to 767.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //if 3A hex while using two byte opcode go three byte opcodes.\n\n  else if(Opcode === 0x13A && Mnemonics[0x13A] === \"\")\n  {\n    Opcode = 0x300; //By starting at 0x300 hex and adding the 8 bit opcode then Opcode goes 768 to 1023.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //Rex prefix decodes only in 64 bit mode.\n\n  if( Opcode >= 0x40 & Opcode <= 0x4F && BitMode === 2 )\n  {\n    RexActive = 1; //Set Rex active uses 8 bit registers in lower order as 0 to 15.\n    BaseExtend = ( Opcode & 0x01 ) << 3; //Base Register extend setting.\n    IndexExtend = ( Opcode & 0x02 ) << 2; //Index Register extend setting.\n    RegExtend = ( Opcode & 0x04 ) << 1; //Register Extend Setting.\n    WidthBit = ( Opcode & 0x08 ) >> 3; //Set The Width Bit setting if active.\n    SizeAttrSelect = WidthBit ? 2 : 1; //The width Bit open all 64 bits.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //The VEX2 Operation code Extension to SSE settings decoding.\n\n  if( Opcode === 0xC5 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) )\n  {\n    Extension = 1;\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = BinCode[CodePos]; //read VEX2 byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    //some bits are inverted, so uninvert them arithmetically.\n\n    Opcode ^= 0xF8;\n\n    //Decode bit settings.\n\n    if( BitMode === 2 )\n    {\n      RegExtend = ( Opcode & 0x80 ) >> 4; //Register Extend.\n      VectorRegister = ( Opcode & 0x78 ) >> 3; //The added in Vector register to SSE.\n    }\n\n    SizeAttrSelect = ( Opcode & 0x04 ) >> 2; //The L bit for 256 vector size.\n    SIMD = Opcode & 0x03; //The SIMD mode.\n\n    //Automatically uses the two byte opcode map starts at 256 goes to 511.\n\n    Opcode = 0x100;\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the opcode.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    //Stop decoding prefixes.\n\n    return(null);\n  }\n\n  //The VEX3 prefix settings decoding.\n\n  if( Opcode === 0xC4 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) )\n  {\n    Extension = 1;\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = BinCode[CodePos]; //read VEX3 byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode |= ( BinCode[CodePos] << 8 ); //Read next VEX3 byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    //Some bits are inverted, so uninvert them arithmetically.\n\n    Opcode ^= 0x78E0;\n\n    //Decode bit settings.\n\n    if( BitMode === 2 )\n    {\n      RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting.\n      IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting.\n      BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting.\n    }\n\n    WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator.\n    VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE.\n    SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting.\n    SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode.\n    Opcode = ( Opcode & 0x001F ) << 8; //Change Operation code map.\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    return(null);\n  }\n\n  //The AMD XOP prefix.\n\n  if( Opcode === 0x8F )\n  {\n    //If XOP\n\n    var Code = BinCode[ CodePos ] & 0x0F;\n\n    if( Code >= 8 && Code <= 10 )\n    {\n      Extension = 1;\n      //-------------------------------------------------------------------------------------------------------------------------\n      Opcode = BinCode[CodePos]; //read XOP byte settings.\n      NextByte(); //Move to the next byte.\n      //-------------------------------------------------------------------------------------------------------------------------\n      Opcode |= ( BinCode[CodePos] << 8 ); //Read next XOP byte settings.\n      NextByte(); //Move to the next byte.\n      //-------------------------------------------------------------------------------------------------------------------------\n\n      //Some bits are inverted, so uninvert them arithmetically.\n\n      Opcode ^= 0x78E0;\n\n      //Decode bit settings.\n\n      RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting.\n      IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting.\n      BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting.\n      WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator.\n      VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE.\n      SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting.\n      SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode.\n      if( SIMD > 0 ) { InvalidOp = true; } //If SIMD MODE is set anything other than 0 the instruction is invalid.\n      Opcode = 0x400 | ( ( Opcode & 0x0003 ) << 8 ); //Change Operation code map.\n\n      //-------------------------------------------------------------------------------------------------------------------------\n      Opcode = ( Opcode & 0x700 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's.\n      NextByte(); //Move to the next byte.\n      //-------------------------------------------------------------------------------------------------------------------------\n\n      return(null);\n    }\n  }\n  \n  //The L1OM vector prefix settings decoding.\n\n  if( Opcode === 0xD6 )\n  {\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = BinCode[CodePos]; //read L1OM byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode |= ( BinCode[CodePos] << 8 ); //Read next L1OM byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    WidthBit = SIMD & 1;\n    VectorRegister = ( Opcode & 0xF800 ) >> 11;\n    RoundMode = VectorRegister >> 3;\n    MaskRegister = ( Opcode & 0x0700 ) >> 8;\n    HInt_ZeroMerg = ( Opcode & 0x0080 ) >> 7;\n    ConversionMode = ( Opcode & 0x0070 ) >> 4;\n    RegExtend = ( Opcode & 0x000C ) << 1;\n    BaseExtend = ( Opcode & 0x0003 ) << 3;\n    IndexExtend = ( Opcode & 0x0002 ) << 2;\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = 0x700 | BinCode[CodePos]; //read the 8 bit opcode.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    return(null);\n  }\n\n  //Only decode L1OM instead of MVEX/EVEX if L1OM compatibility mode is set.\n\n  if( Mnemonics[0x62] === \"\" && Opcode === 0x62 )\n  {\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = BinCode[CodePos]; //read L1OM byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    Opcode ^= 0xF0;\n\n    IndexExtend = ( Opcode & 0x80 ) >> 4;\n    BaseExtend = ( Opcode & 0x40 ) >> 3;\n    RegExtend = ( Opcode & 0x20 ) >> 2;\n\n    if ( SIMD !== 1 ) { SizeAttrSelect = ( ( Opcode & 0x10 ) === 0x10 ) ? 2 : 1; } else { SIMD = 0; }\n\n    Opcode = 0x800 | ( ( Opcode & 0x30 ) >> 4 ) | ( ( Opcode & 0x0F ) << 2 );\n\n    return(null);\n  }\n\n  //The MVEX/EVEX prefix settings decoding.\n\n  if ( Opcode === 0x62 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) )\n  {\n    Extension = 2;\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = BinCode[CodePos]; //read MVEX/EVEX byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode |= ( BinCode[CodePos] << 8 ); //read next MVEX/EVEX byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode |= ( BinCode[CodePos] << 16 ); //read next MVEX/EVEX byte settings.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    //Some bits are inverted, so uninvert them arithmetically.\n\n    Opcode ^= 0x0878F0;\n\n    //Check if Reserved bits are 0 if they are not 0 the MVEX/EVEX instruction is invalid.\n\n    InvalidOp = ( Opcode & 0x00000C ) > 0;\n\n    //Decode bit settings.\n    \n    if( BitMode === 2 )\n    {\n      RegExtend = ( ( Opcode & 0x80 ) >> 4 ) | ( Opcode & 0x10 ); //The Double R'R bit decode for Register Extension 0 to 32.\n      BaseExtend = ( Opcode & 0x60 ) >> 2; //The X bit, and B Bit base register extend combination 0 to 32.\n      IndexExtend = ( Opcode & 0x40 ) >> 3; //The X extends the SIB Index by 8.\n    }\n    \n    VectorRegister = ( ( Opcode & 0x7800 ) >> 11 ) | ( ( Opcode & 0x080000 ) >> 15 ); //The Added in Vector Register for SSE under MVEX/EVEX.\n    \n    WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit separator for MVEX/EVEX.\n    SIMD = ( Opcode & 0x0300 ) >> 8; //decode the SIMD mode setting.\n    HInt_ZeroMerg = ( Opcode & 0x800000 ) >> 23; //Zero Merge to destination control, or MVEX EH control.\n      \n    //EVEX option bits take the place of Vector length control.\n      \n    if ( ( Opcode & 0x0400 ) > 0 )\n    {\n      SizeAttrSelect = ( Opcode & 0x600000 ) >> 21; //The EVEX.L'L Size combination.\n      RoundMode = SizeAttrSelect | 4; //Rounding mode is Vector length if used.\n      ConversionMode = (Opcode & 0x100000 ) >> 20; //Broadcast Round Memory address system.\n    }\n      \n    //MVEX Vector Length, and Broadcast round.\n      \n    else\n    {\n      SizeAttrSelect = 2; //Max Size by default.\n      ConversionMode = ( Opcode & 0x700000 ) >> 20; //\"MVEX.sss\" Option bits.\n      RoundMode = ConversionMode; //Rounding mode selection is ConversionMode if used.\n      Extension = 3;\n    }\n\n    MaskRegister = ( Opcode & 0x070000 ) >> 16; //Mask to destination.\n    Opcode = ( Opcode & 0x03 ) << 8; //Change Operation code map.\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map extend bit's.\n    NextByte(); //Move to the next byte.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    //Stop decoding prefixes.\n\n    return(null);\n  }\n\n  //Segment overrides\n\n  if ( ( Opcode & 0x7E7 ) === 0x26 || ( Opcode & 0x7FE ) === 0x64 )\n  {\n    SegOverride = Mnemonics[ Opcode ]; //Set the Left Bracket for the ModR/M memory address.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //Operand override Prefix\n\n  if(Opcode === 0x66)\n  {\n    SIMD = 1; //sets SIMD mode 1 in case of SSE instruction opcode.\n    SizeAttrSelect = 0; //Adjust the size attribute setting for the size adjustment to the next instruction.\n    return(DecodePrefixAdjustments());  //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //Ram address size override.\n\n  if(Opcode === 0x67)\n  {\n    AddressOverride = true; //Set the setting active for the ModR/M address size.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //if repeat Prefixes F2 hex REP,or F3 hex RENP\n\n  if (Opcode === 0xF2 || Opcode === 0xF3)\n  {\n    SIMD = (Opcode & 0x02 )  |  ( 1 - Opcode & 0x01 ); //F2, and F3 change the SIMD mode during SSE instructions.\n    PrefixG1 = Mnemonics[ Opcode ]; //set the Prefix string.\n    HLEFlipG1G2 = true; //set Filp HLE in case this is the last prefix read, and LOCK was set in string G2 first for HLE.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //if the lock prefix note the lock prefix is separate\n\n  if (Opcode === 0xF0)\n  {\n    PrefixG2 = Mnemonics[ Opcode ]; //set the Prefix string\n    HLEFlipG1G2 = false; //set Flip HLE false in case this is the last prefix read, and REP, or REPNE was set in string G2 first for HLE.\n    return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction.\n  }\n\n  //Before ending the function \"DecodePrefixAdjustments()\" some opcode combinations are invalid in 64 bit mode.\n\n  if ( BitMode === 2 )\n  {\n    InvalidOp |= ( ( ( Opcode & 0x07 ) >= 0x06 ) & ( Opcode <= 0x40 ) );\n    InvalidOp |= ( Opcode === 0x60 | Opcode === 0x61 );\n    InvalidOp |= ( Opcode === 0xD4 | Opcode === 0xD5 );\n    InvalidOp |= ( Opcode === 0x9A | Opcode === 0xEA );\n    InvalidOp |= ( Opcode === 0x82 );\n  }\n\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe Decode opcode function gives back the operation name, and what it uses for input.\nThe input types are for example which registers it uses with the ModR/M, or which Immediate type is used.\nThe input types are stored into an operand string. This function gives back the instruction name, And what the operands use.\n---------------------------------------------------------------------------------------------------------------------------\nThis function is designed to be used after the Decode prefix adjustments function because the Opcode should contain an real instruction code.\nThis is because the Decode prefix adjustments function will only end if the Opcode value is not a prefix adjustment code to the ModR/M etc.\nHowever DecodePrefixAdjustments can also prevent this function from being called next if the prefix settings are bad or an invalid instruction is\nused for the bit mode the CPU is in as it will set InvalidOp true.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction DecodeOpcode()\n{\n  //get the Operation name by the operations opcode.\n\n  Instruction = Mnemonics[Opcode];\n\n  //get the Operands for this opcode it follows the same array structure as Mnemonics array\n\n  InsOperands = Operands[Opcode];\n\n  //Some Opcodes use the next byte automatically for extended opcode selection. Or current SIMD mode.\n\n  var ModRMByte = BinCode[CodePos]; //Read the byte but do not move to the next byte.\n\n  //If the current Mnemonic is an array two in size then Register Mode, and memory mode are separate from each other.\n  //Used in combination with Grouped opcodes, and Static opcodes.\n\n  if(Instruction instanceof Array && Instruction.length == 2) { var bits = ( ModRMByte >> 6 ) & ( ModRMByte >> 7 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; }\n\n  //Arithmetic unit 8x8 combinational logic array combinations.\n  //If the current Mnemonic is an array 8 in length It is a group opcode instruction may repeat previous instructions in different forums.\n\n  if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x38 ) >> 3; Instruction = Instruction[bits]; InsOperands = InsOperands[bits];\n\n    //if The select Group opcode is another array 8 in size it is a static opcode selection which makes the last three bits of the ModR/M byte combination.\n\n    if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x07 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; NextByte(); } }\n\n  //Vector unit 4x4 combinational array logic.\n  //if the current Mnemonic is an array 4 in size it is an SIMD instruction with four possible modes N/A, 66, F3, F2.\n  //The mode is set to SIMD, it could have been set by the EVEX.pp, VEX.pp bit combination, or by prefixes N/A, 66, F3, F2.\n\n  if(Instruction instanceof Array && Instruction.length == 4)\n  {\n    Vect = true; //Set Vector Encoding true.\n\n    //Reset the prefix string G1 because prefix codes F2, and F3 are used with SSE which forum the repeat prefix.\n    //Some SSE instructions can use the REP, RENP prefixes.\n    //The Vectors that do support the repeat prefix uses Packed Single format.\n\n    if(Instruction[2] !== \"\" && Instruction[3] !== \"\") { PrefixG1 = \"\"; } else { SIMD = ( SIMD === 1 ) & 1; }\n    Instruction = Instruction[SIMD]; InsOperands = InsOperands[SIMD];\n\n    //If the SIMD instruction uses another array 4 in length in the Selected SIMD vector Instruction.\n    //Then each vector Extension is separate. The first extension is used if no extension is active for Regular instructions, and vector instruction septation.\n    //0=None. 1=VEX only. 2=EVEX only. 3=??? unused.\n\n    if(Instruction instanceof Array && Instruction.length == 4)\n    {\n      //Get the correct Instruction for the Active Extension type.\n\n      if(Instruction[Extension] !== \"\") { Instruction = Instruction[Extension]; InsOperands = InsOperands[Extension]; }\n      else{ Instruction = \"???\"; InsOperands = \"\"; }\n    }\n    else if( Extension === 3 ){ Instruction = \"???\"; InsOperands = \"\"; }\n  }\n  else if( Opcode >= 0x700 && SIMD > 0 ){ Instruction = \"???\"; InsOperands = \"\"; }\n\n  //if Any Mnemonic is an array 3 in size the instruction name goes by size.\n\n  if(Instruction instanceof Array && Instruction.length == 3)\n  {\n    var bits = ( Extension === 0 & BitMode !== 0 ) ^ ( SizeAttrSelect >= 1 ); //The first bit in SizeAttrSelect for size 32/16 Flips if 16 bit mode.\n    ( WidthBit ) && ( bits = 2 ); //Goes 64 using the Width bit.\n    ( Extension === 3 && HInt_ZeroMerg && Instruction[1] !== \"\" ) && ( HInt_ZeroMerg = false, bits = 1 ); //MVEX uses element 1 if MVEX.E is set for instruction that change name.\n\n    if (Instruction[bits] !== \"\") { Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; }\n\n    //else no size prefix name then use the default size Mnemonic name.\n\n    else { Instruction = Instruction[0]; InsOperands = InsOperands[0]; }\n  }\n\n  //If Extension is not 0 then add the vector extend \"V\" to the instruction.\n  //During the decoding of the operands the instruction can be returned as invalid if it is an Arithmetic, or MM, ST instruction.\n  //Vector mask instructions start with K instead of V any instruction that starts with K and is an\n  //vector mask Instruction which starts with K instead of V.\n\n  if( Opcode <= 0x400 && Extension > 0 && Instruction.charAt(0) !== \"K\" && Instruction !== \"???\" ) { Instruction = \"V\" + Instruction; }\n\n  //In 32 bit mode, or bellow only one instruction MOVSXD is replaced with ARPL.\n\n  if( BitMode <= 1 && Instruction === \"MOVSXD\" ) { Instruction = \"ARPL\"; InsOperands = \"06020A01\"; }\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nRead each operand in the Operand String then set the correct operand in the X86 decoder array.\nOpNum is the order the operands are read in the operand string. The Operand type is which operand will be set\nactive along the X86Decoder. The OpNum is the order the decoded operands will be positioned after they are decoded\nin order along the X86 decoder. The order the operands display is different than the order they decode in sequence.\n---------------------------------------------------------------------------------------------------------------------------\nThis function is used after the function ^DecodeOpcode()^ because the function ^DecodeOpcode()^ gives back the\noperand string for what the instruction takes as input.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction DecodeOperandString()\n{\n  //Variables that are used for decoding one operands across the operand string.\n\n  var OperandValue = 0, Code = 0, BySize = 0, Setting = 0;\n\n  //It does not matter which order the explicit operands decode as they do not require reading another byte.\n  //They start at 7 and are set in order, but the order they are displayed is the order they are read in the operand string because of OpNum.\n\n  var ExplicitOp = 8, ImmOp = 3;\n\n  //Each operand is 4 hex digits, and OpNum is added by one for each operand that is read per Iteration.\n\n  for( var i = 0, OpNum = 0; i < InsOperands.length; i+=4 ) //Iterate though operand string.\n  {\n    OperandValue = parseInt( InsOperands.substring(i, (i + 4) ), 16 ); //Convert the four hex digits to a 16 bit number value.\n    Code = ( OperandValue & 0xFE00 ) >> 9; //Get the operand Code.\n    BySize = ( OperandValue & 0x0100 ) >> 8; //Get it's by size attributes setting for if Setting is used as size attributes.\n    Setting = ( OperandValue & 0x00FF ); //Get the 8 bit Size setting.\n\n    //If code is 0 the next 8 bit value specifies which type of of prefix settings are active.\n\n    if( Code === 0 )\n    {\n      if(BySize) //Vector adjustment settings.\n      {\n        RoundingSetting = ( Setting & 0x03 ) << 3;\n        if( Opcode >= 0x700 && RoundingSetting >= 0x10 ){ RoundMode |= 0x10; }\n        VSIB = ( Setting >> 2 ) & 1;\n        IgnoresWidthbit = ( Setting >> 3 ) & 1;\n        VectS = ( Setting >> 4 ) & 7;\n        Swizzle = ( VectS >> 2 ) & 1;\n        Up = ( VectS >> 1 ) & 1;\n        Float = VectS & 1;\n        if( ( Setting & 0x80 ) == 0x80 ) { Vect = false; } //If Non vector instruction set Vect false.\n      }\n      else //Instruction Prefix types.\n      {\n        XRelease = Setting & 0x01;\n        XAcquire = ( Setting & 0x02 ) >> 1;\n        HT = ( Setting & 0x04 ) >> 2;\n        BND = ( Setting & 0x08 ) >> 3;\n      }\n    }\n\n    //if it is a opcode Reg Encoding then first element along the decoder is set as this has to be decode first, before moving to the\n    //byte for modR/M.\n\n    else if( Code === 1 )\n    {\n      X86Decoder[0].set( 0, BySize, Setting, OpNum++ );\n    }\n\n    //if it is a ModR/M, or Far pointer ModR/M, or Moffs address then second decoder element is set.\n\n    else if( Code >= 2 && Code <= 4 )\n    {\n      X86Decoder[1].set( ( Code - 2 ), BySize, Setting, OpNum++ );\n      if( Code == 4 ){ FarPointer = 1; } //If code is 4 it is a far pointer.\n    }\n\n    //The ModR/M Reg bit's are separated from the address system above. The ModR/M register can be used as a different register with a\n    //different address pointer. The Reg bits of the ModR/M decode next as it would be inefficient to read the register value if the\n    //decoder moves to the immediate.\n\n    else if( Code === 5 )\n    {\n      X86Decoder[2].set( 0, BySize, Setting, OpNum++ );\n    }\n\n    //Immediate input one. The immediate input is just a number input it is decoded last unless the instruction does not use a\n    //ModR/M encoding, or Reg Opcode.\n\n    else if( Code >= 6 && Code <= 8 && ImmOp <= 5 )\n    {\n      X86Decoder[ImmOp++].set( ( Code - 6 ), BySize, Setting, OpNum++ );\n    }\n\n    //Vector register. If the instruction uses this register it will not be decoded or displayed unless one of the vector extension codes are\n    //decoded by the function ^DecodePrefixAdjustments()^. The Vector extension codes also have a Vector register value that is stored into\n    //the variable VectorRegister. The variable VectorRegister is given to the function ^DecodeRegValue()^.\n\n    else if( Code === 9 && ( Extension > 0 || Opcode >= 0x700 ) )\n    {\n      X86Decoder[6].set( 0, BySize, Setting, OpNum++ );\n    }\n\n    //The upper four bits of the Immediate is used as an register. The variable IMM stores the last immediate byte that is read by ^DecodeImmediate()^.\n    //The upper four bits of the IMM is given to the function ^DecodeRegValue()^.\n\n    else if( Code === 10 )\n    {\n      X86Decoder[7].set( 0, BySize, Setting, OpNum++ );\n    }\n\n    //Else any other encoding type higher than 13 is an explicit operand selection.\n    //And also there can only be an max of four explicit operands.\n\n    else if( Code >= 11 && ExplicitOp <= 11)\n    {\n      X86Decoder[ExplicitOp].set( ( Code - 11 ), BySize, Setting, OpNum++ );\n      ExplicitOp++; //move to the next Explicit operand.\n    }\n  }\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nDecode each of the operands along the X86Decoder and deactivate them.\nThis function is used after ^DecodeOperandString()^ which sets up the X86 Decoder for the instructions operands.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction DecodeOperands()\n{\n  //The Operands array is a string array in which the operand number is the element the decoded operand is positioned.\n\n  var out = [];\n\n  //This holds the decoded ModR/M byte from the \"Decode_ModRM_SIB_Value()\" function because the Register, and Address can decode differently.\n\n  var ModRMByte = [ -1, //Mode is set negative one used to check if the ModR/M has been decoded.\n    0, //The register value is decoded separately if used.\n    0 //the base register for the address location.\n  ];\n\n  //If no Immediate operand is used then the Immediate register encoding forces an IMM8 for the register even if the immediate is not used.\n\n  var IMM_Used = false; //This is set true for if any Immediate is read because the last Immediate byte is used as the register on the upper four bits.\n\n  //If reg opcode is active.\n\n  if( X86Decoder[0].Active )\n  {\n    out[ X86Decoder[0].OpNum ] = DecodeRegValue(\n      ( RegExtend | ( Opcode & 0x07 ) ), //Register value.\n      X86Decoder[0].BySizeAttrubute, //By size attribute or not.\n      X86Decoder[0].Size //Size settings.\n    );\n  }\n\n  //If ModR/M Address is active.\n\n  if( X86Decoder[1].Active )\n  {\n    //Decode the ModR/M byte Address which can end up reading another byte for SIB address, and including displacements.\n\n    if(X86Decoder[1].Type !== 0)\n    {\n      ModRMByte = Decode_ModRM_SIB_Value(); //Decode the ModR/M byte.\n      out[ X86Decoder[1].OpNum ] = Decode_ModRM_SIB_Address(\n        ModRMByte, //The ModR/M byte.\n        X86Decoder[1].BySizeAttrubute, //By size attribute or not.\n        X86Decoder[1].Size //Size settings.\n      );\n    }\n\n    //Else If the ModR/M type is 0 then it is a moffs address.\n\n    else\n    {\n      var s=0, AddrsSize = 0;\n      if( X86Decoder[1].BySizeAttrubute )\n      {\n        AddrsSize = ( Math.pow( 2, BitMode ) << 1 );\n        s = GetOperandSize( X86Decoder[1].Size, true ) << 1;\n      }\n      else\n      {\n        AddrsSize =  BitMode + 1;\n        s = X86Decoder[1].Size;\n      }\n      out[ X86Decoder[1].OpNum ] = PTR[ s ];\n      out[ X86Decoder[1].OpNum ] += SegOverride + DecodeImmediate( 0, X86Decoder[1].BySizeAttrubute, AddrsSize ) + \"]\";\n    }\n  }\n\n  //Decode the Register value of the ModR/M byte.\n\n  if( X86Decoder[2].Active )\n  {\n    //If the ModR/M address is not used, and ModR/M byte was not previously decoded then decode it.\n\n    if( ModRMByte[0] === -1 ){ ModRMByte = Decode_ModRM_SIB_Value(); }\n\n    //Decode only the Register Section of the ModR/M byte values.\n\n    out[ X86Decoder[2].OpNum ] = DecodeRegValue(\n      ( RegExtend | ( ModRMByte[1] & 0x07 ) ), //Register value.\n      X86Decoder[2].BySizeAttrubute, //By size attribute or not.\n      X86Decoder[2].Size //Size settings.\n    );\n  }\n\n  //First Immediate if used.\n\n  if( X86Decoder[3].Active )\n  {\n    var t = DecodeImmediate(\n      X86Decoder[3].Type, //Immediate input type.\n      X86Decoder[3].BySizeAttrubute, //By size attribute or not.\n      X86Decoder[3].Size //Size settings.\n    );\n\t  \n    //Check if Instruction uses condition codes.\n\n    if( Instruction.slice(-1) === \",\" )\n    {\n      Instruction = Instruction.split(\",\");\n\n      if( ( Extension >= 1 && Extension <= 2 && Opcode <= 0x400 && IMMValue < 0x20 ) || IMMValue < 0x08 )\n      {\n        IMMValue |= ( ( ( Opcode > 0x400 ) & 1 ) << 5 ); //XOP adjust.\n        Instruction = Instruction[0] + ConditionCodes[ IMMValue ] + Instruction[1];\n      }\n      else { Instruction = Instruction[0] + Instruction[1]; out[ X86Decoder[3].OpNum ] = t; }\n    }\n\n    //else add the Immediate byte encoding to the decoded instruction operands.\n\n    else { out[ X86Decoder[3].OpNum ] = t; }\n    \n    IMM_Used = true; //Immediate byte is read.\n  }\n\n  //Second Immediate if used.\n\n  if( X86Decoder[4].Active )\n  {\n    out[ X86Decoder[4].OpNum ] = DecodeImmediate(\n      X86Decoder[4].Type, //Immediate input type.\n      X86Decoder[4].BySizeAttrubute, //By size attribute or not.\n      X86Decoder[4].Size //Size settings.\n    );\n  }\n\n  //Third Immediate if used.\n\n  if( X86Decoder[5].Active )\n  {\n    out[ X86Decoder[5].OpNum ] = DecodeImmediate(\n      X86Decoder[5].Type, //Immediate input type.\n      X86Decoder[5].BySizeAttrubute, //By size attribute or not.\n      X86Decoder[5].Size //Size settings.\n    );\n  }\n\n  //Vector register if used from an SIMD vector extended instruction.\n\n  if( X86Decoder[6].Active )\n  {\n      out[ X86Decoder[6].OpNum ] = DecodeRegValue(\n      VectorRegister, //Register value.\n      X86Decoder[6].BySizeAttrubute, //By size attribute or not.\n      X86Decoder[6].Size //Size settings.\n    );\n  }\n\n  //Immediate register encoding.\n\n  if( X86Decoder[7].Active )\n  {\n    if( !IMM_Used ) { DecodeImmediate(0, false, 0); } //forces IMM8 if no Immediate has been used.\n    out[ X86Decoder[7].OpNum ] = DecodeRegValue(\n      ( ( ( IMMValue & 0xF0 ) >> 4 ) | ( ( IMMValue & 0x08 ) << 1 ) ), //Register value.\n      X86Decoder[7].BySizeAttrubute, //By size attribute or not.\n      X86Decoder[7].Size //Size settings.\n    );\n  }\n\n  //-------------------------------------------------------------------------------------------------------------------------\n  //Iterate though the 4 possible Explicit operands The first operands that is not active ends the Iteration.\n  //-------------------------------------------------------------------------------------------------------------------------\n\n  for( var i = 8; i < 11; i++ )\n  {\n    //-------------------------------------------------------------------------------------------------------------------------\n    //if Active Type is used as which Explicit operand.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    if( X86Decoder[i].Active )\n    {\n      //General use registers value 0 though 4 there size can change by size setting but can not be extended or changed.\n\n      if( X86Decoder[i].Type <= 3 )\n      {\n        out[ X86Decoder[i].OpNum ] = DecodeRegValue(\n          X86Decoder[i].Type, //register by value for Explicit Registers A, C, D, B.\n          X86Decoder[i].BySizeAttrubute, //By size attribute or not.\n          X86Decoder[i].Size //Size attribute.\n        );\n      }\n\n      //RBX address Explicit Operands prefixes can extend the registers and change pointer size RegMode 0.\n\n      else if( X86Decoder[i].Type === 4 )\n      {\n        s = 3; //If 32, or 64 bit ModR/M.\n        if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 && AddressOverride ) ){ s = 7; } //If 16 bit ModR/M.\n        out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address(\n          [ 0, 0, s ], //the RBX register only for the pointer.\n          X86Decoder[i].BySizeAttrubute, //By size attribute or not.\n          X86Decoder[i].Size //size attributes.\n        );\n      }\n\n      //source and destination address Explicit Operands prefixes can extend the registers and change pointer size.\n\n      else if( X86Decoder[i].Type === 5 | X86Decoder[i].Type === 6 )\n      {\n        s = 1; //If 32, or 64 bit ModR/M.\n        if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 & AddressOverride ) ) { s = -1; } //If 16 bit ModR/M.\n        out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address(\n          [ 0, 0, ( X86Decoder[i].Type + s ) ], //source and destination pointer register by type value.\n          X86Decoder[i].BySizeAttrubute, //By size attribute or not.\n          X86Decoder[i].Size //size attributes.\n        );\n      }\n\n      //The ST only Operand, and FS, GS.\n\n      else if( X86Decoder[i].Type >= 7 )\n      {\n        out[ X86Decoder[i].OpNum ] = [\"ST\", \"FS\", \"GS\", \"1\", \"3\", \"XMM0\", \"M10\"][ ( X86Decoder[i].Type - 7 ) ];\n      }\n    }\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    //else inactive end iteration.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    else { break; }\n\n  }\n\n  /*-------------------------------------------------------------------------------------------------------------------------\n  If the EVEX vector extension is active the Mask, and Zero merge control are inserted into operand 0 (Destination operand).\n  -------------------------------------------------------------------------------------------------------------------------*/\n\n  //Mask Register is used if it is not 0 in value.\n\n  if( MaskRegister !== 0 ){ out[0] += \"{K\" + MaskRegister + \"}\"; }\n  \n  //EVEX Zero Merge control.\n\n  if( Extension === 2 && HInt_ZeroMerg ) { out[0] += \"{Z}\"; }\n\n  //convert the operand array to a string and return it.\n\n  InsOperands = out.toString();\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThe main Instruction decode function plugs everything in together for the steps required to decode a full X86 instruction.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction DecodeInstruction()\n{\n  //Reset Prefix adjustments, and vector setting adjustments.\n\n  Reset();\n\n  var out = \"\"; //The instruction code that will be returned back from this function.\n\n  //Record the starting position.\n\n  InstructionPos = GetPosition();\n\n  //First read any opcodes (prefix) that act as adjustments to the main three operand decode functions ^DecodeRegValue()^,\n  //^Decode_ModRM_SIB_Address()^, and ^DecodeImmediate()^.\n\n  DecodePrefixAdjustments();\n\n  //Only continue if an invalid opcode is not read by DecodePrefixAdjustments() for cpu bit mode setting.\n\n  if( !InvalidOp )\n  {\n    //Decode the instruction.\n\n    DecodeOpcode();\n\n    //-------------------------------------------------------------------------------------------------------------------------\n    //Intel Larrabee CCCCC condition codes.\n    //-------------------------------------------------------------------------------------------------------------------------\n\n    if( Opcode >= 0x700 && Instruction.slice(-1) === \",\" )\n    {\n      Instruction = Instruction.split(\",\");\n\n      //CMP conditions.\n\n      if( Opcode >= 0x720 && Opcode <= 0x72F )\n      {\n        IMMValue = VectorRegister >> 2;\n\n        if( Float || ( IMMValue !== 3 && IMMValue !== 7 ) )\n        {\n          Instruction = Instruction[0] + ConditionCodes[IMMValue] + Instruction[1];\n        }\n        else { Instruction = Instruction[0] + Instruction[1]; }\n\n        IMMValue = 0; VectorRegister &= 0x03;\n      }\n\n      //Else High/Low.\n\n      else\n      {\n        Instruction = Instruction[0] + ( ( ( VectorRegister & 1 ) === 1 ) ? \"H\" : \"L\" ) + Instruction[1];\n      }\n    }\n\n    //Setup the X86 Decoder for which operands the instruction uses.\n\n    DecodeOperandString();\n\n    //Now only some instructions can vector extend, and that is only if the instruction is an SIMD Vector format instruction.\n\n    if( !Vect && Extension > 0 && Opcode <= 0x400 ) { InvalidOp = true; }\n\n    //The Width Bit setting must match the vector numbers size otherwise this create an invalid operation code in MVEX/EVEX unless the Width bit is ignored.\n\n    if( Vect && !IgnoresWidthbit && Extension >= 2 )\n    {\n      InvalidOp = ( ( SIMD & 1 ) !== ( WidthBit & 1 ) ); //Note use, and ignore width bit pastern EVEX.\n    }\n    if( Opcode >= 0x700 ) { WidthBit ^= IgnoresWidthbit; } //L1OM Width bit invert.\n  }\n\n  //If the instruction is invalid then set the instruction to \"???\"\n\n  if( InvalidOp )\n  {\n    out = \"???\" //set the returned instruction to invalid\n  }\n\n  //Else finish decoding the valid instruction.\n\n  else\n  {\n    //Decode each operand along the Decoder array in order, and deactivate them.\n\n    DecodeOperands();\n\n    /*-------------------------------------------------------------------------------------------------------------------------\n    3DNow Instruction name is encoded by the next byte after the ModR/M, and Reg operands.\n    -------------------------------------------------------------------------------------------------------------------------*/\n\n    if( Opcode === 0x10F )\n    {\n      //Lookup operation code.\n\n      Instruction = M3DNow[ BinCode[CodePos] ]; NextByte();\n\n      //If Invalid instruction.\n\n      if( Instruction === \"\" || Instruction == null )\n      {\n        Instruction = \"???\"; InsOperands = \"\";\n      }\n    }\n\n    /*-------------------------------------------------------------------------------------------------------------------------\n    Synthetic virtual machine operation codes.\n    -------------------------------------------------------------------------------------------------------------------------*/\n\n    else if( Instruction === \"SSS\" )\n    {\n      //The Next two bytes after the static opcode is the select synthetic virtual machine operation code.\n\n      var Code1 = BinCode[CodePos]; NextByte();\n      var Code2 = BinCode[CodePos]; NextByte();\n\n      //No operations exist past 4 in value for both bytes that combine to the operation code.\n\n      if( Code1 >= 5 || Code2 >= 5 ) { Instruction = \"???\"; }\n\n      //Else calculate the operation code in the 5x5 map.\n\n      else\n      {\n        Instruction = MSynthetic[ ( Code1 * 5 ) + Code2 ];\n\n        //If Invalid instruction.\n\n        if( Instruction === \"\" || Instruction == null )\n        {\n          Instruction = \"???\";\n        }\n      }\n    }\n\n    //32/16 bit instructions 9A, and EA use Segment, and offset with Immediate format.\n\n    if( Opcode === 0x9A || Opcode === 0xEA )\n    {\n      var t = InsOperands.split(\",\");\n      InsOperands = t[1] + \":\" +t[0];\n    }\n\n    //**Depending on the operation different prefixes replace others for  HLE, or MPX, and branch prediction.\n    //if REP prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XRELEASE.\n\n    if(PrefixG1 === Mnemonics[0xF3] && PrefixG2 === Mnemonics[0xF0] && XRelease)\n    {\n      PrefixG1 = \"XRELEASE\"; //Then change REP to XRELEASE.\n    }\n\n    //if REPNE prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XACQUIRE.\n\n    if(PrefixG1 === Mnemonics[0xF2] && PrefixG2 === Mnemonics[0xF0] && XAcquire)\n    {\n      PrefixG1 = \"XACQUIRE\"; //Then change REP to XACQUIRE\n    }\n\n    //Depending on the order that the Repeat prefix, and Lock prefix is used flip Prefix G1, and G2 if HLEFlipG1G2 it is true.\n\n    if((PrefixG1 === \"XRELEASE\" || PrefixG1 === \"XACQUIRE\") && HLEFlipG1G2)\n    {\n      t = PrefixG1; PrefixG1 = PrefixG2; PrefixG2 = t;\n    }\n\n    //if HT is active then it is a jump instruction check and adjust for the HT,and HNT prefix.\n\n    if(HT)\n    {\n      if (SegOverride === Mnemonics[0x2E])\n      {\n        PrefixG1 = \"HNT\";\n      }\n      else if (SegOverride === Mnemonics[0x3E])\n      {\n        PrefixG1 = \"HT\";\n      }\n    }\n\n    //else if Prefix is REPNE switch it to BND if operation is a MPX instruction.\n\n    if(PrefixG1 === Mnemonics[0xF2] && BND)\n    {\n      PrefixG1 = \"BND\";\n    }\n\n    //Before the Instruction is put together check the length of the instruction if it is longer than 15 bytes the instruction is undefined.\n\n    if ( InstructionHex.length > 30 )\n    {\n      //Calculate how many bytes over.\n\n      var Dif32 = ( ( InstructionHex.length - 30 ) >> 1 );\n\n      //Limit the instruction hex output to 15 bytes.\n\n      InstructionHex = InstructionHex.substring( 0, 30 );\n\n      //Calculate the Difference between the Disassembler current position.\n\n      Dif32 = Pos32 - Dif32;\n\n      //Convert Dif to unsignified numbers.\n\n      if( Dif32 < 0 ) { Dif32 += 0x100000000; }\n\n      //Convert to strings.\n\n      for (var S32 = Dif32.toString(16) ; S32.length < 8; S32 = \"0\" + S32);\n      for (var S64 = Pos64.toString(16) ; S64.length < 8; S64 = \"0\" + S64);\n\n      //Go to the Calculated address right after the Instruction UD.\n\n      GotoPosition( S64 + S32 );\n\n      //Set prefixes, and operands to empty strings, and set Instruction to UD.\n\n      PrefixG1 = \"\";PrefixG2 = \"\"; Instruction = \"???\"; InsOperands = \"\";\n    }\n\n    //Put the Instruction sequence together.\n\n    out = PrefixG1 + \" \" + PrefixG2 + \" \" + Instruction + \" \" + InsOperands;\n\n    //Remove any trailing spaces because of unused prefixes.\n\n    out = out.replace(/^[ ]+|[ ]+$/g,'');\n\n    //Add error suppression if used.\n\n    if( Opcode >= 0x700 || RoundMode !== 0 )\n    {\n      out += RoundModes[ RoundMode ];\n    }\n\n    //Return the instruction.\n  }\n\n  return( out );\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\nThis function Resets the Decoder in case of error, or an full instruction has been decoded.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nfunction Reset()\n{\n  //Reset Opcode, and Size attribute selector.\n\n  Opcode = 0; SizeAttrSelect = 1;\n  \n  //Reset Operands and instruction.\n  \n  Instruction = \"\"; InsOperands = \"\";\n\n  //Reset ModR/M.\n\n  RexActive = 0; RegExtend = 0; BaseExtend = 0; IndexExtend = 0;\n  SegOverride = \"[\"; AddressOverride = false; FarPointer = 0;\n\n  //Reset Vector extensions controls.\n\n  Extension = 0; SIMD = 0; Vect = false; ConversionMode = 0; WidthBit = false;\n  VectorRegister = 0; MaskRegister = 0; HInt_ZeroMerg = false; RoundMode = 0x00;\n\n  //Reset vector format settings.\n\n  IgnoresWidthbit = false; VSIB = false; RoundingSetting = 0;\n  Swizzle = false; Up = false; Float = false; VectS = 0x00;\n\n  //Reset IMMValue used for Imm register encoding, and Condition codes.\n\n  IMMValue = 0;\n\n  //Reset instruction Prefixes.\n\n  PrefixG1 = \"\", PrefixG2 = \"\";\n  XRelease = false; XAcquire = false; HLEFlipG1G2 = false;\n  HT = false;\n  BND = false;\n\n  //Reset Invalid operation code.\n\n  InvalidOp = false;\n\n  //Reset instruction hex because it is used to check if the instruction is longer than 15 bytes which is impossible for the X86 Decoder Circuit.\n\n  InstructionHex = \"\";\n\n  //Deactivate all operands along the X86Decoder.\n\n  for( var i = 0; i < X86Decoder.length; X86Decoder[i++].Deactivate() );\n}\n\n/*-------------------------------------------------------------------------------------------------------------------------\ndo an linear disassemble.\n-------------------------------------------------------------------------------------------------------------------------*/\n\nexport function LDisassemble()\n{\n  var Instruction = \"\"; //Stores the Decoded instruction.\n  var Out = \"\";  //The Disassemble output\n\n  //Disassemble binary code using an linear pass.\n\n  var len = BinCode.length;\n\n  //Backup the base address.\n\n  var BPos64 = Pos64, BPos32 = Pos32;\n\n  while( CodePos < len )\n  {\n    Instruction = DecodeInstruction();\n\n    //Add the 64 bit address of the output if ShowInstructionPos decoding is active.\n\n    if( ShowInstructionPos )\n    {\n      Out += InstructionPos + \" \";\n    }\n\n    //Show Each byte that was read to decode the instruction if ShowInstructionHex decoding is active.\n\n    if(ShowInstructionHex)\n    {\n      InstructionHex = InstructionHex.toUpperCase();\n      for(; InstructionHex.length < 32; InstructionHex = InstructionHex + \" \" );\n      Out += InstructionHex + \"\";\n    }\n\n    //Put the decoded instruction into the output and make a new line.\n\n    Out += Instruction + \"\\r\\n\";\n\n    //Reset instruction Pos and Hex.\n\n    InstructionPos = \"\"; InstructionHex = \"\";\n  }\n\n  CodePos = 0; //Reset the Code position\n  Pos32 = BPos32; Pos64 = BPos64; //Reset Base address.\n\n  //return the decoded binary code\n\n  return(Out);\n\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////\n\n/*\n * The following code has been added to expose public methods for use in CyberChef\n */\n\nexport function setBitMode (val) {\n  BitMode = val; \n};\nexport function setShowInstructionHex (val) {\n  ShowInstructionHex = val;\n};\nexport function setShowInstructionPos (val) {\n  ShowInstructionPos = val; \n};\n\n"
  },
  {
    "path": "src/core/vendor/gost/gostCipher.mjs",
    "content": "/**\n * GOST 28147-89/GOST R 34.12-2015/GOST R 32.13-2015 Encryption Algorithm\n * 1.76\n * 2014-2016, Rudolf Nickolaev. All rights reserved.\n *\n * Exported for CyberChef by mshwed [m@ttshwed.com]\n */\n\n/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\nimport GostRandom from './gostRandom.mjs';\n\nimport crypto from 'crypto'\n\n/*\n* Initial parameters and common algortithms of GOST 28147-89\n*\n* http://tools.ietf.org/html/rfc5830\n*\n*/ // <editor-fold defaultstate=\"collapsed\">\n\nvar root = {};\nvar rootCrypto = crypto;\nvar CryptoOperationData = ArrayBuffer;\nvar SyntaxError = Error,\n        DataError = Error,\n        NotSupportedError = Error;\n/*\n* Check supported\n* This implementation support only Little Endian arhitecture\n*/\n\nvar littleEndian = (function () {\n    var buffer = new CryptoOperationData(2);\n    new DataView(buffer).setInt16(0, 256, true);\n    return new Int16Array(buffer)[0] === 256;\n})();\n\n// Default initial vector\nvar defaultIV = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);\n\n// Predefined sBox collection\nvar sBoxes = {\n    'E-TEST': [\n        0x4, 0x2, 0xF, 0x5, 0x9, 0x1, 0x0, 0x8, 0xE, 0x3, 0xB, 0xC, 0xD, 0x7, 0xA, 0x6,\n        0xC, 0x9, 0xF, 0xE, 0x8, 0x1, 0x3, 0xA, 0x2, 0x7, 0x4, 0xD, 0x6, 0x0, 0xB, 0x5,\n        0xD, 0x8, 0xE, 0xC, 0x7, 0x3, 0x9, 0xA, 0x1, 0x5, 0x2, 0x4, 0x6, 0xF, 0x0, 0xB,\n        0xE, 0x9, 0xB, 0x2, 0x5, 0xF, 0x7, 0x1, 0x0, 0xD, 0xC, 0x6, 0xA, 0x4, 0x3, 0x8,\n        0x3, 0xE, 0x5, 0x9, 0x6, 0x8, 0x0, 0xD, 0xA, 0xB, 0x7, 0xC, 0x2, 0x1, 0xF, 0x4,\n        0x8, 0xF, 0x6, 0xB, 0x1, 0x9, 0xC, 0x5, 0xD, 0x3, 0x7, 0xA, 0x0, 0xE, 0x2, 0x4,\n        0x9, 0xB, 0xC, 0x0, 0x3, 0x6, 0x7, 0x5, 0x4, 0x8, 0xE, 0xF, 0x1, 0xA, 0x2, 0xD,\n        0xC, 0x6, 0x5, 0x2, 0xB, 0x0, 0x9, 0xD, 0x3, 0xE, 0x7, 0xA, 0xF, 0x4, 0x1, 0x8\n    ],\n    'E-A': [\n        0x9, 0x6, 0x3, 0x2, 0x8, 0xB, 0x1, 0x7, 0xA, 0x4, 0xE, 0xF, 0xC, 0x0, 0xD, 0x5,\n        0x3, 0x7, 0xE, 0x9, 0x8, 0xA, 0xF, 0x0, 0x5, 0x2, 0x6, 0xC, 0xB, 0x4, 0xD, 0x1,\n        0xE, 0x4, 0x6, 0x2, 0xB, 0x3, 0xD, 0x8, 0xC, 0xF, 0x5, 0xA, 0x0, 0x7, 0x1, 0x9,\n        0xE, 0x7, 0xA, 0xC, 0xD, 0x1, 0x3, 0x9, 0x0, 0x2, 0xB, 0x4, 0xF, 0x8, 0x5, 0x6,\n        0xB, 0x5, 0x1, 0x9, 0x8, 0xD, 0xF, 0x0, 0xE, 0x4, 0x2, 0x3, 0xC, 0x7, 0xA, 0x6,\n        0x3, 0xA, 0xD, 0xC, 0x1, 0x2, 0x0, 0xB, 0x7, 0x5, 0x9, 0x4, 0x8, 0xF, 0xE, 0x6,\n        0x1, 0xD, 0x2, 0x9, 0x7, 0xA, 0x6, 0x0, 0x8, 0xC, 0x4, 0x5, 0xF, 0x3, 0xB, 0xE,\n        0xB, 0xA, 0xF, 0x5, 0x0, 0xC, 0xE, 0x8, 0x6, 0x2, 0x3, 0x9, 0x1, 0x7, 0xD, 0x4\n    ],\n    'E-B': [\n        0x8, 0x4, 0xB, 0x1, 0x3, 0x5, 0x0, 0x9, 0x2, 0xE, 0xA, 0xC, 0xD, 0x6, 0x7, 0xF,\n        0x0, 0x1, 0x2, 0xA, 0x4, 0xD, 0x5, 0xC, 0x9, 0x7, 0x3, 0xF, 0xB, 0x8, 0x6, 0xE,\n        0xE, 0xC, 0x0, 0xA, 0x9, 0x2, 0xD, 0xB, 0x7, 0x5, 0x8, 0xF, 0x3, 0x6, 0x1, 0x4,\n        0x7, 0x5, 0x0, 0xD, 0xB, 0x6, 0x1, 0x2, 0x3, 0xA, 0xC, 0xF, 0x4, 0xE, 0x9, 0x8,\n        0x2, 0x7, 0xC, 0xF, 0x9, 0x5, 0xA, 0xB, 0x1, 0x4, 0x0, 0xD, 0x6, 0x8, 0xE, 0x3,\n        0x8, 0x3, 0x2, 0x6, 0x4, 0xD, 0xE, 0xB, 0xC, 0x1, 0x7, 0xF, 0xA, 0x0, 0x9, 0x5,\n        0x5, 0x2, 0xA, 0xB, 0x9, 0x1, 0xC, 0x3, 0x7, 0x4, 0xD, 0x0, 0x6, 0xF, 0x8, 0xE,\n        0x0, 0x4, 0xB, 0xE, 0x8, 0x3, 0x7, 0x1, 0xA, 0x2, 0x9, 0x6, 0xF, 0xD, 0x5, 0xC\n    ],\n    'E-C': [\n        0x1, 0xB, 0xC, 0x2, 0x9, 0xD, 0x0, 0xF, 0x4, 0x5, 0x8, 0xE, 0xA, 0x7, 0x6, 0x3,\n        0x0, 0x1, 0x7, 0xD, 0xB, 0x4, 0x5, 0x2, 0x8, 0xE, 0xF, 0xC, 0x9, 0xA, 0x6, 0x3,\n        0x8, 0x2, 0x5, 0x0, 0x4, 0x9, 0xF, 0xA, 0x3, 0x7, 0xC, 0xD, 0x6, 0xE, 0x1, 0xB,\n        0x3, 0x6, 0x0, 0x1, 0x5, 0xD, 0xA, 0x8, 0xB, 0x2, 0x9, 0x7, 0xE, 0xF, 0xC, 0x4,\n        0x8, 0xD, 0xB, 0x0, 0x4, 0x5, 0x1, 0x2, 0x9, 0x3, 0xC, 0xE, 0x6, 0xF, 0xA, 0x7,\n        0xC, 0x9, 0xB, 0x1, 0x8, 0xE, 0x2, 0x4, 0x7, 0x3, 0x6, 0x5, 0xA, 0x0, 0xF, 0xD,\n        0xA, 0x9, 0x6, 0x8, 0xD, 0xE, 0x2, 0x0, 0xF, 0x3, 0x5, 0xB, 0x4, 0x1, 0xC, 0x7,\n        0x7, 0x4, 0x0, 0x5, 0xA, 0x2, 0xF, 0xE, 0xC, 0x6, 0x1, 0xB, 0xD, 0x9, 0x3, 0x8\n    ],\n    'E-D': [\n        0xF, 0xC, 0x2, 0xA, 0x6, 0x4, 0x5, 0x0, 0x7, 0x9, 0xE, 0xD, 0x1, 0xB, 0x8, 0x3,\n        0xB, 0x6, 0x3, 0x4, 0xC, 0xF, 0xE, 0x2, 0x7, 0xD, 0x8, 0x0, 0x5, 0xA, 0x9, 0x1,\n        0x1, 0xC, 0xB, 0x0, 0xF, 0xE, 0x6, 0x5, 0xA, 0xD, 0x4, 0x8, 0x9, 0x3, 0x7, 0x2,\n        0x1, 0x5, 0xE, 0xC, 0xA, 0x7, 0x0, 0xD, 0x6, 0x2, 0xB, 0x4, 0x9, 0x3, 0xF, 0x8,\n        0x0, 0xC, 0x8, 0x9, 0xD, 0x2, 0xA, 0xB, 0x7, 0x3, 0x6, 0x5, 0x4, 0xE, 0xF, 0x1,\n        0x8, 0x0, 0xF, 0x3, 0x2, 0x5, 0xE, 0xB, 0x1, 0xA, 0x4, 0x7, 0xC, 0x9, 0xD, 0x6,\n        0x3, 0x0, 0x6, 0xF, 0x1, 0xE, 0x9, 0x2, 0xD, 0x8, 0xC, 0x4, 0xB, 0xA, 0x5, 0x7,\n        0x1, 0xA, 0x6, 0x8, 0xF, 0xB, 0x0, 0x4, 0xC, 0x3, 0x5, 0x9, 0x7, 0xD, 0x2, 0xE\n    ],\n    'E-SC': [\n        0x3, 0x6, 0x1, 0x0, 0x5, 0x7, 0xd, 0x9, 0x4, 0xb, 0x8, 0xc, 0xe, 0xf, 0x2, 0xa,\n        0x7, 0x1, 0x5, 0x2, 0x8, 0xb, 0x9, 0xc, 0xd, 0x0, 0x3, 0xa, 0xf, 0xe, 0x4, 0x6,\n        0xf, 0x1, 0x4, 0x6, 0xc, 0x8, 0x9, 0x2, 0xe, 0x3, 0x7, 0xa, 0xb, 0xd, 0x5, 0x0,\n        0x3, 0x4, 0xf, 0xc, 0x5, 0x9, 0xe, 0x0, 0x6, 0x8, 0x7, 0xa, 0x1, 0xb, 0xd, 0x2,\n        0x6, 0x9, 0x0, 0x7, 0xb, 0x8, 0x4, 0xc, 0x2, 0xe, 0xa, 0xf, 0x1, 0xd, 0x5, 0x3,\n        0x6, 0x1, 0x2, 0xf, 0x0, 0xb, 0x9, 0xc, 0x7, 0xd, 0xa, 0x5, 0x8, 0x4, 0xe, 0x3,\n        0x0, 0x2, 0xe, 0xc, 0x9, 0x1, 0x4, 0x7, 0x3, 0xf, 0x6, 0x8, 0xa, 0xd, 0xb, 0x5,\n        0x5, 0x2, 0xb, 0x8, 0x4, 0xc, 0x7, 0x1, 0xa, 0x6, 0xe, 0x0, 0x9, 0x3, 0xd, 0xf\n    ],\n    'E-Z': [// This is default S-box in according to draft of new standard\n        0xc, 0x4, 0x6, 0x2, 0xa, 0x5, 0xb, 0x9, 0xe, 0x8, 0xd, 0x7, 0x0, 0x3, 0xf, 0x1,\n        0x6, 0x8, 0x2, 0x3, 0x9, 0xa, 0x5, 0xc, 0x1, 0xe, 0x4, 0x7, 0xb, 0xd, 0x0, 0xf,\n        0xb, 0x3, 0x5, 0x8, 0x2, 0xf, 0xa, 0xd, 0xe, 0x1, 0x7, 0x4, 0xc, 0x9, 0x6, 0x0,\n        0xc, 0x8, 0x2, 0x1, 0xd, 0x4, 0xf, 0x6, 0x7, 0x0, 0xa, 0x5, 0x3, 0xe, 0x9, 0xb,\n        0x7, 0xf, 0x5, 0xa, 0x8, 0x1, 0x6, 0xd, 0x0, 0x9, 0x3, 0xe, 0xb, 0x4, 0x2, 0xc,\n        0x5, 0xd, 0xf, 0x6, 0x9, 0x2, 0xc, 0xa, 0xb, 0x7, 0x8, 0x1, 0x4, 0x3, 0xe, 0x0,\n        0x8, 0xe, 0x2, 0x5, 0x6, 0x9, 0x1, 0xc, 0xf, 0x4, 0xb, 0x0, 0xd, 0xa, 0x3, 0x7,\n        0x1, 0x7, 0xe, 0xd, 0x0, 0x5, 0x8, 0x3, 0x4, 0xf, 0xa, 0x6, 0x9, 0xc, 0xb, 0x2\n    ],\n    //S-box for digest\n    'D-TEST': [\n        0x4, 0xA, 0x9, 0x2, 0xD, 0x8, 0x0, 0xE, 0x6, 0xB, 0x1, 0xC, 0x7, 0xF, 0x5, 0x3,\n        0xE, 0xB, 0x4, 0xC, 0x6, 0xD, 0xF, 0xA, 0x2, 0x3, 0x8, 0x1, 0x0, 0x7, 0x5, 0x9,\n        0x5, 0x8, 0x1, 0xD, 0xA, 0x3, 0x4, 0x2, 0xE, 0xF, 0xC, 0x7, 0x6, 0x0, 0x9, 0xB,\n        0x7, 0xD, 0xA, 0x1, 0x0, 0x8, 0x9, 0xF, 0xE, 0x4, 0x6, 0xC, 0xB, 0x2, 0x5, 0x3,\n        0x6, 0xC, 0x7, 0x1, 0x5, 0xF, 0xD, 0x8, 0x4, 0xA, 0x9, 0xE, 0x0, 0x3, 0xB, 0x2,\n        0x4, 0xB, 0xA, 0x0, 0x7, 0x2, 0x1, 0xD, 0x3, 0x6, 0x8, 0x5, 0x9, 0xC, 0xF, 0xE,\n        0xD, 0xB, 0x4, 0x1, 0x3, 0xF, 0x5, 0x9, 0x0, 0xA, 0xE, 0x7, 0x6, 0x8, 0x2, 0xC,\n        0x1, 0xF, 0xD, 0x0, 0x5, 0x7, 0xA, 0x4, 0x9, 0x2, 0x3, 0xE, 0x6, 0xB, 0x8, 0xC\n    ],\n    'D-A': [\n        0xA, 0x4, 0x5, 0x6, 0x8, 0x1, 0x3, 0x7, 0xD, 0xC, 0xE, 0x0, 0x9, 0x2, 0xB, 0xF,\n        0x5, 0xF, 0x4, 0x0, 0x2, 0xD, 0xB, 0x9, 0x1, 0x7, 0x6, 0x3, 0xC, 0xE, 0xA, 0x8,\n        0x7, 0xF, 0xC, 0xE, 0x9, 0x4, 0x1, 0x0, 0x3, 0xB, 0x5, 0x2, 0x6, 0xA, 0x8, 0xD,\n        0x4, 0xA, 0x7, 0xC, 0x0, 0xF, 0x2, 0x8, 0xE, 0x1, 0x6, 0x5, 0xD, 0xB, 0x9, 0x3,\n        0x7, 0x6, 0x4, 0xB, 0x9, 0xC, 0x2, 0xA, 0x1, 0x8, 0x0, 0xE, 0xF, 0xD, 0x3, 0x5,\n        0x7, 0x6, 0x2, 0x4, 0xD, 0x9, 0xF, 0x0, 0xA, 0x1, 0x5, 0xB, 0x8, 0xE, 0xC, 0x3,\n        0xD, 0xE, 0x4, 0x1, 0x7, 0x0, 0x5, 0xA, 0x3, 0xC, 0x8, 0xF, 0x6, 0x2, 0x9, 0xB,\n        0x1, 0x3, 0xA, 0x9, 0x5, 0xB, 0x4, 0xF, 0x8, 0x6, 0x7, 0xE, 0xD, 0x0, 0x2, 0xC\n    ],\n    'D-SC': [\n        0xb, 0xd, 0x7, 0x0, 0x5, 0x4, 0x1, 0xf, 0x9, 0xe, 0x6, 0xa, 0x3, 0xc, 0x8, 0x2,\n        0x1, 0x2, 0x7, 0x9, 0xd, 0xb, 0xf, 0x8, 0xe, 0xc, 0x4, 0x0, 0x5, 0x6, 0xa, 0x3,\n        0x5, 0x1, 0xd, 0x3, 0xf, 0x6, 0xc, 0x7, 0x9, 0x8, 0xb, 0x2, 0x4, 0xe, 0x0, 0xa,\n        0xd, 0x1, 0xb, 0x4, 0x9, 0xc, 0xe, 0x0, 0x7, 0x5, 0x8, 0xf, 0x6, 0x2, 0xa, 0x3,\n        0x2, 0xd, 0xa, 0xf, 0x9, 0xb, 0x3, 0x7, 0x8, 0xc, 0x5, 0xe, 0x6, 0x0, 0x1, 0x4,\n        0x0, 0x4, 0x6, 0xc, 0x5, 0x3, 0x8, 0xd, 0xa, 0xb, 0xf, 0x2, 0x1, 0x9, 0x7, 0xe,\n        0x1, 0x3, 0xc, 0x8, 0xa, 0x6, 0xb, 0x0, 0x2, 0xe, 0x7, 0x9, 0xf, 0x4, 0x5, 0xd,\n        0xa, 0xb, 0x6, 0x0, 0x1, 0x3, 0x4, 0x7, 0xe, 0xd, 0x5, 0xf, 0x8, 0x2, 0x9, 0xc\n    ]\n};\n\nvar C = new Uint8Array([\n    0x69, 0x00, 0x72, 0x22, 0x64, 0xC9, 0x04, 0x23,\n    0x8D, 0x3A, 0xDB, 0x96, 0x46, 0xE9, 0x2A, 0xC4,\n    0x18, 0xFE, 0xAC, 0x94, 0x00, 0xED, 0x07, 0x12,\n    0xC0, 0x86, 0xDC, 0xC2, 0xEF, 0x4C, 0xA9, 0x2B\n]);\n\nfunction signed(x) {\n    return x >= 0x80000000 ? x - 0x100000000 : x;\n}\n\nfunction unsigned(x) {\n    return x < 0 ? x + 0x100000000 : x;\n}\n\n// Set random values into Uint8Arry\n// Random generator\nfunction randomSeed(e) {\n    GostRandom = GostRandom || root.GostRandom;\n    var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto;\n    if (randomSource.getRandomValues)\n        randomSource.getRandomValues(e);\n    else\n        throw new NotSupportedError('Random generator not found');\n}\n\n// Get buffer\nfunction buffer(d) {\n    if (d instanceof CryptoOperationData)\n        return d;\n    else if (d && d?.buffer instanceof CryptoOperationData)\n        return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ?\n                d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer;\n    else\n        throw new DataError('CryptoOperationData required');\n}\n\n// Get byte array\nfunction byteArray(d) {\n    return new Uint8Array(buffer(d));\n}\n\n// Clone byte array\nfunction cloneArray(d) {\n    return new Uint8Array(byteArray(d));\n}\n\n\n// Get int32 array\nfunction intArray(d) {\n    return new Int32Array(buffer(d));\n}\n\n// Swap bytes for version 2015\nfunction swap32(b) {\n    return ((b & 0xff) << 24)\n            | ((b & 0xff00) << 8)\n            | ((b >> 8) & 0xff00)\n            | ((b >> 24) & 0xff);\n}\n\n// </editor-fold>\n\n/*\n    * Initial parameters and common algortithms of GOST R 34.12-15\n    * Algorithm \"Kuznechik\" 128bit\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\n// Default initial vector\nvar defaultIV128 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);\n\n// Mult table for R function\nvar multTable = (function () {\n\n    // Multiply two numbers in the GF(2^8) finite field defined\n    // by the polynomial x^8 + x^7 + x^6 + x + 1 = 0 */\n    function gmul(a, b) {\n        var p = 0, counter, carry;\n        for (counter = 0; counter < 8; counter++) {\n            if (b & 1)\n                p ^= a;\n            carry = a & 0x80; // detect if x^8 term is about to be generated\n            a = (a << 1) & 0xff;\n            if (carry)\n                a ^= 0xc3; // replace x^8 with x^7 + x^6 + x + 1\n            b >>= 1;\n        }\n        return p & 0xff;\n    }\n\n    // It is required only this values for R function\n    //       0   1   2    3    4    5    6    7\n    var x = [1, 16, 32, 133, 148, 192, 194, 251];\n    var m = [];\n    for (var i = 0; i < 8; i++) {\n        m[i] = [];\n        for (var j = 0; j < 256; j++)\n            m[i][j] = gmul(x[i], j);\n    }\n    return m;\n})();\n\n// 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1\nvar kB = [4, 2, 3, 1, 6, 5, 0, 7, 0, 5, 6, 1, 3, 2, 4, 0];\n\n// R - function\nfunction funcR(d) {\n    var sum = 0;\n    for (var i = 0; i < 16; i++)\n        sum ^= multTable[kB[i]][d[i]];\n\n    for (var i = 16; i > 0; --i)\n        d[i] = d[i - 1];\n    d[0] = sum;\n}\n\nfunction funcReverseR(d) {\n    var tmp = d[0];\n    for (var i = 0; i < 15; i++)\n        d[i] = d[i + 1];\n    d[15] = tmp;\n\n    var sum = 0;\n    for (i = 0; i < 16; i++)\n        sum ^= multTable[kB[i]][d[i]];\n    d[15] = sum;\n}\n\n// Nonlinear transformation\nvar kPi = [\n    252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77,\n    233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193,\n    249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79,\n    5, 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31,\n    235, 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204,\n    181, 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135,\n    21, 161, 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177,\n    50, 117, 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87,\n    223, 245, 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3,\n    224, 15, 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74,\n    167, 151, 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65,\n    173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59,\n    7, 88, 179, 64, 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137,\n    225, 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97,\n    32, 113, 103, 164, 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82,\n    89, 166, 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182\n];\n\nvar kReversePi = (function () {\n    var m = [];\n    for (var i = 0, n = kPi.length; i < n; i++)\n        m[kPi[i]] = i;\n    return m;\n})();\n\nfunction funcS(d) {\n    for (var i = 0; i < 16; ++i)\n        d[i] = kPi[d[i]];\n}\n\nfunction funcReverseS(d) {\n    for (var i = 0; i < 16; ++i)\n        d[i] = kReversePi[d[i]];\n}\n\nfunction funcX(a, b) {\n    for (var i = 0; i < 16; ++i)\n        a[i] ^= b[i];\n}\n\nfunction funcL(d) {\n    for (var i = 0; i < 16; ++i)\n        funcR(d);\n}\n\nfunction funcReverseL(d) {\n    for (var i = 0; i < 16; ++i)\n        funcReverseR(d);\n}\n\nfunction funcLSX(a, b) {\n    funcX(a, b);\n    funcS(a);\n    funcL(a);\n}\n\nfunction funcReverseLSX(a, b) {\n    funcX(a, b);\n    funcReverseL(a);\n    funcReverseS(a);\n}\n\nfunction funcF(inputKey, inputKeySecond, iterationConst) {\n    var tmp = new Uint8Array(inputKey);\n    funcLSX(inputKey, iterationConst);\n    funcX(inputKey, inputKeySecond);\n    inputKeySecond.set(tmp);\n}\n\nfunction funcC(number, d) {\n    for (var i = 0; i < 15; i++)\n        d[i] = 0;\n    d[15] = number;\n    funcL(d);\n}\n\n// </editor-fold>\n\n/**\n * Key schedule for GOST R 34.12-15 128bits\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method keySchedule\n * @param {type} k\n * @returns {Uint8Array}\n */\nfunction keySchedule128(k) // <editor-fold defaultstate=\"collapsed\">\n{\n    var keys = new Uint8Array(160), c = new Uint8Array(16);\n    keys.set(byteArray(k));\n    for (var j = 0; j < 4; j++) {\n        var j0 = 32 * j, j1 = 32 * (j + 1);\n        keys.set(new Uint8Array(keys.buffer, j0, 32), j1);\n        for (var i = 1; i < 9; i++) {\n            funcC(j * 8 + i, c);\n            funcF(new Uint8Array(keys.buffer, j1, 16),\n                    new Uint8Array(keys.buffer, j1 + 16, 16), c);\n        }\n    }\n    return keys;\n} // </editor-fold>\n\n/**\n * GOST R 34.12-15 128 bits encrypt/decrypt process\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method round\n * @param {Uint8Array} k Scheduled key\n * @param {Uint8Array} d Data\n * @param {number} ofs Offsec\n * @param {number} e true - decrypt\n */\nfunction process128(k, d, ofs, e) // <editor-fold defaultstate=\"collapsed\">\n{\n    ofs = ofs || d.byteOffset;\n    var r = new Uint8Array(d.buffer, ofs, 16);\n    if (e) {\n        for (var i = 0; i < 9; i++)\n            funcReverseLSX(r, new Uint8Array(k.buffer, (9 - i) * 16, 16));\n\n        funcX(r, new Uint8Array(k.buffer, 0, 16));\n    } else {\n        for (var i = 0; i < 9; i++)\n            funcLSX(r, new Uint8Array(k.buffer, 16 * i, 16));\n\n        funcX(r, new Uint8Array(k.buffer, 16 * 9, 16));\n    }\n} // </editor-fold>\n\n/**\n * One GOST encryption round\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method round\n * @param {Int8Array} S sBox\n * @param {Int32Array} m 2x32 bits cipher block\n * @param {Int32Array} k 32 bits key[i]\n */\nfunction round(S, m, k) // <editor-fold defaultstate=\"collapsed\">\n{\n    var cm = (m[0] + k) & 0xffffffff;\n\n    var om = S[  0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4);\n    om |= S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4);\n    om |= S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4);\n    om |= S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4);\n    om |= S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4);\n    om |= S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4);\n    om |= S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4);\n    om |= S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4);\n    cm = om << 11 | om >>> (32 - 11);\n\n    cm ^= m[1];\n    m[1] = m[0];\n    m[0] = cm;\n\n} // </editor-fold>\n\n/**\n * Process encrypt/decrypt block with key K using GOST 28147-89\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method process\n * @param k {Int32Array} 8x32 bits key\n * @param d {Int32Array} 8x8 bits cipher block\n * @param ofs {number} offset\n */\nfunction process89(k, d, ofs) // <editor-fold defaultstate=\"collapsed\">\n{\n    ofs = ofs || d.byteOffset;\n    var s = this.sBox,\n            m = new Int32Array(d.buffer, ofs, 2);\n\n    for (var i = 0; i < 32; i++)\n        round(s, m, k[i]);\n\n    var r = m[0];\n    m[0] = m[1];\n    m[1] = r;\n} // </editor-fold>\n\n/**\n * Process encrypt/decrypt block with key K using GOST R 34.12-15 64bit block\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method process\n * @param k {Int32Array} 8x32 bits key\n * @param d {Int32Array} 8x8 bits cipher block\n * @param ofs {number} offset\n */\nfunction process15(k, d, ofs) // <editor-fold defaultstate=\"collapsed\">\n{\n    ofs = ofs || d.byteOffset;\n    var s = this.sBox,\n            m = new Int32Array(d.buffer, ofs, 2),\n            r = swap32(m[0]);\n    m[0] = swap32(m[1]);\n    m[1] = r;\n\n    for (var i = 0; i < 32; i++)\n        round(s, m, k[i]);\n\n    m[0] = swap32(m[0]);\n    m[1] = swap32(m[1]);\n} // </editor-fold>\n\n/**\n * Key keySchedule algorithm for GOST 28147-89 64bit cipher\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method process\n * @param k {Uint8Array} 8 bit key array\n * @param e {boolean}  true - decrypt\n * @returns {Int32Array} keyScheduled 32-bit key\n */\nfunction keySchedule89(k, e) // <editor-fold defaultstate=\"collapsed\">\n{\n    var sch = new Int32Array(32),\n            key = new Int32Array(buffer(k));\n\n    for (var i = 0; i < 8; i++)\n        sch[i] = key[i];\n\n    if (e) {\n        for (var i = 0; i < 8; i++)\n            sch[i + 8] = sch[7 - i];\n\n        for (var i = 0; i < 8; i++)\n            sch[i + 16] = sch[7 - i];\n    } else {\n        for (var i = 0; i < 8; i++)\n            sch[i + 8] = sch[i];\n\n        for (var i = 0; i < 8; i++)\n            sch[i + 16] = sch[i];\n    }\n\n    for (var i = 0; i < 8; i++)\n        sch[i + 24] = sch[7 - i];\n\n    return sch;\n} // </editor-fold>\n\n/**\n * Key keySchedule algorithm for GOST R 34.12-15 64bit cipher\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method process\n * @param k {Uint8Array} 8 bit key array\n * @param e {boolean}  true - decrypt\n * @returns {Int32Array} keyScheduled 32-bit key\n */\nfunction keySchedule15(k, e) // <editor-fold defaultstate=\"collapsed\">\n{\n    var sch = new Int32Array(32),\n            key = new Int32Array(buffer(k));\n\n    for (var i = 0; i < 8; i++)\n        sch[i] = swap32(key[i]);\n\n    if (e) {\n        for (var i = 0; i < 8; i++)\n            sch[i + 8] = sch[7 - i];\n\n        for (var i = 0; i < 8; i++)\n            sch[i + 16] = sch[7 - i];\n    } else {\n        for (var i = 0; i < 8; i++)\n            sch[i + 8] = sch[i];\n\n        for (var i = 0; i < 8; i++)\n            sch[i + 16] = sch[i];\n    }\n\n    for (var i = 0; i < 8; i++)\n        sch[i + 24] = sch[7 - i];\n\n    return sch;\n} // </editor-fold>\n\n/**\n * Key schedule for RC2\n *\n * https://tools.ietf.org/html/rfc2268\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method keySchedule\n * @param {Uint8Array} k\n * @returns {Uint16Array}\n */\nvar keyScheduleRC2 = (function () // <editor-fold defaultstate=\"collapsed\">\n{\n    // an array of \"random\" bytes based on the digits of PI = 3.14159...\n    var PITABLE = new Uint8Array([\n        0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,\n        0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,\n        0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,\n        0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,\n        0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,\n        0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,\n        0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,\n        0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,\n        0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,\n        0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,\n        0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,\n        0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,\n        0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,\n        0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,\n        0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,\n        0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad\n    ]);\n\n    return function (k)\n    {\n        var key = new Uint8Array(buffer(k)),\n                T = Math.min(key.length, 128),\n                T1 = this.effectiveLength,\n                T8 = Math.floor((T1 + 7) / 8),\n                TM = 0xff % Math.pow(2, 8 + T1 - 8 * T8);\n\n        var L = new Uint8Array(128), K = new Uint16Array(L.buffer);\n        for (var i = 0; i < T; i++)\n            L[i] = key[i];\n        for (var i = T; i < 128; i++)\n            L[i] = PITABLE[(L[i - 1] + L[i - T]) % 256];\n        L[128 - T8] = PITABLE[L[128 - T8] & TM];\n        for (var i = 127 - T8; i >= 0; --i)\n            L[i] = PITABLE[L[i + 1] ^ L[i + T8]];\n        return K;\n    };\n} // </editor-fold>\n)();\n\n/**\n * RC2 encrypt/decrypt process\n *\n * https://tools.ietf.org/html/rfc2268\n *\n * @memberOf GostCipher\n * @private\n * @instance\n * @method round\n * @param {CryptoOperationData} k Scheduled key\n * @param {CryptoOperationData} d Data\n * @param {number} ofs Offsec\n * @param {number} e true - decrypt\n */\nvar processRC2 = (function () // <editor-fold defaultstate=\"collapsed\">\n{\n    var K, j, R = new Uint16Array(4),\n            s = new Uint16Array([1, 2, 3, 5]), reverse;\n\n    function rol(R, s) {\n        return (R << s | R >>> (16 - s)) & 0xffff;\n    }\n\n    function ror(R, s) {\n        return (R >>> s | R << (16 - s)) & 0xffff;\n    }\n\n    function mix(i) {\n        if (reverse) {\n            R[i] = ror(R[i], s[i]);\n            R[i] = R[i] - K[j] - (R[(i + 3) % 4] & R[(i + 2) % 4]) - ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);\n            j = j - 1;\n        } else {\n            R[i] = R[i] + K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) + ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);\n            j = j + 1;\n            R[i] = rol(R[i], s[i]);\n        }\n    }\n\n    function mash(i) {\n        if (reverse) {\n            R[i] = R[i] - K[R[(i + 3) % 4] & 63];\n        } else {\n            R[i] = R[i] + K[R[(i + 3) % 4] & 63];\n        }\n    }\n\n    function perform(method, count) {\n        count = count || 1;\n        for (var j = 0; j < count; j++) {\n            if (reverse) {\n                for (var i = 3; i >= 0; --i)\n                    method(i);\n            } else {\n                for (var i = 0; i < 4; i++)\n                    method(i);\n            }\n        }\n    }\n\n    return function (k, d, ofs, e) {\n        reverse = e;\n        //  1. Initialize words R[0], ..., R[3] to contain the 64-bit\n        //     ciphertext value.\n        R = new Uint16Array(d.buffer, ofs || d.byteOffset, 4);\n        //  2. Expand the key, so that words K[0], ..., K[63] become\n        //     defined.\n        K = k;\n        //  3. Initialize j to zero (enc) j to 63 (dec).\n        j = e ? 63 : 0;\n        //  4. Perform five mixing rounds.\n        perform(mix, 5);\n        //  5. Perform one mashing round.\n        perform(mash);\n        //  6. Perform six mixing rounds.\n        perform(mix, 6);\n        //  7. Perform one mashing round.\n        perform(mash);\n        //  8. Perform five mixing rounds.\n        perform(mix, 5);\n    };\n} // </editor-fold>\n)();\n\n/**\n * Algorithm name GOST 28147-ECB<br><br>\n *\n * encryptECB (K, D) is D, encrypted with key k using GOST 28147/GOST R 34.13 in\n * \"prostaya zamena\" (Electronic Codebook, ECB) mode.\n * @memberOf GostCipher\n * @method encrypt\n * @instance\n * @param k {CryptoOperationData} 8x32 bit key\n * @param d {CryptoOperationData} 8 bits message\n * @return {CryptoOperationData} result\n */\nfunction encryptECB(k, d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var p = this.pad(byteArray(d)),\n            n = this.blockSize,\n            b = p.byteLength / n,\n            key = this.keySchedule(k);\n\n    for (var i = 0; i < b; i++)\n        this.process(key, p, n * i);\n\n    return p.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-ECB<br><br>\n *\n * decryptECB (K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 in\n * \"prostaya zamena\"  (Electronic Codebook, ECB) mode.\n *\n * @memberOf GostCipher\n * @method decrypt\n * @instance\n * @param k {CryptoOperationData} 8x32 bits key\n * @param d {CryptoOperationData} 8 bits message\n * @return {CryptoOperationData} result\n */\nfunction decryptECB(k, d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var p = cloneArray(d),\n            n = this.blockSize,\n            b = p.byteLength / n,\n            key = this.keySchedule(k, 1);\n\n    for (var i = 0; i < b; i++)\n        this.process(key, p, n * i, 1);\n\n    return this.unpad(p).buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CFB<br><br>\n *\n * encryptCFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13\n * in \"gammirovanie s obratnoj svyaziyu\" (Cipher Feedback, CFB) mode, and IV is\n * used as the initialization vector.\n *\n * @memberOf GostCipher\n * @method encrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv initial vector\n * @return {CryptoOperationData} result\n */\nfunction encryptCFB(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var s = new Uint8Array(iv || this.iv),\n            c = cloneArray(d),\n            m = s.length,\n            t = new Uint8Array(m),\n            b = this.shiftBits >> 3,\n            cb = c.length, r = cb % b, q = (cb - r) / b,\n            key = this.keySchedule(k);\n\n    for (var i = 0; i < q; i++) {\n\n        for (var j = 0; j < m; j++)\n            t[j] = s[j];\n\n        this.process(key, s);\n\n        for (var j = 0; j < b; j++)\n            c[i * b + j] ^= s[j];\n\n        for (var j = 0; j < m - b; j++)\n            s[j] = t[b + j];\n\n        for (var j = 0; j < b; j++)\n            s[m - b + j] = c[i * b + j];\n\n        k = this.keyMeshing(k, s, i, key);\n    }\n\n    if (r > 0) {\n        this.process(key, s);\n\n        for (var i = 0; i < r; i++)\n            c[q * b + i] ^= s[i];\n    }\n    return c.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CFB<br><br>\n *\n * decryptCFB (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13\n * in \"gammirovanie s obratnoj svyaziyu po shifrotekstu\" (Cipher Feedback, CFB) mode, and IV is\n * used as the initialization vector.\n *\n * @memberOf GostCipher\n * @method decrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv initial vector\n * @return {CryptoOperationData} result\n */\nfunction decryptCFB(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var s = new Uint8Array(iv || this.iv),\n            c = cloneArray(d),\n            m = s.length,\n            t = new Uint8Array(m),\n            b = this.shiftBits >> 3,\n            cb = c.length, r = cb % b, q = (cb - r) / b,\n            key = this.keySchedule(k);\n\n    for (var i = 0; i < q; i++) {\n\n        for (var j = 0; j < m; j++)\n            t[j] = s[j];\n\n        this.process(key, s);\n\n        for (var j = 0; j < b; j++) {\n            t[j] = c[i * b + j];\n            c[i * b + j] ^= s[j];\n        }\n\n        for (var j = 0; j < m - b; j++)\n            s[j] = t[b + j];\n\n        for (var j = 0; j < b; j++)\n            s[m - b + j] = t[j];\n\n        k = this.keyMeshing(k, s, i, key);\n    }\n\n    if (r > 0) {\n        this.process(key, s);\n\n        for (var i = 0; i < r; i++)\n            c[q * b + i] ^= s[i];\n    }\n    return c.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-OFB<br><br>\n *\n * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13\n * in \"gammirovanie s obratnoj svyaziyu po vyhodu\" (Output Feedback, OFB) mode, and IV is\n * used as the initialization vector.\n *\n * @memberOf GostCipher\n * @method encrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv 8x8 optional bits initial vector\n * @return {CryptoOperationData} result\n */\n/**\n * Algorithm name GOST 28147-OFB<br><br>\n *\n * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13\n * in \"gammirovanie s obratnoj svyaziyu po vyhodu\" (Output Feedback, OFB) mode, and IV is\n * used as the initialization vector.\n *\n * @memberOf GostCipher\n * @method decrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv initial vector\n * @return {CryptoOperationData} result\n */\nfunction processOFB(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var s = new Uint8Array(iv || this.iv),\n            c = cloneArray(d),\n            m = s.length,\n            t = new Uint8Array(m),\n            b = this.shiftBits >> 3,\n            p = new Uint8Array(b),\n            cb = c.length, r = cb % b, q = (cb - r) / b,\n            key = this.keySchedule(k);\n\n    for (var i = 0; i < q; i++) {\n\n        for (var j = 0; j < m; j++)\n            t[j] = s[j];\n\n        this.process(key, s);\n\n        for (var j = 0; j < b; j++)\n            p[j] = s[j];\n\n        for (var j = 0; j < b; j++)\n            c[i * b + j] ^= s[j];\n\n        for (var j = 0; j < m - b; j++)\n            s[j] = t[b + j];\n\n        for (var j = 0; j < b; j++)\n            s[m - b + j] = p[j];\n\n        k = this.keyMeshing(k, s, i, key);\n    }\n\n    if (r > 0) {\n        this.process(key, s);\n\n        for (var i = 0; i < r; i++)\n            c[q * b + i] ^= s[i];\n    }\n    return c.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CTR<br><br>\n *\n * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13\n * in \"gammirovanie\" (Counter Mode-CTR) mode, and IV is used as the\n * initialization vector.\n * @memberOf GostCipher\n * @method encrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv 8x8 optional bits initial vector\n * @return {CryptoOperationData} result\n */\n/**\n * Algorithm name GOST 28147-CTR<br><br>\n *\n * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13\n * in \"gammirovanie\" (Counter Mode-CTR) mode, and IV is used as the\n * initialization vector.\n * @memberOf GostCipher\n * @method decrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv initial vector\n * @return {CryptoOperationData} result\n */\nfunction processCTR89(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var s = new Uint8Array(iv || this.iv),\n            c = cloneArray(d),\n            b = this.blockSize,\n            t = new Int8Array(b),\n            cb = c.length, r = cb % b, q = (cb - r) / b,\n            key = this.keySchedule(k),\n            syn = new Int32Array(s.buffer);\n\n    this.process(key, s);\n\n    for (var i = 0; i < q; i++) {\n        syn[0] = (syn[0] + 0x1010101) & 0xffffffff;\n        // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff);\n        var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov\n        syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff);\n\n        for (var j = 0; j < b; j++)\n            t[j] = s[j];\n\n        this.process(key, syn);\n\n        for (var j = 0; j < b; j++)\n            c[i * b + j] ^= s[j];\n\n        for (var j = 0; j < b; j++)\n            s[j] = t[j];\n\n        k = this.keyMeshing(k, s, i, key);\n    }\n    if (r > 0) {\n        syn[0] = (syn[0] + 0x1010101) & 0xffffffff;\n        // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff);\n        var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov\n        syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff);\n\n        this.process(key, syn);\n\n        for (var i = 0; i < r; i++)\n            c[q * b + i] ^= s[i];\n    }\n    return c.buffer;\n} // </editor-fold>\n\nfunction processCTR15(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var c = cloneArray(d),\n            n = this.blockSize,\n            b = this.shiftBits >> 3,\n            cb = c.length, r = cb % b, q = (cb - r) / b,\n            s = new Uint8Array(n),\n            t = new Int32Array(n),\n            key = this.keySchedule(k);\n\n    s.set(iv || this.iv);\n    for (var i = 0; i < q; i++) {\n\n        for (var j = 0; j < n; j++)\n            t[j] = s[j];\n\n        this.process(key, s);\n\n        for (var j = 0; j < b; j++)\n            c[b * i + j] ^= s[j];\n\n        for (var j = 0; j < n; j++)\n            s[j] = t[j];\n\n        for (var j = n - 1; i >= 0; --i) {\n            if (s[j] > 0xfe) {\n                s[j] -= 0xfe;\n            } else {\n                s[j]++;\n                break;\n            }\n        }\n    }\n\n    if (r > 0) {\n        this.process(key, s);\n        for (var j = 0; j < r; j++)\n            c[b * q + j] ^= s[j];\n    }\n\n    return c.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CBC<br><br>\n *\n * encryptCBC (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13\n * in \"Prostaya zamena s zatsepleniem\" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization\n * vector.\n *\n * @memberOf GostCipher\n * @method encrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv initial vector\n * @return {CryptoOperationData} result\n */\nfunction encryptCBC(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var s = new Uint8Array(iv || this.iv),\n            n = this.blockSize,\n            m = s.length,\n            c = this.pad(byteArray(d)),\n            key = this.keySchedule(k);\n\n    for (var i = 0, b = c.length / n; i < b; i++) {\n\n        for (var j = 0; j < n; j++)\n            s[j] ^= c[i * n + j];\n\n        this.process(key, s);\n\n        for (var j = 0; j < n; j++)\n            c[i * n + j] = s[j];\n\n        if (m !== n) {\n            for (var j = 0; j < m - n; j++)\n                s[j] = s[n + j];\n\n            for (var j = 0; j < n; j++)\n                s[j + m - n] = c[i * n + j];\n        }\n\n        k = this.keyMeshing(k, s, i, key);\n    }\n\n    return c.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CBC<br><br>\n *\n * decryptCBC (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13\n * in \"Prostaya zamena s zatsepleniem\" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization\n * vector.\n *\n * @memberOf GostCipher\n * @method decrypt\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv initial vector\n * @return {CryptoOperationData} result\n */\nfunction decryptCBC(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var s = new Uint8Array(iv || this.iv),\n            n = this.blockSize,\n            m = s.length,\n            c = cloneArray(d),\n            next = new Uint8Array(n),\n            key = this.keySchedule(k, 1);\n\n    for (var i = 0, b = c.length / n; i < b; i++) {\n\n        for (var j = 0; j < n; j++)\n            next[j] = c[i * n + j];\n\n        this.process(key, c, i * n, 1);\n\n        for (var j = 0; j < n; j++)\n            c[i * n + j] ^= s[j];\n\n        if (m !== n) {\n            for (var j = 0; j < m - n; j++)\n                s[j] = s[n + j];\n        }\n\n        for (var j = 0; j < n; j++)\n            s[j + m - n] = next[j];\n\n        k = this.keyMeshing(k, s, i, key, 1);\n    }\n\n    return this.unpad(c).buffer;\n} // </editor-fold>\n\n/**\n * The generateKey method returns a new generated key.\n *\n * @memberOf GostCipher\n * @method generateKey\n * @instance\n * @return {CryptoOperationData} result\n */\n\nfunction generateKey() // <editor-fold defaultstate=\"collapsed\">\n{\n    // Simple generate 256 bit random seed\n    var k = new Uint8Array(this.keySize);\n    randomSeed(k);\n    return k.buffer;\n} // </editor-fold>\n\n\n/**\n * makeIMIT (K, D) is the 32-bit result of the GOST 28147/GOST R 34.13 in\n * \"imitovstavka\" (MAC) mode, used with D as plaintext, K as key and IV\n * as initialization vector.  Note that the standard specifies its use\n * in this mode only with an initialization vector of zero.\n *\n * @memberOf GostCipher\n * @method processMAC\n * @private\n * @instance\n * @param {Int32Array} key 8x32 bits key\n * @param {Int32Array} s 8x8 sum array\n * @param {Uint8Array} d 8 bits array with data\n * @return {Uint8Array} result\n */\nfunction processMAC89(key, s, d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var c = zeroPad.call(this, byteArray(d)),\n            n = this.blockSize,\n            q = c.length / n,\n            sBox = this.sBox,\n            sum = new Int32Array(s.buffer);\n\n    for (var i = 0; i < q; i++) {\n\n        for (var j = 0; j < n; j++)\n            s[j] ^= c[i * n + j];\n\n        for (var j = 0; j < 16; j++) // 1-16 steps\n            round(sBox, sum, key[j]);\n    }\n} // </editor-fold>\n\nfunction processKeyMAC15(s) // <editor-fold defaultstate=\"collapsed\">\n{\n    var t = 0, n = s.length;\n    for (var i = n - 1; i >= 0; --i) {\n        var t1 = s[i] >>> 7;\n        s[i] = (s[i] << 1) & 0xff | t;\n        t = t1;\n    }\n    if (t !== 0) {\n        if (n === 16)\n            s[15] ^= 0x87;\n        else\n            s[7] ^= 0x1b;\n    }\n} // </editor-fold>\n\nfunction processMAC15(key, s, d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = this.blockSize,\n            sBox = this.sBox, c = byteArray(d),\n            r = new Uint8Array(n);\n    // R\n    this.process(key, r);\n    // K1\n    processKeyMAC15(r);\n    if (d.byteLength % n !== 0) {\n        c = bitPad.call(this, byteArray(d));\n        // K2\n        processKeyMAC15(r);\n    }\n\n    for (var i = 0, q = c.length / n; i < q; i++) {\n\n        for (var j = 0; j < n; j++)\n            s[j] ^= c[i * n + j];\n\n        if (i === q - 1) {// Last block\n            for (var j = 0; j < n; j++)\n                s[j] ^= r[j];\n        }\n\n        this.process(key, s);\n    }\n} // </editor-fold>\n\n/**\n * signMAC (K, D, IV) is the 32-bit result of the GOST 28147/GOST R 34.13 in\n * \"imitovstavka\" (MAC) mode, used with D as plaintext, K as key and IV\n * as initialization vector.  Note that the standard specifies its use\n * in this mode only with an initialization vector of zero.\n *\n * @memberOf GostCipher\n * @method sign\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv initial vector\n * @return {CryptoOperationData} result\n */\nfunction signMAC(k, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var key = this.keySchedule(k),\n            s = new Uint8Array(iv || this.iv),\n            m = Math.ceil(this.macLength >> 3) || this.blockSize >> 1;\n\n    this.processMAC(key, s, d);\n\n    var mac = new Uint8Array(m); // mac size\n    mac.set(new Uint8Array(s.buffer, 0, m));\n    return mac.buffer;\n} // </editor-fold>\n\n/**\n * verifyMAC (K, M, D, IV) the 32-bit result verification of the GOST 28147/GOST R 34.13 in\n * \"imitovstavka\" (MAC) mode, used with D as plaintext, K as key and IV\n * as initialization vector.  Note that the standard specifies its use\n * in this mode only with an initialization vector of zero.\n *\n * @memberOf GostCipher\n * @method verify\n * @instance\n * @param {CryptoOperationData} k 8x32 bits key\n * @param {CryptoOperationData} m 8 bits array with signature\n * @param {CryptoOperationData} d 8 bits array with data\n * @param {CryptoOperationData} iv 8x8 optional bits initial vector\n * @return {boolen} MAC verified = true\n */\nfunction verifyMAC(k, m, d, iv) // <editor-fold defaultstate=\"collapsed\">\n{\n    var mac = new Uint8Array(signMAC.call(this, k, d, iv)),\n            test = byteArray(m);\n    if (mac.length !== test.length)\n        return false;\n    for (var i = 0, n = mac.length; i < n; i++)\n        if (mac[i] !== test[i])\n            return false;\n    return true;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-KW<br><br>\n *\n * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147/GOST R 34.13 KEK.\n * Ref. rfc4357 6.1 GOST 28147-89 Key Wrap\n * Note: This algorithm MUST NOT be used with a KEK produced by VKO GOST\n * R 34.10-94, because such a KEK is constant for every sender-recipient\n * pair.  Encrypting many different content encryption keys on the same\n * constant KEK may reveal that KEK.\n *\n * @memberOf GostCipher\n * @method wrapKey\n * @instance\n * @param {CryptoOperationData} kek Key encryption key\n * @param {CryptoOperationData} cek Content encryption key\n * @returns {CryptoOperationData} Encrypted cek\n */\nfunction wrapKeyGOST(kek, cek) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);\n    // 1) For a unique symmetric KEK, generate 8 octets at random and call\n    // the result UKM.  For a KEK, produced by VKO GOST R 34.10-2001, use\n    // the UKM that was used for key derivation.\n    if (!this.ukm)\n        throw new DataError('UKM must be defined');\n    var ukm = new Uint8Array(this.ukm);\n    // 2) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK).\n    // Call the result CEK_MAC.\n    var mac = signMAC.call(this, kek, cek, ukm);\n    // 3) Encrypt the CEK in ECB mode using the KEK.  Call the ciphertext CEK_ENC.\n    var enc = encryptECB.call(this, kek, cek);\n    // 4) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC).\n    var r = new Uint8Array(len);\n    r.set(new Uint8Array(enc), 0);\n    r.set(new Uint8Array(mac), k);\n    return r.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-KW<br><br>\n *\n *  This algorithm decrypts GOST 28147-89 CEK with a GOST 28147 KEK.\n *  Ref. rfc4357 6.2 GOST 28147-89 Key Unwrap\n *\n * @memberOf GostCipher\n * @method unwrapKey\n * @instance\n * @param {type} kek Key encryption key\n * @param {type} data Content encryption key\n * @return {CryptoOperationData} result\n */\nfunction unwrapKeyGOST(kek, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);\n    // 1) If the wrapped content-encryption key is not 44 octets, then error.\n    var d = buffer(data);\n    if (d.byteLength !== len)\n        throw new DataError('Wrapping key size must be ' + len + ' bytes');\n    // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, and CEK_MAC.\n    // UKM is the most significant (first) 8 octets. CEK_ENC is next 32 octets,\n    // and CEK_MAC is the least significant (last) 4 octets.\n    if (!this.ukm)\n        throw new DataError('UKM must be defined');\n    var ukm = new Uint8Array(this.ukm),\n            enc = new Uint8Array(d, 0, k),\n            mac = new Uint8Array(d, k, n >> 1);\n    // 3) Decrypt CEK_ENC in ECB mode using the KEK.  Call the output CEK.\n    var cek = decryptECB.call(this, kek, enc);\n    // 4) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK),\n    // compare the result with CEK_MAC.  If they are not equal, then error.\n    var check = verifyMAC.call(this, kek, mac, cek, ukm);\n    if (!check)\n        throw new DataError('Error verify MAC of wrapping key');\n    return cek;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CPKW<br><br>\n *\n * Given a random 64-bit UKM and a GOST 28147 key K, this algorithm\n * creates a new GOST 28147-89 key K(UKM).\n * Ref. rfc4357 6.3 CryptoPro KEK Diversification Algorithm\n *\n * @memberOf GostCipher\n * @method diversify\n * @instance\n * @private\n * @param {CryptoOperationData} kek Key encryption key\n * @param {CryptoOperationData} ukm Random generated value\n * @returns {CryptoOperationData} Diversified kek\n */\nfunction diversifyKEK(kek, ukm) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = this.blockSize;\n\n    // 1) Let K[0] = K;\n    var k = intArray(kek);\n    // 2) UKM is split into components a[i,j]:\n    //    UKM = a[0]|..|a[7] (a[i] - byte, a[i,0]..a[i,7] - it’s bits)\n    var a = [];\n    for (var i = 0; i < n; i++) {\n        a[i] = [];\n        for (var j = 0; j < 8; j++) {\n            a[i][j] = (ukm[i] >>> j) & 0x1;\n        }\n    }\n    // 3) Let i be 0.\n    // 4) K[1]..K[8] are calculated by repeating the following algorithm\n    //    eight times:\n    for (var i = 0; i < n; i++) {\n        //     A) K[i] is split into components k[i,j]:\n        //        K[i] = k[i,0]|k[i,1]|..|k[i,7] (k[i,j] - 32-bit integer)\n        //     B) Vector S[i] is calculated:\n        //        S[i] = ((a[i,0]*k[i,0] + ... + a[i,7]*k[i,7]) mod 2^32) |\n        //         (((~a[i,0])*k[i,0] + ... + (~a[i,7])*k[i,7]) mod 2^32);\n        var s = new Int32Array(2);\n        for (var j = 0; j < 8; j++) {\n            if (a[i][j])\n                s[0] = (s[0] + k[j]) & 0xffffffff;\n            else\n                s[1] = (s[1] + k[j]) & 0xffffffff;\n        }\n        //     C) K[i+1] = encryptCFB (S[i], K[i], K[i])\n        var iv = new Uint8Array(s.buffer);\n        k = new Int32Array(encryptCFB.call(this, k, k, iv));\n        //     D) i = i + 1\n    }\n    // 5) Let K(UKM) be K[8].\n    return k;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CPKW<br><br>\n *\n * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK.\n * It can be used with any KEK (e.g., produced by VKO GOST R 34.10-94 or\n * VKO GOST R 34.10-2001) because a unique UKM is used to diversify the KEK.\n * Ref. rfc4357 6.3  CryptoPro Key Wrap\n *\n * @memberOf GostCipher\n * @method wrapKey\n * @instance\n * @param {CryptoOperationData} kek Key encryption key\n * @param {CryptoOperationData} cek Content encryption key\n * @returns {CryptoOperationData} Encrypted cek\n */\nfunction wrapKeyCP(kek, cek) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);\n    // 1) For a unique symmetric KEK or a KEK produced by VKO GOST R\n    // 34.10-94, generate 8 octets at random.  Call the result UKM.  For\n    // a KEK, produced by VKO GOST R 34.10-2001, use the UKM that was\n    // used for key derivation.\n    if (!this.ukm)\n        throw new DataError('UKM must be defined');\n    var ukm = new Uint8Array(this.ukm);\n    // 2) Diversify KEK, using the CryptoPro KEK Diversification Algorithm,\n    // described in Section 6.5.  Call the result KEK(UKM).\n    var dek = diversifyKEK.call(this, kek, ukm);\n    // 3) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM),\n    // CEK).  Call the result CEK_MAC.\n    var mac = signMAC.call(this, dek, cek, ukm);\n    // 4) Encrypt CEK in ECB mode using KEK(UKM).  Call the ciphertext\n    // CEK_ENC.\n    var enc = encryptECB.call(this, dek, cek);\n    // 5) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC).\n    var r = new Uint8Array(len);\n    r.set(new Uint8Array(enc), 0);\n    r.set(new Uint8Array(mac), k);\n    return r.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CPKW<br><br>\n *\n * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK.\n * Ref. rfc4357 6.4 CryptoPro Key Unwrap\n *\n * @memberOf GostCipher\n * @method unwrapKey\n * @instance\n * @param {CryptoOperationData} kek Key encryption key\n * @param {CryptoOperationData} data Encrypted content encryption keu\n * @return {CryptoOperationData} result Decrypted content encryption keu\n */\nfunction unwrapKeyCP(kek, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = this.blockSize, k = this.keySize, len = k + (n >> 1);\n    // 1) If the wrapped content-encryption key is not 44 octets, then error.\n    var d = buffer(data);\n    if (d.byteLength !== len)\n        throw new DataError('Wrapping key size must be ' + len + ' bytes');\n    // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC,\n    // and CEK_MAC.  UKM is the most significant (first) 8 octets.\n    // CEK_ENC is next 32 octets, and CEK_MAC is the least significant\n    // (last) 4 octets.\n    if (!this.ukm)\n        throw new DataError('UKM must be defined');\n    var ukm = new Uint8Array(this.ukm),\n            enc = new Uint8Array(d, 0, k),\n            mac = new Uint8Array(d, k, n >> 1);\n    // 3) Diversify KEK using the CryptoPro KEK Diversification Algorithm,\n    // described in section 6.5.  Call the result KEK(UKM).\n    var dek = diversifyKEK.call(this, kek, ukm);\n    // 4) Decrypt CEK_ENC in ECB mode using KEK(UKM).  Call the output CEK.\n    var cek = decryptECB.call(this, dek, enc);\n    // 5) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM),\n    // CEK), compare the result with CEK_MAC.  If they are not equal,\n    // then it is an error.\n    var check = verifyMAC.call(this, dek, mac, cek, ukm);\n    if (!check)\n        throw new DataError('Error verify MAC of wrapping key');\n    return cek;\n} // </editor-fold>\n\n/**\n * SignalCom master key packing algorithm\n *\n * kek stored in 3 files - kek.opq, mk.db3, masks.db3\n * kek.opq - always 36 bytes length = 32 bytes encrypted kek + 4 bytes mac of decrypted kek\n * mk.db3 - 6 bytes header (1 byte magic code 0x22 + 1 byte count of masks + 4 bytes mac of\n * xor summarizing masks value) + attached masks\n * masks.db3 - detached masks.\n * Total length  of attached + detached masks = 32 bits * count of masks\n * Default value of count 8 = (7 attached + 1 detached). But really no reason for such\n * separation - all masks xor summarizing - order is not matter.\n * Content of file rand.opq can used as ukm. Don't forget change file content after using.\n *\n * For usb-token files has names:\n * a001 - mk.db3, b001 - masks.db3, c001 - kek.opq, d001 - rand.opq\n * For windows registry\n * 00000001 - mk.db3, 00000002 - masks.db3, 00000003 - key.opq, 00000004 - rand.opq,\n * 00000006 - keys\\00000001.key, 0000000A - certificate\n *\n * @memberOf GostCipher\n * @method packKey\n * @instance\n * @private\n * @param {CryptoOperationData} unpacked - clear main key 32 bytes\n * @param {CryptoOperationData} ukm - random vector for packing - 32 bytes * (count of masks - 1)\n * @returns {CryptoOperationData} packed master key - concatination of mk.db3 + masks.db3\n */\nfunction packKeySC(unpacked, ukm) // <editor-fold defaultstate=\"collapsed\">\n{\n    var m = this.blockSize >> 1, k = this.keySize;\n    var mcount = 8;\n    var key = new Uint8Array(buffer(unpacked));\n    if (key.byteLength !== k)\n        throw new DataError('Wrong cleartext size ' + key.byteLength + ' bytes');\n    // Check or generate UKM\n    ukm = ukm || this.ukm;\n    if (ukm) {\n        ukm = new Uint8Array(buffer(ukm));\n        if (ukm.byteLength > 0 && ukm.byteLength % k === 0)\n            mcount = ukm.byteLength / k + 1;\n        else\n            throw new DataError('Wrong rand size ' + ukm.byteLength + ' bytes');\n    } else\n        randomSeed(ukm = new Uint8Array((mcount - 1) * k));\n    // Output array\n    var d = new Uint8Array(mcount * k + m + 2), b = d.buffer;\n    // Calculate MAC\n    var zero32 = new Uint8Array(k);\n    var mac = signMAC.call(this, key, zero32);\n    d[0] = 0x22; // Magic code\n    d[1] = mcount; // Count of masks\n    d.set(new Uint8Array(mac), 2);\n    d.set(ukm, k + m + 2);\n    for (var i = 1; i < mcount; i++) {\n        var mask = new Uint8Array(b, 2 + m + k * i);\n        for (var j = 0; j < k; j++)\n            key[j] ^= mask[j];\n    }\n    d.set(key, m + 2);\n    return d.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-SCKW<br><br>\n *\n * SignalCom master key unpacking algorithm\n *\n * @memberOf GostCipher\n * @method unpackKey\n * @instance\n * @private\n * @param {CryptoOperationData} packed - concatination of mk.db3 + masks.db3\n * @returns {CryptoOperationData} unpacked master key\n */\nfunction unpackKeySC(packed) // <editor-fold defaultstate=\"collapsed\">\n{\n    var m = this.blockSize >> 1, k = this.keySize;\n    var b = buffer(packed);\n    // Unpack master key\n    var magic = new Uint8Array(b, 0, 1)[0];\n    if (magic !== 0x22)\n        throw new DataError('Invalid magic number');\n    var mcount = new Uint8Array(b, 1, 1)[0];\n    var mac = new Uint8Array(b, 2, m); // MAC for summarized mask\n    // Compute packKey xor summing for all masks\n    var key = new Uint8Array(k);\n    for (var i = 0; i < mcount; i++) {\n        var mask = new Uint8Array(b, 2 + m + k * i, k);\n        for (var j = 0; j < k; j++)\n            key[j] ^= mask[j];\n    }\n    // Test MAC for packKey with default sBox on zero 32 bytes array\n    var zero32 = new Uint8Array(k);\n    var test = verifyMAC.call(this, key, mac, zero32);\n    if (!test) {\n        // Try to use different sBoxes\n        var names = ['E-A', 'E-B', 'E-C', 'E-D', 'E-SC'];\n        for (var i = 0, n = names.length; i < n; i++) {\n            this.sBox = sBoxes[names[i]];\n            test = verifyMAC.call(this, key, mac, zero32);\n            if (test)\n                break;\n        }\n    }\n    if (!test)\n        throw new DataError('Invalid main key MAC');\n    return key.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-SCKW<br><br>\n *\n * SignalCom Key Wrapping algorithm\n *\n * @memberOf GostCipher\n * @method wrapKey\n * @instance\n * @param {CryptoOperationData} kek - clear kek or concatination of mk.db3 + masks.db3\n * @param {CryptoOperationData} cek - key for wrapping\n * @returns {CryptoOperationData} wrapped key - file kek.opq\n */\nfunction wrapKeySC(kek, cek) // <editor-fold defaultstate=\"collapsed\">\n{\n    var m = this.blockSize >> 1, n = this.keySize;\n    var k = buffer(kek);\n    var c = buffer(cek);\n    if (k.byteLength !== n)\n        k = unpackKeySC.call(this, k);\n    var enc = encryptECB.call(this, k, c);\n    var mac = signMAC.call(this, k, c);\n    var d = new Uint8Array(m + n);\n    d.set(new Uint8Array(enc), 0);\n    d.set(new Uint8Array(mac), n);\n    return d.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-SCKW<br><br>\n *\n * SignalCom Key UnWrapping algorithm\n *\n * @memberOf GostCipher\n * @method unwrapKey\n * @instance\n * @param {CryptoOperationData} kek - concatination of files mk.db3 + masks.db3 or clear kek\n * @param {CryptoOperationData} cek - wrapping key - file kek.opq\n * @return {CryptoOperationData} result\n */\nfunction unwrapKeySC(kek, cek) // <editor-fold defaultstate=\"collapsed\">\n{\n    var m = this.blockSize >> 1, n = this.keySize;\n    var k = buffer(kek);\n    var c = buffer(cek);\n    if (k.byteLength !== n)\n        k = unpackKeySC.call(this, k);\n    var enc = new Uint8Array(c, 0, n); // Encrypted kek\n    var mac = new Uint8Array(c, n, m); // MAC for clear kek\n    var d = decryptECB.call(this, k, enc);\n    if (!verifyMAC.call(this, k, mac, d))\n        throw new DataError('Invalid key MAC');\n    return d;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-SCKW<br><br>\n *\n * SignalCom master key generation for wrapping\n *\n * @memberOf GostCipher\n * @method generateKey\n * @instance\n * @return {CryptoOperationData} result\n */\nfunction generateWrappingKeySC() // <editor-fold defaultstate=\"collapsed\">\n{\n    return packKeySC.call(this, generateKey.call(this));\n} // </editor-fold>\n\nfunction maskKey(mask, key, inverse, keySize) // <editor-fold defaultstate=\"collapsed\">\n{\n    var k = keySize / 4,\n            m32 = new Int32Array(buffer(mask)),\n            k32 = new Int32Array(buffer(key)),\n            r32 = new Int32Array(k);\n    if (inverse)\n        for (var i = 0; i < k; i++)\n            r32[i] = (k32[i] + m32[i]) & 0xffffffff;\n    else\n        for (var i = 0; i < k; i++)\n            r32[i] = (k32[i] - m32[i]) & 0xffffffff;\n    return r32.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-MASK<br><br>\n *\n * This algorithm wrap key mask\n *\n * @memberOf GostCipher\n * @method wrapKey\n * @instance\n * @param {CryptoOperationData} mask The mask\n * @param {CryptoOperationData} key The key\n * @returns {CryptoOperationData} The masked key\n */\nfunction wrapKeyMask(mask, key) // <editor-fold defaultstate=\"collapsed\">\n{\n    return maskKey(mask, key, this.procreator === 'VN', this.keySize);\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CPKW<br><br>\n *\n * This algorithm unwrap key mask\n *\n * @memberOf GostCipher\n * @method unwrapKey\n * @instance\n * @param {CryptoOperationData} mask The mask\n * @param {CryptoOperationData} key The masked key\n * @return {CryptoOperationData} result The key\n */\nfunction unwrapKeyMask(mask, key) // <editor-fold defaultstate=\"collapsed\">\n{\n    return maskKey(mask, key, this.procreator !== 'VN', this.keySize);\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-CPKM<br><br>\n *\n * Key meshing in according to rfc4357 2.3.2. CryptoPro Key Meshing\n *\n * @memberOf GostCipher\n * @method keyMeshing\n * @instance\n * @private\n * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key\n * @param {Uint8Array} s 8x8 bit sync (iv)\n * @param {Integer} i block index\n * @param {Int32Array} key 8x32 bit key schedule\n * @param {boolean} e true - decrypt\n * @returns CryptoOperationData next 8x8 bit key\n */\nfunction keyMeshingCP(k, s, i, key, e) // <editor-fold defaultstate=\"collapsed\">\n{\n    if ((i + 1) * this.blockSize % 1024 === 0) { // every 1024 octets\n        // K[i+1] = decryptECB (K[i], C);\n        k = decryptECB.call(this, k, C);\n        // IV0[i+1] = encryptECB (K[i+1],IVn[i])\n        s.set(new Uint8Array(encryptECB.call(this, k, s)));\n        // restore key schedule\n        key.set(this.keySchedule(k, e));\n    }\n    return k;\n} // </editor-fold>\n\n/**\n *  Null Key Meshing in according to rfc4357 2.3.1\n *\n * @memberOf GostCipher\n * @method keyMeshing\n * @instance\n * @private\n * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key\n */\nfunction noKeyMeshing(k) // <editor-fold defaultstate=\"collapsed\">\n{\n    return k;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-NoPadding<br><br>\n *\n * No padding.\n *\n * @memberOf GostCipher\n * @method padding\n * @instance\n * @private\n * @param {Uint8Array} d array with source data\n * @returns {Uint8Array} result\n */\nfunction noPad(d) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Uint8Array(d);\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-PKCS5Padding<br><br>\n *\n *  PKCS#5 padding: 8-x remaining bytes are filled with the value of\n *  8-x.  If there’s no incomplete block, one extra block filled with\n *  value 8 is added\n *\n * @memberOf GostCipher\n * @method padding\n * @instance\n * @private\n * @param {Uint8Array} d array with source data\n * @returns {Uint8Array} result\n */\nfunction pkcs5Pad(d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = d.byteLength,\n            nb = this.blockSize,\n            q = nb - n % nb,\n            m = Math.ceil((n + 1) / nb) * nb,\n            r = new Uint8Array(m);\n    r.set(d);\n    for (var i = n; i < m; i++)\n        r[i] = q;\n    return r;\n} // </editor-fold>\n\nfunction pkcs5Unpad(d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var m = d.byteLength,\n            nb = this.blockSize,\n            q = d[m - 1],\n            n = m - q;\n    if (q > nb)\n        throw DataError('Invalid padding');\n    var r = new Uint8Array(n);\n    if (n > 0)\n        r.set(new Uint8Array(d.buffer, 0, n));\n    return r;\n} // </editor-fold>\n\n\n/**\n * Algorithm name GOST 28147-ZeroPadding<br><br>\n *\n * Zero padding: 8-x remaining bytes are filled with zero\n *\n * @memberOf GostCipher\n * @method padding\n * @instance\n * @private\n * @param {Uint8Array} d array with source data\n * @returns {Uint8Array} result\n */\nfunction zeroPad(d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = d.byteLength,\n            nb = this.blockSize,\n            m = Math.ceil(n / nb) * nb,\n            r = new Uint8Array(m);\n    r.set(d);\n    for (var i = n; i < m; i++)\n        r[i] = 0;\n    return r;\n} // </editor-fold>\n\n\n/**\n * Algorithm name GOST 28147-BitPadding<br><br>\n *\n * Bit padding: P* = P || 1 || 000...0 If there’s no incomplete block,\n * one extra block filled with 1 || 000...0\n *\n * @memberOf GostCipher\n * @method padding\n * @instance\n * @private\n * @param {Uint8Array} d array with source data\n * @returns {Uint8Array} result\n */\nfunction bitPad(d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = d.byteLength,\n            nb = this.blockSize,\n            m = Math.ceil((n + 1) / nb) * nb,\n            r = new Uint8Array(m);\n    r.set(d);\n    r[n] = 1;\n    for (var i = n + 1; i < m; i++)\n        r[i] = 0;\n    return r;\n} // </editor-fold>\n\nfunction bitUnpad(d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var m = d.byteLength,\n            n = m;\n    while (n > 1 && d[n - 1] === 0)\n        n--;\n    if (d[n - 1] !== 1)\n        throw DataError('Invalid padding');\n    n--;\n    var r = new Uint8Array(n);\n    if (n > 0)\n        r.set(new Uint8Array(d.buffer, 0, n));\n    return r;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST 28147-RandomPadding<br><br>\n *\n * Random padding: 8-x remaining bytes of the last block are set to\n * random.\n *\n * @memberOf GostCipher\n * @method padding\n * @instance\n * @private\n * @param {Uint8Array} d array with source data\n * @returns {Uint8Array} result\n */\nfunction randomPad(d) // <editor-fold defaultstate=\"collapsed\">\n{\n    var n = d.byteLength,\n            nb = this.blockSize,\n            q = nb - n % nb,\n            m = Math.ceil(n / nb) * nb,\n            r = new Uint8Array(m), e = new Uint8Array(r.buffer, n, q);\n    r.set(d);\n    randomSeed(e);\n    return r;\n} // </editor-fold>\n\n/**\n * GOST 28147-89 Encryption Algorithm<br><br>\n *\n * References {@link http://tools.ietf.org/html/rfc5830}<br><br>\n *\n * When keys and initialization vectors are converted to/from byte arrays,\n * little-endian byte order is assumed.<br><br>\n *\n * Normalized algorithm identifier common parameters:\n *\n *  <ul>\n *      <li><b>name</b> Algorithm name 'GOST 28147' or 'GOST R 34.12'</li>\n *      <li><b>version</b> Algorithm version, number\n *          <ul>\n *              <li><b>1989</b> Current version of standard</li>\n *              <li><b>2015</b> New draft version of standard</li>\n *          </ul>\n *      </li>\n *      <li><b>length</b> Block length\n *          <ul>\n *              <li><b>64</b> 64 bits length (default)</li>\n *              <li><b>128</b> 128 bits length (only for version 2015)</li>\n *          </ul>\n *      </li>\n *      <li><b>mode</b> Algorithm mode, string\n *          <ul>\n *              <li><b>ES</b> Encryption mode (default)</li>\n *              <li><b>MAC</b> \"imitovstavka\" (MAC) mode</li>\n *              <li><b>KW</b> Key wrapping mode</li>\n *          </ul>\n *      </li>\n *      <li><b>sBox</b> Paramset sBox for GOST 28147-89, string. Used only if version = 1989</li>\n *  </ul>\n *\n * Supported algorithms, modes and parameters:\n *\n *  <ul>\n *      <li>Encript/Decrypt mode (ES)\n *          <ul>\n *              <li><b>block</b> Block mode, string. Default ECB</li>\n *              <li><b>keyMeshing</b> Key meshing mode, string. Default NO</li>\n *              <li><b>padding</b> Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others</li>\n *              <li><b>iv</b> {@link CryptoOperationData} Initial vector with length of block. Default - zero block</li>\n *          </ul>\n *      </li>\n *      <li>Sign/Verify mode (MAC)\n *          <ul>\n *              <li><b>macLength</b> Length of mac in bits (default - 32 bits)</li>\n *              <li><b>iv</b> {@link CryptoOperationData} Initial vector with length of block. Default - zero block</li>\n *          </ul>\n *      </li>\n *      <li>Wrap/Unwrap key mode (KW)\n *          <ul>\n *              <li><b>keyWrapping</b> Mode of keywrapping, string. Default NO - standard GOST key wrapping</li>\n *              <li><b>ukm</b> {@link CryptoOperationData} User key material. Default - random generated value</li>\n *          </ul>\n *      </li>\n *  </ul>\n *\n * Supported paramters values:\n *\n *  <ul>\n *      <li>Block modes (parameter 'block')\n *          <ul>\n *              <li><b>ECB</b> \"prostaya zamena\" (ECB) mode (default)</li>\n *              <li><b>CFB</b> \"gammirovanie s obratnoj svyaziyu po shifrotekstu\" (CFB) mode</li>\n *              <li><b>OFB</b> \"gammirovanie s obratnoj svyaziyu po vyhodu\" (OFB) mode</li>\n *              <li><b>CTR</b> \"gammirovanie\" (counter) mode</li>\n *              <li><b>CBC</b> Cipher-Block-Chaining (CBC) mode</li>\n *          </ul>\n *      </li>\n *      <li>Key meshing modes (parameter 'keyMeshing')\n *          <ul>\n *              <li><b>NO</b> No key wrapping (default)</li>\n *              <li><b>CP</b> CryptoPor Key key meshing</li>\n *          </ul>\n *      </li>\n *      <li>Padding modes (parameter 'padding')\n *          <ul>\n *              <li><b>NO</b> No padding only for CFB, OFB and CTR modes</li>\n *              <li><b>PKCS5</b> PKCS#5 padding mode</li>\n *              <li><b>ZERO</b> Zero bits padding mode</li>\n *              <li><b>RANDOM</b> Random bits padding mode</li>\n *              <li><b>BIT</b> One bit padding mode</li>\n *          </ul>\n *      </li>\n *      <li>Wrapping key modes (parameter 'keyWrapping')\n *          <ul>\n *              <li><b>NO</b> Ref. rfc4357 6.1 GOST 28147-89 Key wrapping</li>\n *              <li><b>CP</b> CryptoPro Key wrapping mode</li>\n *              <li><b>SC</b> SignalCom Key wrapping mode</li>\n *          </ul>\n *      </li>\n *  </ul>\n *\n * @class GostCipher\n * @param {AlgorithmIndentifier} algorithm WebCryptoAPI algorithm identifier\n */\nfunction GostCipher(algorithm) // <editor-fold defaultstate=\"collapsed\">\n{\n    // Check little endian support\n    if (!littleEndian)\n        throw new NotSupportedError('Big endian platform not supported');\n    algorithm = algorithm || {};\n    this.keySize = 32;\n    this.blockLength = algorithm.length || 64;\n    this.blockSize = this.blockLength >> 3;\n\n    this.name = (algorithm.name || (algorithm.version === 1 ? 'RC2' :\n            algorithm.version === 1989 ? 'GOST 28147' : 'GOST R 34.12')) +\n            (algorithm.version > 4 ? '-' + ((algorithm.version || 1989) % 100) : '') + '-' +\n            (this.blockLength === 64 ? '' : this.blockLength + '-') +\n            ((algorithm.mode === 'MAC') ? 'MAC-' + (algorithm.macLength || this.blockLength >> 1) :\n                    (algorithm.mode === 'KW' || algorithm.keyWrapping) ?\n                    ((algorithm.keyWrapping || 'NO') !== 'NO' ? algorithm.keyWrapping : '') + 'KW' :\n                    (algorithm.block || 'ECB') + ((algorithm.block === 'CFB' || algorithm.block === 'OFB' ||\n                    (algorithm.block === 'CTR' && algorithm.version === 2015)) &&\n                    algorithm?.shiftBits !== this.blockLength ? '-' + algorithm.shiftBits : '') +\n                    (algorithm.padding ? '-' + (algorithm.padding || (algorithm.block === 'CTR' ||\n                            algorithm.block === 'CFB' || algorithm.block === 'OFB' ? 'NO' : 'ZERO')) + 'PADDING' : '') +\n                    ((algorithm.keyMeshing || 'NO') !== 'NO' ? '-CPKEYMESHING' : '')) +\n            (algorithm.procreator ? '/' + algorithm.procreator : '') +\n            (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : '');\n\n    // Algorithm procreator\n    this.procreator = algorithm.procreator;\n\n    switch (algorithm.version || 1989) {\n        case 1:\n            this.process = processRC2;\n            this.keySchedule = keyScheduleRC2;\n            this.blockLength = 64;\n            this.effectiveLength = algorithm.length || 32;\n            this.keySize = 8 * Math.ceil(this.effectiveLength / 8); // Max 128\n            this.blockSize = this.blockLength >> 3;\n            break;\n        case 2015:\n            this.version = 2015;\n            if (this.blockLength === 64) {\n                this.process = process15;\n                this.keySchedule = keySchedule15;\n            } else if (this.blockLength === 128) {\n                this.process = process128;\n                this.keySchedule = keySchedule128;\n            } else\n                throw new DataError('Invalid block length');\n            this.processMAC = processMAC15;\n            break;\n        case 1989:\n            this.version = 1989;\n            this.process = process89;\n            this.processMAC = processMAC89;\n            this.keySchedule = keySchedule89;\n            if (this.blockLength !== 64)\n                throw new DataError('Invalid block length');\n            break;\n        default:\n            throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported');\n    }\n\n    switch (algorithm.mode || (algorithm.keyWrapping && 'KW') || 'ES') {\n\n        case 'ES':\n            switch (algorithm.block || 'ECB') {\n                case 'ECB':\n                    this.encrypt = encryptECB;\n                    this.decrypt = decryptECB;\n                    break;\n                case 'CTR':\n                    if (this.version === 1989) {\n                        this.encrypt = processCTR89;\n                        this.decrypt = processCTR89;\n                    } else {\n                        this.encrypt = processCTR15;\n                        this.decrypt = processCTR15;\n                        this.shiftBits = algorithm.shiftBits || this.blockLength;\n                    }\n                    break\n                case 'CBC':\n                    this.encrypt = encryptCBC;\n                    this.decrypt = decryptCBC;\n                    break\n                case 'CFB':\n                    this.encrypt = encryptCFB;\n                    this.decrypt = decryptCFB;\n                    this.shiftBits = algorithm.shiftBits || this.blockLength;\n                    break;\n                case 'OFB':\n                    this.encrypt = processOFB;\n                    this.decrypt = processOFB;\n                    this.shiftBits = algorithm.shiftBits || this.blockLength;\n                    break;\n                default:\n                    throw new NotSupportedError('Block mode ' + algorithm.block + ' not supported');\n            }\n            switch (algorithm.keyMeshing) {\n                case 'CP':\n                    this.keyMeshing = keyMeshingCP;\n                    break;\n                default:\n                    this.keyMeshing = noKeyMeshing;\n            }\n            if (this.encrypt === encryptECB || this.encrypt === encryptCBC) {\n                switch (algorithm.padding) {\n                    case 'PKCS5P':\n                        this.pad = pkcs5Pad;\n                        this.unpad = pkcs5Unpad;\n                        break;\n                    case 'RANDOM':\n                        this.pad = randomPad;\n                        this.unpad = noPad;\n                        break;\n                    case 'BIT':\n                        this.pad = bitPad;\n                        this.unpad = bitUnpad;\n                        break;\n                    default:\n                        this.pad = zeroPad;\n                        this.unpad = noPad;\n                }\n            } else {\n                this.pad = noPad;\n                this.unpad = noPad;\n            }\n            this.generateKey = generateKey;\n            break;\n        case 'MAC':\n            this.sign = signMAC;\n            this.verify = verifyMAC;\n            this.generateKey = generateKey;\n            this.macLength = algorithm.macLength || (this.blockLength >> 1);\n            this.pad = noPad;\n            this.unpad = noPad;\n            this.keyMeshing = noKeyMeshing;\n            break;\n        case 'KW':\n            this.pad = noPad;\n            this.unpad = noPad;\n            this.keyMeshing = noKeyMeshing;\n            switch (algorithm.keyWrapping) {\n                case 'CP':\n                    this.wrapKey = wrapKeyCP;\n                    this.unwrapKey = unwrapKeyCP;\n                    this.generateKey = generateKey;\n                    this.shiftBits = algorithm.shiftBits || this.blockLength;\n                    break;\n                case 'SC':\n                    this.wrapKey = wrapKeySC;\n                    this.unwrapKey = unwrapKeySC;\n                    this.generateKey = generateWrappingKeySC;\n                    break;\n                default:\n                    this.wrapKey = wrapKeyGOST;\n                    this.unwrapKey = unwrapKeyGOST;\n                    this.generateKey = generateKey;\n            }\n            break;\n        case 'MASK':\n            this.wrapKey = wrapKeyMask;\n            this.unwrapKey = unwrapKeyMask;\n            this.generateKey = generateKey;\n            break;\n        default:\n            throw new NotSupportedError('Mode ' + algorithm.mode + ' not supported');\n    }\n\n    // Define sBox parameter\n    var sBox = algorithm.sBox, sBoxName;\n    if (!sBox)\n        sBox = this.version === 2015 ? sBoxes['E-Z'] : this.procreator === 'SC' ? sBoxes['E-SC'] : sBoxes['E-A'];\n    else if (typeof sBox === 'string') {\n        sBoxName = sBox.toUpperCase();\n        sBox = sBoxes[sBoxName];\n        if (!sBox)\n            throw new SyntaxError('Unknown sBox name: ' + algorithm.sBox);\n    } else if (!sBox.length || sBox.length !== sBoxes['E-Z'].length)\n        throw new SyntaxError('Length of sBox must be ' + sBoxes['E-Z'].length);\n    this.sBox = sBox;\n    // Initial vector\n    if (algorithm.iv) {\n        this.iv = new Uint8Array(algorithm.iv);\n        if (this.iv.byteLength !== this.blockSize && this.version === 1989)\n            throw new SyntaxError('Length of iv must be ' + this.blockLength + ' bits');\n        else if (this.iv.byteLength !== this.blockSize >> 1 && this.encrypt === processCTR15)\n            throw new SyntaxError('Length of iv must be ' + this.blockLength >> 1 + ' bits');\n        else if (this.iv.byteLength % this.blockSize !== 0 && this.encrypt !== processCTR15)\n            throw new SyntaxError('Length of iv must be a multiple of ' + this.blockLength + ' bits');\n    } else\n        this.iv = this.blockLength === 128 ? defaultIV128 : defaultIV;\n    // User key material\n    if (algorithm.ukm) {\n        this.ukm = new Uint8Array(algorithm.ukm);\n        if (this.ukm.byteLength * 8 !== this.blockLength)\n            throw new SyntaxError('Length of ukm must be ' + this.blockLength + ' bits');\n    }\n} // </editor-fold>\n\nexport default GostCipher;\n"
  },
  {
    "path": "src/core/vendor/gost/gostCoding.mjs",
    "content": "/**\n * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM\n * version 1.76\n * 2014-2016, Rudolf Nickolaev. All rights reserved.\n *\n * Exported for CyberChef by mshwed [m@ttshwed.com]\n */\n\n/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * THIS SOfTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES Of MERCHANTABILITY AND fITNESS fOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * fOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT Of SUBSTITUTE GOODS OR\n * SERVICES; LOSS Of USE, DATA, OR PROfITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY Of LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT Of THE USE\n * Of THIS SOfTWARE, EVEN If ADVISED Of THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\nimport gostCrypto from './gostCrypto.mjs';\n\n/**\n * The Coding interface provides string converting methods: Base64, Hex,\n * Int16, Chars, BER and PEM\n * @class GostCoding\n *\n */ // <editor-fold defaultstate=\"collapsed\">\nvar root = {};\nvar DataError = Error;\nvar CryptoOperationData = ArrayBuffer;\nvar Date = Date;\n\nfunction buffer(d) {\n    if (d instanceof CryptoOperationData)\n        return d;\n    else if (d && d?.buffer instanceof CryptoOperationData)\n        return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ?\n                d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer;\n    else\n        throw new DataError('CryptoOperationData required');\n} // </editor-fold>\n\nfunction GostCoding() {\n}\n\n/**\n * BASE64 conversion\n *\n * @class GostCoding.Base64\n */\nvar Base64 = {// <editor-fold defaultstate=\"collapsed\">\n    /**\n     * Base64.decode convert BASE64 string s to CryptoOperationData\n     *\n     * @memberOf GostCoding.Base64\n     * @param {String} s BASE64 encoded string value\n     * @returns {CryptoOperationData} Binary decoded data\n     */\n    decode: function (s) {\n        s = s.replace(/[^A-Za-z0-9\\+\\/]/g, '');\n        var n = s.length,\n                k = n * 3 + 1 >> 2, r = new Uint8Array(k);\n\n        for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) {\n            m4 = i & 3;\n            var c = s.charCodeAt(i);\n\n            c = c > 64 && c < 91 ?\n                    c - 65 : c > 96 && c < 123 ?\n                    c - 71 : c > 47 && c < 58 ?\n                    c + 4 : c === 43 ?\n                    62 : c === 47 ?\n                    63 : 0;\n\n            u24 |= c << 18 - 6 * m4;\n            if (m4 === 3 || n - i === 1) {\n                for (m3 = 0; m3 < 3 && j < k; m3++, j++) {\n                    r[j] = u24 >>> (16 >>> m3 & 24) & 255;\n                }\n                u24 = 0;\n\n            }\n        }\n        return r.buffer;\n    },\n    /**\n     * Base64.encode(data) convert CryptoOperationData data to BASE64 string\n     *\n     * @memberOf GostCoding.Base64\n     * @param {CryptoOperationData} data Bynary data for encoding\n     * @returns {String} BASE64 encoded data\n     */\n    encode: function (data) {\n        var slen = 8, d = new Uint8Array(buffer(data));\n        var m3 = 2, s = '';\n        for (var n = d.length, u24 = 0, i = 0; i < n; i++) {\n            m3 = i % 3;\n            if (i > 0 && (i * 4 / 3) % (12 * slen) === 0)\n                s += '\\r\\n';\n            u24 |= d[i] << (16 >>> m3 & 24);\n            if (m3 === 2 || n - i === 1) {\n                for (var j = 18; j >= 0; j -= 6) {\n                    var c = u24 >>> j & 63;\n                    c = c < 26 ? c + 65 : c < 52 ? c + 71 : c < 62 ? c - 4 :\n                            c === 62 ? 43 : c === 63 ? 47 : 65;\n                    s += String.fromCharCode(c);\n                }\n                u24 = 0;\n            }\n        }\n        return s.substr(0, s.length - 2 + m3) + (m3 === 2 ? '' : m3 === 1 ? '=' : '==');\n    } // </editor-fold>\n};\n\n/**\n * BASE64 conversion\n *\n * @memberOf GostCoding\n * @insnance\n * @type GostCoding.Base64\n */\nGostCoding.prototype.Base64 = Base64;\n\n/**\n * Text string conversion <br>\n * Methods support charsets: ascii, win1251, utf8, utf16 (ucs2, unicode), utf32 (ucs4)\n *\n * @class GostCoding.Chars\n */\nvar Chars = (function () { // <editor-fold defaultstate=\"collapsed\">\n\n    var _win1251_ = {\n        0x402: 0x80, 0x403: 0x81, 0x201A: 0x82, 0x453: 0x83, 0x201E: 0x84, 0x2026: 0x85, 0x2020: 0x86, 0x2021: 0x87,\n        0x20AC: 0x88, 0x2030: 0x89, 0x409: 0x8A, 0x2039: 0x8B, 0x40A: 0x8C, 0x40C: 0x8D, 0x40B: 0x8E, 0x40f: 0x8f,\n        0x452: 0x90, 0x2018: 0x91, 0x2019: 0x92, 0x201C: 0x93, 0x201D: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97,\n        0x2122: 0x99, 0x459: 0x9A, 0x203A: 0x9B, 0x45A: 0x9C, 0x45C: 0x9D, 0x45B: 0x9E, 0x45f: 0x9f,\n        0xA0: 0xA0, 0x40E: 0xA1, 0x45E: 0xA2, 0x408: 0xA3, 0xA4: 0xA4, 0x490: 0xA5, 0xA6: 0xA6, 0xA7: 0xA7,\n        0x401: 0xA8, 0xA9: 0xA9, 0x404: 0xAA, 0xAB: 0xAB, 0xAC: 0xAC, 0xAD: 0xAD, 0xAE: 0xAE, 0x407: 0xAf,\n        0xB0: 0xB0, 0xB1: 0xB1, 0x406: 0xB2, 0x456: 0xB3, 0x491: 0xB4, 0xB5: 0xB5, 0xB6: 0xB6, 0xB7: 0xB7,\n        0x451: 0xB8, 0x2116: 0xB9, 0x454: 0xBA, 0xBB: 0xBB, 0x458: 0xBC, 0x405: 0xBD, 0x455: 0xBE, 0x457: 0xBf\n    };\n    var _win1251back_ = {};\n    for (var from in _win1251_) {\n        var to = _win1251_[from];\n        _win1251back_[to] = from;\n    }\n\n    return {\n        /**\n         * Chars.decode(s, charset) convert string s with defined charset to CryptoOperationData\n         *\n         * @memberOf GostCoding.Chars\n         * @param {string} s Javascript string\n         * @param {string} charset Charset, default 'win1251'\n         * @returns {CryptoOperationData} Decoded binary data\n         */\n        decode: function (s, charset) {\n            charset = (charset || 'win1251').toLowerCase().replace('-', '');\n            var r = [];\n            for (var i = 0, j = s.length; i < j; i++) {\n                var c = s.charCodeAt(i);\n                if (charset === 'utf8') {\n                    if (c < 0x80) {\n                        r.push(c);\n                    } else if (c < 0x800) {\n                        r.push(0xc0 + (c >>> 6));\n                        r.push(0x80 + (c & 63));\n                    } else if (c < 0x10000) {\n                        r.push(0xe0 + (c >>> 12));\n                        r.push(0x80 + (c >>> 6 & 63));\n                        r.push(0x80 + (c & 63));\n                    } else if (c < 0x200000) {\n                        r.push(0xf0 + (c >>> 18));\n                        r.push(0x80 + (c >>> 12 & 63));\n                        r.push(0x80 + (c >>> 6 & 63));\n                        r.push(0x80 + (c & 63));\n                    } else if (c < 0x4000000) {\n                        r.push(0xf8 + (c >>> 24));\n                        r.push(0x80 + (c >>> 18 & 63));\n                        r.push(0x80 + (c >>> 12 & 63));\n                        r.push(0x80 + (c >>> 6 & 63));\n                        r.push(0x80 + (c & 63));\n                    } else {\n                        r.push(0xfc + (c >>> 30));\n                        r.push(0x80 + (c >>> 24 & 63));\n                        r.push(0x80 + (c >>> 18 & 63));\n                        r.push(0x80 + (c >>> 12 & 63));\n                        r.push(0x80 + (c >>> 6 & 63));\n                        r.push(0x80 + (c & 63));\n                    }\n                } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') {\n                    if (c < 0xD800 || (c >= 0xE000 && c <= 0x10000)) {\n                        r.push(c >>> 8);\n                        r.push(c & 0xff);\n                    } else if (c >= 0x10000 && c < 0x110000) {\n                        c -= 0x10000;\n                        var first = ((0xffc00 & c) >> 10) + 0xD800;\n                        var second = (0x3ff & c) + 0xDC00;\n                        r.push(first >>> 8);\n                        r.push(first & 0xff);\n                        r.push(second >>> 8);\n                        r.push(second & 0xff);\n                    }\n                } else if (charset === 'utf32' || charset === 'ucs4') {\n                    r.push(c >>> 24 & 0xff);\n                    r.push(c >>> 16 & 0xff);\n                    r.push(c >>> 8 & 0xff);\n                    r.push(c & 0xff);\n                } else if (charset === 'win1251') {\n                    if (c >= 0x80) {\n                        if (c >= 0x410 && c < 0x450) // А..Яа..я\n                            c -= 0x350;\n                        else\n                            c = _win1251_[c] || 0;\n                    }\n                    r.push(c);\n                } else\n                    r.push(c & 0xff);\n            }\n            return new Uint8Array(r).buffer;\n        },\n        /**\n         * Chars.encode(data, charset) convert CryptoOperationData data to string with defined charset\n         *\n         * @memberOf GostCoding.Chars\n         * @param {CryptoOperationData} data Binary data\n         * @param {string} charset Charset, default win1251\n         * @returns {string} Encoded javascript string\n         */\n        encode: function (data, charset) {\n            charset = (charset || 'win1251').toLowerCase().replace('-', '');\n            var r = [], d = new Uint8Array(buffer(data));\n            for (var i = 0, n = d.length; i < n; i++) {\n                var c = d[i];\n                if (charset === 'utf8') {\n                    c = c >= 0xfc && c < 0xfe && i + 5 < n ? // six bytes\n                            (c - 0xfc) * 1073741824 + (d[++i] - 0x80 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80\n                            : c >> 0xf8 && c < 0xfc && i + 4 < n ? // five bytes\n                            (c - 0xf8 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80\n                            : c >> 0xf0 && c < 0xf8 && i + 3 < n ? // four bytes\n                            (c - 0xf0 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80\n                            : c >= 0xe0 && c < 0xf0 && i + 2 < n ? // three bytes\n                            (c - 0xe0 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80\n                            : c >= 0xc0 && c < 0xe0 && i + 1 < n ? // two bytes\n                            (c - 0xc0 << 6) + d[++i] - 0x80\n                            : c; // one byte\n                } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') {\n                    c = (c << 8) + d[++i];\n                    if (c >= 0xD800 && c < 0xE000) {\n                        var first = (c - 0xD800) << 10;\n                        c = d[++i];\n                        c = (c << 8) + d[++i];\n                        var second = c - 0xDC00;\n                        c = first + second + 0x10000;\n                    }\n                } else if (charset === 'utf32' || charset === 'ucs4') {\n                    c = (c << 8) + d[++i];\n                    c = (c << 8) + d[++i];\n                    c = (c << 8) + d[++i];\n                } else if (charset === 'win1251') {\n                    if (c >= 0x80) {\n                        if (c >= 0xC0 && c < 0x100)\n                            c += 0x350; // А..Яа..я\n                        else\n                            c = _win1251back_[c] || 0;\n                    }\n                }\n                r.push(String.fromCharCode(c));\n            }\n            return r.join('');\n        }\n    }; // </editor-fold>\n})();\n\n/**\n * Text string conversion\n *\n * @memberOf GostCoding\n * @insnance\n * @type GostCoding.Chars\n */\nGostCoding.prototype.Chars = Chars;\n\n/**\n * HEX conversion\n *\n * @class GostCoding.Hex\n */\nvar Hex = {// <editor-fold defaultstate=\"collapsed\">\n    /**\n     * Hex.decode(s, endean) convert HEX string s to CryptoOperationData in endean mode\n     *\n     * @memberOf GostCoding.Hex\n     * @param {string} s Hex encoded string\n     * @param {boolean} endean Little or Big Endean, default Little\n     * @returns {CryptoOperationData} Decoded binary data\n     */\n    decode: function (s, endean) {\n        s = s.replace(/[^A-Fa-f0-9]/g, '');\n        var n = Math.ceil(s.length / 2), r = new Uint8Array(n);\n        s = (s.length % 2 > 0 ? '0' : '') + s;\n        if (endean && ((typeof endean !== 'string') ||\n                (endean.toLowerCase().indexOf('little') < 0)))\n            for (var i = 0; i < n; i++)\n                r[i] = parseInt(s.substr((n - i - 1) * 2, 2), 16);\n        else\n            for (var i = 0; i < n; i++)\n                r[i] = parseInt(s.substr(i * 2, 2), 16);\n        return r.buffer;\n    },\n    /**\n     * Hex.encode(data, endean) convert CryptoOperationData data to HEX string in endean mode\n     *\n     * @memberOf GostCoding.Hex\n     * @param {CryptoOperationData} data Binary data\n     * @param {boolean} endean Little/Big Endean, default Little\n     * @returns {string} Hex decoded string\n     */\n    encode: function (data, endean) {\n        var s = [], d = new Uint8Array(buffer(data)), n = d.length;\n        if (endean && ((typeof endean !== 'string') ||\n                (endean.toLowerCase().indexOf('little') < 0)))\n            for (var i = 0; i < n; i++) {\n                var j = n - i - 1;\n                s[j] = (j > 0 && j % 32 === 0 ? '\\r\\n' : '') +\n                        ('00' + d[i].toString(16)).slice(-2);\n            }\n        else\n            for (var i = 0; i < n; i++)\n                s[i] = (i > 0 && i % 32 === 0 ? '\\r\\n' : '') +\n                        ('00' + d[i].toString(16)).slice(-2);\n        return s.join('');\n    } // </editor-fold>\n};\n\n/**\n *  HEX conversion\n * @memberOf GostCoding\n * @insnance\n * @type GostCoding.Hex\n */\nGostCoding.prototype.Hex = Hex;\n\n/**\n * String hex-encoded integer conversion\n *\n * @class GostCoding.Int16\n */\nvar Int16 = {// <editor-fold defaultstate=\"collapsed\">\n    /**\n     * Int16.decode(s) convert hex big insteger s to CryptoOperationData\n     *\n     * @memberOf GostCoding.Int16\n     * @param {string} s Int16 string\n     * @returns {CryptoOperationData} Decoded binary data\n     */\n    decode: function (s) {\n        s = (s || '').replace(/[^\\-A-Fa-f0-9]/g, '');\n        if (s.length === 0)\n            s = '0';\n        // Signature\n        var neg = false;\n        if (s.charAt(0) === '-') {\n            neg = true;\n            s = s.substring(1);\n        }\n        // Align 2 chars\n        while (s.charAt(0) === '0' && s.length > 1)\n            s = s.substring(1);\n        s = (s.length % 2 > 0 ? '0' : '') + s;\n        // Padding for singanuture\n        // '800000' - 'ffffff' - for positive\n        // '800001' - 'ffffff' - for negative\n        if ((!neg && !/^[0-7]/.test(s)) ||\n                (neg && !/^[0-7]|8[0]+$/.test(s)))\n            s = '00' + s;\n        // Convert hex\n        var n = s.length / 2, r = new Uint8Array(n), t = 0;\n        for (var i = n - 1; i >= 0; --i) {\n            var c = parseInt(s.substr(i * 2, 2), 16);\n            if (neg && (c + t > 0)) {\n                c = 256 - c - t;\n                t = 1;\n            }\n            r[i] = c;\n        }\n        return r.buffer;\n    },\n    /**\n     * Int16.encode(data) convert CryptoOperationData data to big integer hex string\n     *\n     * @memberOf GostCoding.Int16\n     * @param {CryptoOperationData} data Binary data\n     * @returns {string} Int16 encoded string\n     */\n    encode: function (data) {\n        var d = new Uint8Array(buffer(data)), n = d.length;\n        if (d.length === 0)\n            return '0x00';\n        var s = [], neg = d[0] > 0x7f, t = 0;\n        for (var i = n - 1; i >= 0; --i) {\n            var v = d[i];\n            if (neg && (v + t > 0)) {\n                v = 256 - v - t;\n                t = 1;\n            }\n            s[i] = ('00' + v.toString(16)).slice(-2);\n        }\n        s = s.join('');\n        while (s.charAt(0) === '0')\n            s = s.substring(1);\n        return (neg ? '-' : '') + '0x' + s;\n    } // </editor-fold>\n};\n\n/**\n * String hex-encoded integer conversion\n * @memberOf GostCoding\n * @insnance\n * @type GostCoding.Int16\n */\nGostCoding.prototype.Int16 = Int16;\n\n/**\n * BER, DER, CER conversion\n *\n * @class GostCoding.BER\n */\nvar BER = (function () { // <editor-fold defaultstate=\"collapsed\">\n\n    // Predefenition block\n    function encodeBER(source, format, onlyContent) {\n        // Correct primitive type\n        var object = source.object;\n        if (object === undefined)\n            object = source;\n\n        // Determinate tagClass\n        var tagClass = source.tagClass = source.tagClass || 0; // Universial default\n\n        // Determinate tagNumber. Use only for Universal class\n        if (tagClass === 0) {\n            var tagNumber = source.tagNumber;\n            if (typeof tagNumber === 'undefined') {\n                if (typeof object === 'string') {\n                    if (object === '')   // NULL\n                        tagNumber = 0x05;\n                    else if (/^\\-?0x[0-9a-fA-F]+$/.test(object)) // INTEGER\n                        tagNumber = 0x02;\n                    else if (/^(\\d+\\.)+\\d+$/.test(object)) // OID\n                        tagNumber = 0x06;\n                    else if (/^[01]+$/.test(object)) // BIT STRING\n                        tagNumber = 0x03;\n                    else if (/^(true|false)$/.test(object)) // BOOLEAN\n                        tagNumber = 0x01;\n                    else if (/^[0-9a-fA-F]+$/.test(object)) // OCTET STRING\n                        tagNumber = 0x04;\n                    else\n                        tagNumber = 0x13; // Printable string (later can be changed to UTF8String)\n                } else if (typeof object === 'number') { // INTEGER\n                    tagNumber = 0x02;\n                } else if (typeof object === 'boolean') { // BOOLEAN\n                    tagNumber = 0x01;\n                } else if (object instanceof Array) { // SEQUENCE\n                    tagNumber = 0x10;\n                } else if (object instanceof Date) { // GeneralizedTime\n                    tagNumber = 0x18;\n                } else if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) {\n                    tagNumber = 0x04;\n                } else\n                    throw new DataError('Unrecognized type for ' + object);\n            }\n        }\n\n        // Determinate constructed\n        var tagConstructed = source.tagConstructed;\n        if (typeof tagConstructed === 'undefined')\n            tagConstructed = source.tagConstructed = object instanceof Array;\n\n        // Create content\n        var content;\n        if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { // Direct\n            content = new Uint8Array(buffer(object));\n            if (tagNumber === 0x03) { // BITSTRING\n                // Set unused bits\n                var a = new Uint8Array(buffer(content));\n                content = new Uint8Array(a.length + 1);\n                content[0] = 0; // No unused bits\n                content.set(a, 1);\n            }\n        } else if (tagConstructed) { // Sub items coding\n            if (object instanceof Array) {\n                var bytelen = 0, ba = [], offset = 0;\n                for (var i = 0, n = object.length; i < n; i++) {\n                    ba[i] = encodeBER(object[i], format);\n                    bytelen += ba[i].length;\n                }\n                if (tagNumber === 0x11)\n                    ba.sort(function (a, b) { // Sort order for SET components\n                        for (var i = 0, n = Math.min(a.length, b.length); i < n; i++) {\n                            var r = a[i] - b[i];\n                            if (r !== 0)\n                                return r;\n                        }\n                        return a.length - b.length;\n                    });\n                if (format === 'CER') { // final for CER 00 00\n                    ba[n] = new Uint8Array(2);\n                    bytelen += 2;\n                }\n                content = new Uint8Array(bytelen);\n                for (var i = 0, n = ba.length; i < n; i++) {\n                    content.set(ba[i], offset);\n                    offset = offset + ba[i].length;\n                }\n            } else\n                throw new DataError('Constracted block can\\'t be primitive');\n        } else {\n            switch (tagNumber) {\n                // 0x00: // EOC\n                case 0x01: // BOOLEAN\n                    content = new Uint8Array(1);\n                    content[0] = object ? 0xff : 0;\n                    break;\n                case 0x02: // INTEGER\n                case 0x0a: // ENUMIRATED\n                    content = Int16.decode(\n                            typeof object === 'number' ? object.toString(16) : object);\n                    break;\n                case 0x03: // BIT STRING\n                    if (typeof object === 'string') {\n                        var unusedBits = 7 - (object.length + 7) % 8;\n                        var n = Math.ceil(object.length / 8);\n                        content = new Uint8Array(n + 1);\n                        content[0] = unusedBits;\n                        for (var i = 0; i < n; i++) {\n                            var c = 0;\n                            for (var j = 0; j < 8; j++) {\n                                var k = i * 8 + j;\n                                c = (c << 1) + (k < object.length ? (object.charAt(k) === '1' ? 1 : 0) : 0);\n                            }\n                            content[i + 1] = c;\n                        }\n                    }\n                    break;\n                case 0x04:\n                    content = Hex.decode(\n                            typeof object === 'number' ? object.toString(16) : object);\n                    break;\n                    // case 0x05: // NULL\n                case 0x06: // OBJECT IDENTIFIER\n                    var a = object.match(/\\d+/g), r = [];\n                    for (var i = 1; i < a.length; i++) {\n                        var n = +a[i], r1 = [];\n                        if (i === 1)\n                            n = n + a[0] * 40;\n                        do {\n                            r1.push(n & 0x7F);\n                            n = n >>> 7;\n                        } while (n);\n                        // reverse order\n                        for (j = r1.length - 1; j >= 0; --j)\n                            r.push(r1[j] + (j === 0 ? 0x00 : 0x80));\n                    }\n                    content = new Uint8Array(r);\n                    break;\n                    // case 0x07: // ObjectDescriptor\n                    // case 0x08: // EXTERNAL\n                    // case 0x09: // REAL\n                    // case 0x0A: // ENUMERATED\n                    // case 0x0B: // EMBEDDED PDV\n                case 0x0C: // UTF8String\n                    content = Chars.decode(object, 'utf8');\n                    break;\n                    // case 0x10: // SEQUENCE\n                    // case 0x11: // SET\n                case 0x12: // NumericString\n                case 0x16: // IA5String // ASCII\n                case 0x13: // PrintableString // ASCII subset\n                case 0x14: // TeletexString // aka T61String\n                case 0x15: // VideotexString\n                case 0x19: // GraphicString\n                case 0x1A: // VisibleString // ASCII subset\n                case 0x1B: // GeneralString\n                    // Reflect on character encoding\n                    for (var i = 0, n = object.length; i < n; i++)\n                        if (object.charCodeAt(i) > 255)\n                            tagNumber = 0x0C;\n                    if (tagNumber === 0x0C)\n                        content = Chars.decode(object, 'utf8');\n                    else\n                        content = Chars.decode(object, 'ascii');\n                    break;\n                case 0x17: // UTCTime\n                case 0x18: // GeneralizedTime\n                    var result = object.original;\n                    if (!result) {\n                        var date = new Date(object);\n                        date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); // to UTC\n                        var ms = tagNumber === 0x18 ? date.getMilliseconds().toString() : ''; // Milliseconds, remove trailing zeros\n                        while (ms.length > 0 && ms.charAt(ms.length - 1) === '0')\n                            ms = ms.substring(0, ms.length - 1);\n                        if (ms.length > 0)\n                            ms = '.' + ms;\n                        result = (tagNumber === 0x17 ? date.getYear().toString().slice(-2) : date.getFullYear().toString()) +\n                                ('00' + (date.getMonth() + 1)).slice(-2) +\n                                ('00' + date.getDate()).slice(-2) +\n                                ('00' + date.getHours()).slice(-2) +\n                                ('00' + date.getMinutes()).slice(-2) +\n                                ('00' + date.getSeconds()).slice(-2) + ms + 'Z';\n                    }\n                    content = Chars.decode(result, 'ascii');\n                    break;\n                case 0x1C: // UniversalString\n                    content = Chars.decode(object, 'utf32');\n                    break;\n                case 0x1E: // BMPString\n                    content = Chars.decode(object, 'utf16');\n                    break;\n            }\n        }\n\n        if (!content)\n            content = new Uint8Array(0);\n        if (content instanceof CryptoOperationData)\n            content = new Uint8Array(content);\n\n        if (!tagConstructed && format === 'CER') {\n            // Encoding CER-form for string types\n            var k;\n            switch (tagNumber) {\n                case 0x03: // BIT_STRING\n                    k = 1; // ingnore unused bit for bit string\n                case 0x04: // OCTET_STRING\n                case 0x0C: // UTF8String\n                case 0x12: // NumericString\n                case 0x13: // PrintableString\n                case 0x14: // TeletexString\n                case 0x15: // VideotexString\n                case 0x16: // IA5String\n                case 0x19: // GraphicString\n                case 0x1A: // VisibleString\n                case 0x1B: // GeneralString\n                case 0x1C: // UniversalString\n                case 0x1E: // BMPString\n                    k = k || 0;\n                    // Split content on 1000 octet len parts\n                    var size = 1000;\n                    var bytelen = 0, ba = [], offset = 0;\n                    for (var i = k, n = content.length; i < n; i += size - k) {\n                        ba[i] = encodeBER({\n                            object: new Unit8Array(content.buffer, i, Math.min(size - k, n - i)),\n                            tagNumber: tagNumber,\n                            tagClass: 0,\n                            tagConstructed: false\n                        }, format);\n                        bytelen += ba[i].length;\n                    }\n                    ba[n] = new Uint8Array(2); // final for CER 00 00\n                    bytelen += 2;\n                    content = new Uint8Array(bytelen);\n                    for (var i = 0, n = ba.length; i < n; i++) {\n                        content.set(ba[i], offset);\n                        offset = offset + ba[i].length;\n                    }\n            }\n        }\n\n        // Restore tagNumber for all classes\n        if (tagClass === 0)\n            source.tagNumber = tagNumber;\n        else\n            source.tagNumber = tagNumber = source.tagNumber || 0;\n        source.content = content;\n\n        if (onlyContent)\n            return content;\n\n        // Create header\n        // tagNumber\n        var ha = [], first = tagClass === 3 ? 0xC0 : tagClass === 2 ? 0x80 :\n                tagClass === 1 ? 0x40 : 0x00;\n        if (tagConstructed)\n            first |= 0x20;\n        if (tagNumber < 0x1F) {\n            first |= tagNumber & 0x1F;\n            ha.push(first);\n        } else {\n            first |= 0x1F;\n            ha.push(first);\n            var n = tagNumber, ha1 = [];\n            do {\n                ha1.push(n & 0x7F);\n                n = n >>> 7;\n            } while (n)\n            // reverse order\n            for (var j = ha1.length - 1; j >= 0; --j)\n                ha.push(ha1[j] + (j === 0 ? 0x00 : 0x80));\n        }\n        // Length\n        if (tagConstructed && format === 'CER') {\n            ha.push(0x80);\n        } else {\n            var len = content.length;\n            if (len > 0x7F) {\n                var l2 = len, ha2 = [];\n                do {\n                    ha2.push(l2 & 0xff);\n                    l2 = l2 >>> 8;\n                } while (l2);\n                ha.push(ha2.length + 0x80); // reverse order\n                for (var j = ha2.length - 1; j >= 0; --j)\n                    ha.push(ha2[j]);\n            } else {\n                // simple len\n                ha.push(len);\n            }\n        }\n        var header = source.header = new Uint8Array(ha);\n\n        // Result - complete buffer\n        var block = new Uint8Array(header.length + content.length);\n        block.set(header, 0);\n        block.set(content, header.length);\n        return block;\n    }\n\n    function decodeBER(source, offset) {\n\n        // start pos\n        var pos = offset || 0, start = pos;\n        var tagNumber, tagClass, tagConstructed,\n                content, header, buffer, sub, len;\n\n        if (source.object) {\n            // Ready from source\n            tagNumber = source.tagNumber;\n            tagClass = source.tagClass;\n            tagConstructed = source.tagConstructed;\n            content = source.content;\n            header = source.header;\n            buffer = source.object instanceof CryptoOperationData ?\n                    new Uint8Array(source.object) : null;\n            sub = source.object instanceof Array ? source.object : null;\n            len = buffer && buffer.length || null;\n        } else {\n            // Decode header\n            var d = source;\n\n            // Read tag\n            var buf = d[pos++];\n            tagNumber = buf & 0x1f;\n            tagClass = buf >> 6;\n            tagConstructed = (buf & 0x20) !== 0;\n            if (tagNumber === 0x1f) { // long tag\n                tagNumber = 0;\n                do {\n                    if (tagNumber > 0x1fffffffffff80)\n                        throw new DataError('Convertor not supported tag number more then (2^53 - 1) at position ' + offset);\n                    buf = d[pos++];\n                    tagNumber = (tagNumber << 7) + (buf & 0x7f);\n                } while (buf & 0x80);\n            }\n\n            // Read len\n            buf = d[pos++];\n            len = buf & 0x7f;\n            if (len !== buf) {\n                if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways\n                    throw new DataError('Length over 48 bits not supported at position ' + offset);\n                if (len === 0)\n                    len = null; // undefined\n                else {\n                    buf = 0;\n                    for (var i = 0; i < len; ++i)\n                        buf = (buf << 8) + d[pos++];\n                    len = buf;\n                }\n            }\n\n            start = pos;\n            sub = null;\n\n            if (tagConstructed) {\n                // must have valid content\n                sub = [];\n                if (len !== null) {\n                    // definite length\n                    var end = start + len;\n                    while (pos < end) {\n                        var s = decodeBER(d, pos);\n                        sub.push(s);\n                        pos += s.header.length + s.content.length;\n                    }\n                    if (pos !== end)\n                        throw new DataError('Content size is not correct for container starting at offset ' + start);\n                } else {\n                    // undefined length\n                    try {\n                        for (; ; ) {\n                            var s = decodeBER(d, pos);\n                            pos += s.header.length + s.content.length;\n                            if (s.tagClass === 0x00 && s.tagNumber === 0x00)\n                                break;\n                            sub.push(s);\n                        }\n                        len = pos - start;\n                    } catch (e) {\n                        throw new DataError('Exception ' + e + ' while decoding undefined length content at offset ' + start);\n                    }\n                }\n            }\n\n            // Header and content\n            header = new Uint8Array(d.buffer, offset, start - offset);\n            content = new Uint8Array(d.buffer, start, len);\n            buffer = content;\n        }\n\n        // Constructed types - check for string concationation\n        if (sub !== null && tagClass === 0) {\n            var k;\n            switch (tagNumber) {\n                case 0x03: // BIT_STRING\n                    k = 1; // ingnore unused bit for bit string\n                case 0x04: // OCTET_STRING\n                case 0x0C: // UTF8String\n                case 0x12: // NumericString\n                case 0x13: // PrintableString\n                case 0x14: // TeletexString\n                case 0x15: // VideotexString\n                case 0x16: // IA5String\n                case 0x19: // GraphicString\n                case 0x1A: // VisibleString\n                case 0x1B: // GeneralString\n                case 0x1C: // UniversalString\n                case 0x1E: // BMPString\n                    k = k || 0;\n                    // Concatination\n                    if (sub.length === 0)\n                        throw new DataError('No constructed encoding content of string type at offset ' + start);\n                    len = k;\n                    for (var i = 0, n = sub.length; i < n; i++) {\n                        var s = sub[i];\n                        if (s.tagClass !== tagClass || s.tagNumber !== tagNumber || s.tagConstructed)\n                            throw new DataError('Invalid constructed encoding of string type at offset ' + start);\n                        len += s.content.length - k;\n                    }\n                    buffer = new Uint8Array(len);\n                    for (var i = 0, n = sub.length, j = k; i < n; i++) {\n                        var s = sub[i];\n                        if (k > 0)\n                            buffer.set(s.content.subarray(1), j);\n                        else\n                            buffer.set(s.content, j);\n                        j += s.content.length - k;\n                    }\n                    tagConstructed = false; // follow not required\n                    sub = null;\n                    break;\n            }\n        }\n        // Primitive types\n        var object = '';\n        if (sub === null) {\n            if (len === null)\n                throw new DataError('Invalid tag with undefined length at offset ' + start);\n\n            if (tagClass === 0) {\n                switch (tagNumber) {\n                    case 0x01: // BOOLEAN\n                        object = buffer[0] !== 0;\n                        break;\n                    case 0x02: // INTEGER\n                    case 0x0a: // ENUMIRATED\n                        if (len > 6) {\n                            object = Int16.encode(buffer);\n                        } else {\n                            var v = buffer[0];\n                            if (buffer[0] > 0x7f)\n                                v = v - 256;\n                            for (var i = 1; i < len; i++)\n                                v = v * 256 + buffer[i];\n                            object = v;\n                        }\n                        break;\n                    case 0x03: // BIT_STRING\n                        if (len > 5) { // Content buffer\n                            object = new Uint8Array(buffer.subarray(1)).buffer;\n                        } else { // Max bit mask only for 32 bit\n                            var unusedBit = buffer[0],\n                                    skip = unusedBit, s = [];\n                            for (var i = len - 1; i >= 1; --i) {\n                                var b = buffer[i];\n                                for (var j = skip; j < 8; ++j)\n                                    s.push((b >> j) & 1 ? '1' : '0');\n                                skip = 0;\n                            }\n                            object = s.reverse().join('');\n                        }\n                        break;\n                    case 0x04: // OCTET_STRING\n                        object = new Uint8Array(buffer).buffer;\n                        break;\n                        //  case 0x05: // NULL\n                    case 0x06: // OBJECT_IDENTIFIER\n                        var s = '',\n                                n = 0,\n                                bits = 0;\n                        for (var i = 0; i < len; ++i) {\n                            var v = buffer[i];\n                            n = (n << 7) + (v & 0x7F);\n                            bits += 7;\n                            if (!(v & 0x80)) { // finished\n                                if (s === '') {\n                                    var m = n < 80 ? n < 40 ? 0 : 1 : 2;\n                                    s = m + \".\" + (n - m * 40);\n                                } else\n                                    s += \".\" + n.toString();\n                                n = 0;\n                                bits = 0;\n                            }\n                        }\n                        if (bits > 0)\n                            throw new DataError('Incompleted OID at offset ' + start);\n                        object = s;\n                        break;\n                        //case 0x07: // ObjectDescriptor\n                        //case 0x08: // EXTERNAL\n                        //case 0x09: // REAL\n                        //case 0x0A: // ENUMERATED\n                        //case 0x0B: // EMBEDDED_PDV\n                    case 0x10: // SEQUENCE\n                    case 0x11: // SET\n                        object = [];\n                        break;\n                    case 0x0C: // UTF8String\n                        object = Chars.encode(buffer, 'utf8');\n                        break;\n                    case 0x12: // NumericString\n                    case 0x13: // PrintableString\n                    case 0x14: // TeletexString\n                    case 0x15: // VideotexString\n                    case 0x16: // IA5String\n                    case 0x19: // GraphicString\n                    case 0x1A: // VisibleString\n                    case 0x1B: // GeneralString\n                        object = Chars.encode(buffer, 'ascii');\n                        break;\n                    case 0x1C: // UniversalString\n                        object = Chars.encode(buffer, 'utf32');\n                        break;\n                    case 0x1E: // BMPString\n                        object = Chars.encode(buffer, 'utf16');\n                        break;\n                    case 0x17: // UTCTime\n                    case 0x18: // GeneralizedTime\n                        var shortYear = tagNumber === 0x17;\n                        var s = Chars.encode(buffer, 'ascii'),\n                                m = (shortYear ?\n                                        /^(\\d\\d)(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])([01]\\d|2[0-3])(?:([0-5]\\d)(?:([0-5]\\d)(?:[.,](\\d{1,3}))?)?)?(Z|[-+](?:[0]\\d|1[0-2])([0-5]\\d)?)?$/ :\n                                        /^(\\d\\d\\d\\d)(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])([01]\\d|2[0-3])(?:([0-5]\\d)(?:([0-5]\\d)(?:[.,](\\d{1,3}))?)?)?(Z|[-+](?:[0]\\d|1[0-2])([0-5]\\d)?)?$/).exec(s);\n                        if (!m)\n                            throw new DataError('Unrecognized time format \"' + s + '\" at offset ' + start);\n                        if (shortYear) {\n                            // Where YY is greater than or equal to 50, the year SHALL be interpreted as 19YY; and\n                            // Where YY is less than 50, the year SHALL be interpreted as 20YY\n                            m[1] = +m[1];\n                            m[1] += (m[1] < 50) ? 2000 : 1900;\n                        }\n                        var dt = new Date(m[1], +m[2] - 1, +m[3], +(m[4] || '0'), +(m[5] || '0'), +(m[6] || '0'), +(m[7] || '0')),\n                                tz = dt.getTimezoneOffset();\n                        if (m[8] || tagNumber === 0x17) {\n                            if (m[8].toUpperCase() !== 'Z' && m[9]) {\n                                tz = tz + parseInt(m[9]);\n                            }\n                            dt.setMinutes(dt.getMinutes() - tz);\n                        }\n                        dt.original = s;\n                        object = dt;\n                        break;\n                }\n            } else // OCTET_STRING\n                object = new Uint8Array(buffer).buffer;\n        } else\n            object = sub;\n\n        // result\n        return {\n            tagConstructed: tagConstructed,\n            tagClass: tagClass,\n            tagNumber: tagNumber,\n            header: header,\n            content: content,\n            object: object\n        };\n    }\n\n    return {\n        /**\n         * BER.decode(object, format) convert javascript object to ASN.1 format CryptoOperationData<br><br>\n         * If object has members tagNumber, tagClass and tagConstructed\n         * it is clear define encoding rules. Else method use defaul rules:\n         * <ul>\n         *   <li>Empty string or null - NULL</li>\n         *   <li>String starts with '0x' and has 0-9 and a-f characters - INTEGER</li>\n         *   <li>String like d.d.d.d (d - set of digits) - OBJECT IDENTIFIER</li>\n         *   <li>String with characters 0 and 1 - BIT STRING</li>\n         *   <li>Strings 'true' or 'false' - BOOLEAN</li>\n         *   <li>String has only 0-9 and a-f characters - OCTET STRING</li>\n         *   <li>String has only characters with code 0-255 - PrintableString</li>\n         *   <li>Other strings - UTF8String</li>\n         *   <li>Number - INTEGER</li>\n         *   <li>Date - GeneralizedTime</li>\n         *   <li>Boolean - SEQUENCE</li>\n         *   <li>CryptoOperationData - OCTET STRING</li>\n         * </ul>\n         * SEQUENCE or SET arrays recursively encoded for each item.<br>\n         * OCTET STRING and BIT STRING can presents as array with one item.\n         * It means encapsulates encoding for child element.<br>\n         *\n         * If CONTEXT or APPLICATION classes item presents as array with one\n         * item we use EXPLICIT encoding for element, else IMPLICIT encoding.<br>\n         *\n         * @memberOf GostCoding.BER\n         * @param {Object} object Object to encoding\n         * @param {string} format Encoding rule: 'DER' or 'CER', default 'DER'\n         * @param {boolean} onlyContent Encode content only, without header\n         * @returns {CryptoOperationData} BER encoded data\n         */\n        encode: function (object, format, onlyContent) {\n            return encodeBER(object, format, onlyContent).buffer;\n        },\n        /**\n         * BER.encode(data) convert ASN.1 format CryptoOperationData data to javascript object<br><br>\n         *\n         * Conversion rules to javascript object:\n         *  <ul>\n         *      <li>BOOLEAN - Boolean object</li>\n         *      <li>INTEGER, ENUMIRATED - Integer object if len <= 6 (48 bits) else Int16 encoded string</li>\n         *      <li>BIT STRING - Integer object if len <= 5 (w/o unsedBit octet - 32 bits) else String like '10111100' or  Array with one item in case of incapsulates encoding</li>\n         *      <li>OCTET STRING - Hex encoded string or Array with one item in case of incapsulates encoding</li>\n         *      <li>OBJECT IDENTIFIER - String with object identifier</li>\n         *      <li>SEQUENCE, SET - Array of encoded items</li>\n         *      <li>UTF8String, NumericString, PrintableString, TeletexString, VideotexString,\n         *          IA5String, GraphicString, VisibleString, GeneralString, UniversalString,\n         *          BMPString - encoded String</li>\n         *      <li>UTCTime, GeneralizedTime - Date</li>\n         *  </ul>\n         * @memberOf GostCoding.BER\n         * @param {(CryptoOperationData|GostCoding.BER)} data Binary data to decode\n         * @returns {Object} Javascript object with result of decoding\n         */\n        decode: function (data) {\n            return decodeBER(data.object ? data : new Uint8Array(buffer(data)), 0);\n        }\n    }; // </editor-fold>\n})();\n\n/**\n * BER, DER, CER conversion\n * @memberOf GostCoding\n * @insnance\n * @type GostCoding.BER\n */\nGostCoding.prototype.BER = BER;\n\n/**\n * PEM conversion\n * @class GostCoding.PEM\n */\nvar PEM = {// <editor-fold defaultstate=\"collapsed\">\n    /**\n     * PEM.encode(data, name) encode CryptoOperationData to PEM format with name label\n     *\n     * @memberOf GostCoding.PEM\n     * @param {(Object|CryptoOperationData)} data Java script object or BER-encoded binary data\n     * @param {string} name Name of PEM object: 'certificate', 'private key' etc.\n     * @returns {string} Encoded object\n     */\n    encode: function (data, name) {\n        return (name ? '-----BEGIN ' + name.toUpperCase() + '-----\\r\\n' : '') +\n                Base64.encode(data instanceof CryptoOperationData ? data : BER.encode(data)) +\n                (name ? '\\r\\n-----END ' + name.toUpperCase() + '-----' : '');\n    },\n    /**\n     * PEM.decode(s, name, deep) decode PEM format s labeled name to CryptoOperationData or javascript object in according to deep parameter\n     *\n     * @memberOf GostCoding.PEM\n     * @param {string} s PEM encoded string\n     * @param {string} name Name of PEM object: 'certificate', 'private key' etc.\n     * @param {boolean} deep If true method do BER-decoding, else only BASE64 decoding\n     * @param {integer} index Index of decoded value\n     * @returns {(Object|CryptoOperationData)} Decoded javascript object if deep=true, else CryptoOperationData for father BER decoding\n     */\n    decode: function (s, name, deep, index) {\n        // Try clear base64\n        var re1 = /([A-Za-z0-9\\+\\/\\s\\=]+)/g,\n                valid = re1.exec(s);\n        if (valid[1].length !== s.length)\n            valid = false;\n        if (!valid && name) {\n            // Try with the name\n            var re2 = new RegExp(\n                    '-----\\\\s?BEGIN ' + name.toUpperCase() +\n                    '-----([A-Za-z0-9\\\\+\\\\/\\\\s\\\\=]+)-----\\\\s?END ' +\n                    name.toUpperCase() + '-----', 'g');\n            valid = re2.exec(s);\n        }\n        if (!valid) {\n            // Try with some name\n            var re3 = new RegExp(\n                    '-----\\\\s?BEGIN [A-Z0-9\\\\s]+' +\n                    '-----([A-Za-z0-9\\\\+\\\\/\\\\s\\\\=]+)-----\\\\s?END ' +\n                    '[A-Z0-9\\\\s]+-----', 'g');\n            valid = re3.exec(s);\n        }\n        var r = valid && valid[1 + (index || 0)];\n        if (!r)\n            throw new DataError('Not valid PEM format');\n        var out = Base64.decode(r);\n        if (deep)\n            out = BER.decode(out);\n        return out;\n    } // </editor-fold>\n};\n\n/**\n * PEM conversion\n * @memberOf GostCoding\n * @insnance\n * @type GostCoding.PEM\n */\nGostCoding.prototype.PEM = PEM;\n\nif (gostCrypto)\n    /**\n     * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM\n     *\n     * @memberOf gostCrypto\n     * @type GostCoding\n     */\n    gostCrypto.coding = new GostCoding();\n\nexport default GostCoding;\n"
  },
  {
    "path": "src/core/vendor/gost/gostCrypto.mjs",
    "content": "/**\n * Implementation Web Crypto interfaces for GOST algorithms\n * 1.76\n * 2014-2016, Rudolf Nickolaev. All rights reserved.\n *\n * Exported for CyberChef by mshwed [m@ttshwed.com]\n */\n\n/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\nimport GostRandom from './gostRandom.mjs';\nimport gostEngine from './gostEngine.mjs';\n\nimport crypto from 'crypto'\n\n/*\n* Algorithm normalization\n*\n*/ // <editor-fold defaultstate=\"collapsed\">\n\nvar root = {};\nroot.gostEngine = gostEngine;\n\nvar rootCrypto = crypto\n\nvar SyntaxError = Error,\n        DataError = Error,\n        NotSupportedError = Error,\n        OperationError = Error,\n        InvalidStateError = Error,\n        InvalidAccessError = Error;\n\n// Normalize algorithm\nfunction normalize(algorithm, method) {\n    if (typeof algorithm === 'string' || algorithm instanceof String)\n        algorithm = {name: algorithm};\n    var name = algorithm.name;\n    if (!name)\n        throw new SyntaxError('Algorithm name not defined');\n    // Extract algorithm modes from name\n    var modes = name.split('/'), modes = modes[0].split('-').concat(modes.slice(1));\n    // Normalize the name with default modes\n    var na = {};\n    name = modes[0].replace(/[\\.\\s]/g, '');\n    modes = modes.slice(1);\n    if (name.indexOf('28147') >= 0) {\n        na = {\n            name: 'GOST 28147',\n            version: 1989,\n            mode: (algorithm.mode || (// ES, MAC, KW\n                    (method === 'sign' || method === 'verify') ? 'MAC' :\n                    (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(),\n            length: algorithm.length || 64\n        };\n    } else if (name.indexOf('3412') >= 0) {\n        na = {\n            name: 'GOST R 34.12',\n            version: 2015,\n            mode: (algorithm.mode || (// ES, MAC, KW\n                    (method === 'sign' || method === 'verify') ? 'MAC' :\n                    (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(),\n            length: algorithm.length || 64 // 128\n        };\n    } else if (name.indexOf('3411') >= 0) {\n        na = {\n            name: 'GOST R 34.11',\n            version: 2012, // 1994\n            mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF, CPKDF\n                    (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' :\n                    (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(),\n            length: algorithm.length || 256 // 512\n        };\n    } else if (name.indexOf('3410') >= 0) {\n        na = {\n            name: 'GOST R 34.10',\n            version: 2012, // 1994, 2001\n            mode: (algorithm.mode || (// SIGN, DH, MASK\n                    (method === 'deriveKey' || method === 'deriveBits') ? 'DH' : 'SIGN')).toUpperCase(),\n            length: algorithm.length || 256 // 512\n        };\n    } else if (name.indexOf('SHA') >= 0) {\n        na = {\n            name: 'SHA',\n            version: (algorithm.length || 160) === 160 ? 1 : 2, // 1, 2\n            mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF\n                    (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' :\n                    (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(),\n            length: algorithm.length || 160\n        };\n    } else if (name.indexOf('RC2') >= 0) {\n        na = {\n            name: 'RC2',\n            version: 1,\n            mode: (algorithm.mode || (// ES, MAC, KW\n                    (method === 'sign' || method === 'verify') ? 'MAC' :\n                    (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(),\n            length: algorithm.length || 32 // 1 - 1024\n        };\n    } else if (name.indexOf('PBKDF2') >= 0) {\n        na = normalize(algorithm.hash, 'digest');\n        na.mode = 'PBKDF2';\n    } else if (name.indexOf('PFXKDF') >= 0) {\n        na = normalize(algorithm.hash, 'digest');\n        na.mode = 'PFXKDF';\n    } else if (name.indexOf('CPKDF') >= 0) {\n        na = normalize(algorithm.hash, 'digest');\n        na.mode = 'CPKDF';\n    } else if (name.indexOf('HMAC') >= 0) {\n        na = normalize(algorithm.hash, 'digest');\n        na.mode = 'HMAC';\n    } else\n        throw new NotSupportedError('Algorithm not supported');\n\n    // Compile modes\n    modes.forEach(function (mode) {\n        mode = mode.toUpperCase();\n        if (/^[0-9]+$/.test(mode)) {\n            if ((['8', '16', '32'].indexOf(mode) >= 0) || (na.length === '128' && mode === '64')) { // Shift bits\n                if (na.mode === 'ES')\n                    na.shiftBits = parseInt(mode);\n                else if (na.mode === 'MAC')\n                    na.macLength = parseInt(mode);\n                else\n                    throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported');\n            } else if (['89', '94', '01', '12', '15', '1989', '1994', '2001', '2012', '2015'].indexOf(mode) >= 0) { // GOST Year\n                var version = parseInt(mode);\n                version = version < 1900 ? (version < 80 ? 2000 + version : 1900 + version) : version;\n                na.version = version;\n            } else if (['1'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-1\n                na.version = 1;\n                na.length = 160;\n            } else if (['256', '384', '512'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-2\n                na.version = 2;\n                na.length = parseInt(mode);\n            } else if (['40', '128'].indexOf(mode) >= 0 && na.name === 'RC2') { // RC2\n                na.version = 1;\n                na.length = parseInt(mode); // key size\n            } else if (['64', '128', '256', '512'].indexOf(mode) >= 0) // block size\n                na.length = parseInt(mode);\n            else if (['1000', '2000'].indexOf(mode) >= 0) // Iterations\n                na.iterations = parseInt(mode);\n            // Named Paramsets\n        } else if (['E-TEST', 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC', 'E-Z', 'D-TEST', 'D-A', 'D-SC'].indexOf(mode) >= 0) {\n            na.sBox = mode;\n        } else if (['S-TEST', 'S-A', 'S-B', 'S-C', 'S-D', 'X-A', 'X-B', 'X-C'].indexOf(mode) >= 0) {\n            na.namedParam = mode;\n        } else if (['S-256-TEST', 'S-256-A', 'S-256-B', 'S-256-C', 'P-256', 'T-512-TEST', 'T-512-A',\n            'T-512-B', 'X-256-A', 'X-256-B', 'T-256-TEST', 'T-256-A', 'T-256-B', 'S-256-B', 'T-256-C', 'S-256-C'].indexOf(mode) >= 0) {\n            na.namedCurve = mode;\n        } else if (['SC', 'CP', 'VN'].indexOf(mode) >= 0) {\n            na.procreator = mode;\n\n            // Encription GOST 28147 or GOST R 34.12\n        } else if (na.name === 'GOST 28147' || na.name === 'GOST R 34.12' || na.name === 'RC2') {\n            if (['ES', 'MAC', 'KW', 'MASK'].indexOf(mode) >= 0) {\n                na.mode = mode;\n            } else if (['ECB', 'CFB', 'OFB', 'CTR', 'CBC'].indexOf(mode) >= 0) {\n                na.mode = 'ES';\n                na.block = mode;\n            } else if (['CPKW', 'NOKW', 'SCKW'].indexOf(mode) >= 0) {\n                na.mode = 'KW';\n                na.keyWrapping = mode.replace('KW', '');\n            } else if (['ZEROPADDING', 'PKCS5PADDING', 'NOPADDING', 'RANDOMPADDING', 'BITPADDING'].indexOf(mode) >= 0) {\n                na.padding = mode.replace('PADDING', '');\n            } else if (['NOKM', 'CPKM'].indexOf(mode) >= 0) {\n                na.keyMeshing = mode.replace('KM', '');\n            } else\n                throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported');\n\n            // Digesting GOST 34.11\n        } else if (na.name === 'GOST R 34.11' || na.name === 'SHA') {\n            if (['HASH', 'KDF', 'HMAC', 'PBKDF2', 'PFXKDF', 'CPKDF'].indexOf(mode) >= 0)\n                na.mode = mode;\n            else\n                throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported');\n\n            // Signing GOST 34.10\n        } else if (na.name === 'GOST R 34.10') {\n            var hash = mode.replace(/[\\.\\s]/g, '');\n            if (hash.indexOf('GOST') >= 0 && hash.indexOf('3411') >= 0)\n                na.hash = mode;\n            else if (['SIGN', 'DH', 'MASK'].indexOf(mode))\n                na.mode = mode;\n            else\n                throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported');\n        }\n    });\n\n    // Procreator\n    na.procreator = algorithm.procreator || na.procreator || 'CP';\n\n    // Key size\n    switch (na.name) {\n        case 'GOST R 34.10':\n            na.keySize = na.length / (na.version === 1994 ? 4 : 8);\n            break;\n        case 'GOST R 34.11':\n            na.keySize = 32;\n            break;\n        case 'GOST 28147':\n        case 'GOST R 34.12':\n            na.keySize = 32;\n            break;\n        case 'RC2':\n            na.keySize = Math.ceil(na.length / 8);\n            break;\n        case 'SHA':\n            na.keySize = na.length / 8;\n            break;\n    }\n\n    // Encrypt additional modes\n    if (na.mode === 'ES') {\n        if (algorithm.block)\n            na.block = algorithm.block; // ECB, CFB, OFB, CTR, CBC\n        if (na.block)\n            na.block = na.block.toUpperCase();\n        if (algorithm.padding)\n            na.padding = algorithm.padding; // NO, ZERO, PKCS5, RANDOM, BIT\n        if (na.padding)\n            na.padding = na.padding.toUpperCase();\n        if (algorithm.shiftBits)\n            na.shiftBits = algorithm.shiftBits; // 8, 16, 32, 64\n        if (algorithm.keyMeshing)\n            na.keyMeshing = algorithm.keyMeshing; // NO, CP\n        if (na.keyMeshing)\n            na.keyMeshing = na.keyMeshing.toUpperCase();\n        // Default values\n        if (method !== 'importKey' && method !== 'generateKey') {\n            na.block = na.block || 'ECB';\n            na.padding = na.padding || (na.block === 'CBC' || na.block === 'ECB' ? 'ZERO' : 'NO');\n            if (na.block === 'CFB' || na.block === 'OFB')\n                na.shiftBits = na.shiftBits || na.length;\n            na.keyMeshing = na.keyMeshing || 'NO';\n        }\n    }\n    if (na.mode === 'KW') {\n        if (algorithm.keyWrapping)\n            na.keyWrapping = algorithm.keyWrapping; // NO, CP, SC\n        if (na.keyWrapping)\n            na.keyWrapping = na.keyWrapping.toUpperCase();\n        if (method !== 'importKey' && method !== 'generateKey')\n            na.keyWrapping = na.keyWrapping || 'NO';\n    }\n\n    // Paramsets\n    ['sBox', 'namedParam', 'namedCurve', 'curve', 'param', 'modulusLength'].forEach(function (name) {\n        algorithm[name] && (na[name] = algorithm[name]);\n    });\n    // Default values\n    if (method !== 'importKey' && method !== 'generateKey') {\n        if (na.name === 'GOST 28147') {\n            na.sBox = na.sBox || (na.procreator === 'SC' ? 'E-SC' : 'E-A'); // 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC'\n        } else if (na.name === 'GOST R 34.12' && na.length === 64) {\n            na.sBox = 'E-Z';\n        } else if (na.name === 'GOST R 34.11' && na.version === 1994) {\n            na.sBox = na.sBox || (na.procreator === 'SC' ? 'D-SC' : 'D-A'); // 'D-SC'\n        } else if (na.name === 'GOST R 34.10' && na.version === 1994) {\n            na.namedParam = na.namedParam || (na.mode === 'DH' ? 'X-A' : 'S-A'); // 'S-B', 'S-C', 'S-D', 'X-B', 'X-C'\n        } else if (na.name === 'GOST R 34.10' && na.version === 2001) {\n            na.namedCurve = na.namedCurve || (na.length === 256 ?\n                    na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256'\n                    na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C'\n        } else if (na.name === 'GOST R 34.10' && na.version === 2012) {\n            na.namedCurve = na.namedCurve || (na.length === 256 ?\n                    na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256'\n                    na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C'\n        }\n    }\n\n    // Vectors\n    switch (na.mode) {\n        case 'DH':\n            algorithm.ukm && (na.ukm = algorithm.ukm);\n            algorithm['public'] && (na['public'] = algorithm['public']);\n            break;\n        case 'SIGN':\n        case 'KW':\n            algorithm.ukm && (na.ukm = algorithm.ukm);\n            break;\n        case 'ES':\n        case 'MAC':\n            algorithm.iv && (na.iv = algorithm.iv);\n            break;\n        case 'KDF':\n            algorithm.label && (na.label = algorithm.label);\n            algorithm.contex && (na.context = algorithm.contex);\n            break;\n        case 'PBKDF2':\n            algorithm.salt && (na.salt = algorithm.salt);\n            algorithm.iterations && (na.iterations = algorithm.iterations);\n            algorithm.diversifier && (na.diversifier = algorithm.diversifier);\n            break;\n        case 'PFXKDF':\n            algorithm.salt && (na.salt = algorithm.salt);\n            algorithm.iterations && (na.iterations = algorithm.iterations);\n            algorithm.diversifier && (na.diversifier = algorithm.diversifier);\n            break;\n        case 'CPKDF':\n            algorithm.salt && (na.salt = algorithm.salt);\n            algorithm.iterations && (na.iterations = algorithm.iterations);\n            break;\n    }\n\n    // Verification method and modes\n    if (method && (\n            ((na.mode !== 'ES' && na.mode !== 'SIGN' && na.mode !== 'MAC' &&\n                    na.mode !== 'HMAC' && na.mode !== 'KW' && na.mode !== 'DH'\n                    && na.mode !== 'MASK') &&\n                    (method === 'generateKey')) ||\n            ((na.mode !== 'ES') &&\n                    (method === 'encrypt' || method === 'decrypt')) ||\n            ((na.mode !== 'SIGN' && na.mode !== 'MAC' && na.mode !== 'HMAC') &&\n                    (method === 'sign' || method === 'verify')) ||\n            ((na.mode !== 'HASH') &&\n                    (method === 'digest')) ||\n            ((na.mode !== 'KW' && na.mode !== 'MASK') &&\n                    (method === 'wrapKey' || method === 'unwrapKey')) ||\n            ((na.mode !== 'DH' && na.mode !== 'PBKDF2' && na.mode !== 'PFXKDF' &&\n                    na.mode !== 'CPKDF' && na.mode !== 'KDF') &&\n                    (method === 'deriveKey' || method === 'deriveBits'))))\n        throw new NotSupportedError('Algorithm mode ' + na.mode + ' not valid for method ' + method);\n\n    // Normalize hash algorithm\n    algorithm.hash && (na.hash = algorithm.hash);\n    if (na.hash) {\n        if ((typeof na.hash === 'string' || na.hash instanceof String)\n                && na.procreator)\n            na.hash = na.hash + '/' + na.procreator;\n        na.hash = normalize(na.hash, 'digest');\n    }\n\n    // Algorithm object identirifer\n    algorithm.id && (na.id = algorithm.id);\n\n    return na;\n}\n\n// Check for possibility use native crypto.subtle\nfunction checkNative(algorithm) {\n    if (!rootCrypto || !rootCrypto.subtle || !algorithm)\n        return false;\n    // Prepare name\n    var name = (typeof algorithm === 'string' || algorithm instanceof String) ?\n            name = algorithm : algorithm.name;\n    if (!name)\n        return false;\n    name = name.toUpperCase();\n    // Digest algorithm for key derivation\n    if ((name.indexOf('KDF') >= 0 || name.indexOf('HMAC') >= 0) && algorithm.hash)\n        return checkNative(algorithm.hash);\n    // True if no supported names\n    return name.indexOf('GOST') === -1 &&\n            name.indexOf('SHA-1') === -1 &&\n            name.indexOf('RC2') === -1 &&\n            name.indexOf('?DES') === -1;\n}\n// </editor-fold>\n\n/*\n    * Key conversion methods\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\n// Check key parameter\nfunction checkKey(key, method) {\n    if (!key.algorithm)\n        throw new SyntaxError('Key algorithm not defined');\n\n    if (!key.algorithm.name)\n        throw new SyntaxError('Key algorithm name not defined');\n\n    var name = key.algorithm.name,\n            gostCipher = name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2',\n            gostDigest = name === 'GOST R 34.11' || name === 'SHA',\n            gostSign = name === 'GOST R 34.10';\n\n    if (!gostCipher && !gostSign && !gostDigest)\n        throw new NotSupportedError('Key algorithm ' + name + ' is unsupproted');\n\n    if (!key.type)\n        throw new SyntaxError('Key type not defined');\n\n    if (((gostCipher || gostDigest) && key.type !== 'secret') ||\n            (gostSign && !(key.type === 'public' || key.type === 'private')))\n        throw new DataError('Key type ' + key.type + ' is not valid for algorithm ' + name);\n\n    if (!key.usages || !key.usages.indexOf)\n        throw new SyntaxError('Key usages not defined');\n\n    for (var i = 0, n = key.usages.length; i < n; i++) {\n        var md = key.usages[i];\n        if (((md === 'encrypt' || md === 'decrypt') && key.type !== 'secret') ||\n                (md === 'sign' && key.type === 'public') ||\n                (md === 'verify' && key.type === 'private'))\n            throw new InvalidStateError('Key type ' + key.type + ' is not valid for ' + md);\n    }\n\n    if (method)\n        if (key.usages.indexOf(method) === -1)\n            throw new InvalidAccessError('Key usages is not contain method ' + method);\n\n    if (!key.buffer)\n        throw new SyntaxError('Key buffer is not defined');\n\n    var size = key.buffer.byteLength * 8, keySize = 8 * key.algorithm.keySize;\n    if ((key.type === 'secret' && size !== (keySize || 256) &&\n            (key.usages.indexOf('encrypt') >= 0 || key.usages.indexOf('decrypt') >= 0)) ||\n            (key.type === 'private' && !(size === 256 || size === 512)) ||\n            (key.type === 'public' && !(size === 512 || size === 1024)))\n        throw new SyntaxError('Key buffer has wrong size ' + size + ' bit');\n}\n\n// Extract key and enrich cipher algorithm\nfunction extractKey(method, algorithm, key) {\n    checkKey(key, method);\n    if (algorithm) {\n        var params;\n        switch (algorithm.mode) {\n            case 'ES':\n                params = ['sBox', 'keyMeshing', 'padding', 'block'];\n                break;\n            case 'SIGN':\n                params = ['namedCurve', 'namedParam', 'sBox', 'curve', 'param', 'modulusLength'];\n                break;\n            case 'MAC':\n                params = ['sBox'];\n                break;\n            case 'KW':\n                params = ['keyWrapping', 'ukm'];\n                break;\n            case 'DH':\n                params = ['namedCurve', 'namedParam', 'sBox', 'ukm', 'curve', 'param', 'modulusLength'];\n                break;\n            case 'KDF':\n                params = ['context', 'label'];\n                break;\n            case 'PBKDF2':\n                params = ['sBox', 'iterations', 'salt'];\n                break;\n            case 'PFXKDF':\n                params = ['sBox', 'iterations', 'salt', 'diversifier'];\n                break;\n            case 'CPKDF':\n                params = ['sBox', 'salt'];\n                break;\n        }\n        if (params)\n            params.forEach(function (name) {\n                key.algorithm[name] && (algorithm[name] = key.algorithm[name]);\n            });\n    }\n    return key.buffer;\n}\n\n// Make key definition\nfunction convertKey(algorithm, extractable, keyUsages, keyData, keyType) {\n    var key = {\n        type: keyType || (algorithm.name === 'GOST R 34.10' ? 'private' : 'secret'),\n        extractable: extractable || 'false',\n        algorithm: algorithm,\n        usages: keyUsages || [],\n        buffer: keyData\n    };\n    checkKey(key);\n    return key;\n}\n\nfunction convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, publicBuffer, privateBuffer) {\n\n    if (!keyUsages || !keyUsages.indexOf)\n        throw new SyntaxError('Key usages not defined');\n\n    var publicUsages = keyUsages.filter(function (value) {\n        return value !== 'sign';\n    });\n    var privateUsages = keyUsages.filter(function (value) {\n        return value !== 'verify';\n    });\n\n    return {\n        publicKey: convertKey(publicAlgorithm, extractable, publicUsages, publicBuffer, 'public'),\n        privateKey: convertKey(privateAlgorithm, extractable, privateUsages, privateBuffer, 'private')\n    };\n}\n\n// Swap bytes in buffer\nfunction swapBytes(src) {\n    if (src instanceof CryptoOperationData)\n        src = new Uint8Array(src);\n    var dst = new Uint8Array(src.length);\n    for (var i = 0, n = src.length; i < n; i++)\n        dst[n - i - 1] = src[i];\n    return dst.buffer;\n}\n// </editor-fold>\n\n/**\n * Promise stub object (not fulfill specification, only for internal use)\n * Class not defined if Promise class already defined in root context<br><br>\n *\n * The Promise object is used for deferred and asynchronous computations. A Promise is in one of the three states:\n *  <ul>\n *      <li>pending: initial state, not fulfilled or rejected.</li>\n *      <li>fulfilled: successful operation</li>\n *      <li>rejected: failed operation.</li>\n *  </ul>\n * Another term describing the state is settled: the Promise is either fulfilled or rejected, but not pending.<br><br>\n * @class Promise\n * @global\n * @param {function} executor Function object with two arguments resolve and reject.\n * The first argument fulfills the promise, the second argument rejects it.\n * We can call these functions, once our operation is completed.\n */ // <editor-fold defaultstate=\"collapsed\">\nif (!Promise) {\n\n    root.Promise = (function () {\n\n        function mswrap(value) {\n            if (value && value.oncomplete === null && value.onerror === null) {\n                return new Promise(function (resolve, reject) {\n                    value.oncomplete = function () {\n                        resolve(value.result);\n                    };\n                    value.onerror = function () {\n                        reject(new OperationError(value.toString()));\n                    };\n                });\n            } else\n                return value;\n        }\n\n        function Promise(executor) {\n\n            var state = 'pending', result,\n                    resolveQueue = [], rejectQueue = [];\n\n            function call(callback) {\n                try {\n                    callback();\n                } catch (e) {\n                }\n            }\n\n            try {\n                executor(function (value) {\n                    if (state === 'pending') {\n                        state = 'fulfilled';\n                        result = value;\n                        resolveQueue.forEach(call);\n                    }\n                }, function (reason) {\n                    if (state === 'pending') {\n                        state = 'rejected';\n                        result = reason;\n                        rejectQueue.forEach(call);\n                    }\n                });\n            } catch (error) {\n                if (state === 'pending') {\n                    state = 'rejected';\n                    result = error;\n                    rejectQueue.forEach(call);\n                }\n            }\n            /**\n             * The then() method returns a Promise. It takes two arguments, both are\n             * callback functions for the success and failure cases of the Promise.\n             *\n             * @method then\n             * @memberOf Promise\n             * @instance\n             * @param {function} onFulfilled A Function called when the Promise is fulfilled. This function has one argument, the fulfillment value.\n             * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason.\n             * @returns {Promise}\n             */\n            this.then = function (onFulfilled, onRejected) {\n\n                return new Promise(function (resolve, reject) {\n\n                    function asyncOnFulfilled() {\n                        var value;\n                        try {\n                            value = onFulfilled ? onFulfilled(result) : result;\n                        } catch (error) {\n                            reject(error);\n                            return;\n                        }\n                        value = mswrap(value);\n                        if (value && value?.then?.call) {\n                            value.then(resolve, reject);\n                        } else {\n                            resolve(value);\n                        }\n                    }\n\n                    function asyncOnRejected() {\n                        var reason;\n                        try {\n                            reason = onRejected ? onRejected(result) : result;\n                        } catch (error) {\n                            reject(error);\n                            return;\n                        }\n                        reason = mswrap(reason);\n                        if (reason && reason?.then?.call) {\n                            reason.then(resolve, reject);\n                        } else {\n                            reject(reason);\n                        }\n                    }\n\n                    if (state === 'fulfilled') {\n                        asyncOnFulfilled();\n                    } else if (state === 'rejected') {\n                        asyncOnRejected();\n                    } else {\n                        resolveQueue.push(asyncOnFulfilled);\n                        rejectQueue.push(asyncOnRejected);\n                    }\n\n                });\n\n            };\n            /**\n             * The catch() method returns a Promise and deals with rejected cases only.\n             * It behaves the same as calling Promise.prototype.then(undefined, onRejected).\n             *\n             * @method catch\n             * @memberOf Promise\n             * @instance\n             * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason.\n             * @returns {Promise}\n             */\n            this['catch'] = function (onRejected) {\n                return this.then(undefined, onRejected);\n            };\n        }\n\n        /**\n         * The Promise.all(iterable) method returns a promise that resolves when all\n         * of the promises in the iterable argument have resolved.<br><br>\n         *\n         * The result is passed as an array of values from all the promises.\n         * If something passed in the iterable array is not a promise, it's converted to\n         * one by Promise.resolve. If any of the passed in promises rejects, the\n         * all Promise immediately rejects with the value of the promise that rejected,\n         * discarding all the other promises whether or not they have resolved.\n         *\n         * @method all\n         * @memberOf Promise\n         * @static\n         * @param {KeyUsages} promises Array with promises.\n         * @returns {Promise}\n         */\n        Promise.all = function (promises) {\n            return new Promise(function (resolve, reject) {\n                var result = [], count = 0;\n                function asyncResolve(k) {\n                    count++;\n                    return function (data) {\n                        result[k] = data;\n                        count--;\n                        if (count === 0)\n                            resolve(result);\n                    };\n                }\n\n                function asyncReject(reason) {\n                    if (count > 0)\n                        reject(reason);\n                    count = 0;\n                }\n\n                for (var i = 0, n = promises.length; i < n; i++) {\n                    var data = promises[i];\n                    if (data?.then?.call)\n                        data.then(asyncResolve(i), asyncReject);\n                    else\n                        result[i] = data;\n                }\n\n                if (count === 0)\n                    resolve(result);\n            });\n        };\n\n        return Promise;\n    })();\n} // </editor-fold>\n\n/*\n    * Worker executor\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\nvar baseUrl = '', nameSuffix = '';\n// Try to define from DOM model\nif (typeof document !== 'undefined') {\n    (function () {\n        var regs = /^(.*)gostCrypto(.*)\\.js$/i;\n        var list = document.querySelectorAll('script');\n        for (var i = 0, n = list.length; i < n; i++) {\n            var value = list[i].getAttribute('src');\n            var test = regs.exec(value);\n            if (test) {\n                baseUrl = test[1];\n                nameSuffix = test[2];\n            }\n        }\n    })();\n}\n\n// Local importScripts procedure for include dependens\nfunction importScripts() {\n    for (var i = 0, n = arguments.length; i < n; i++) {\n        var name = arguments[i].split('.'),\n                src = baseUrl + name[0] + nameSuffix + '.' + name[1];\n        var el = document.querySelector('script[src=\"' + src + '\"]');\n        if (!el) {\n            el = document.createElement('script');\n            el.setAttribute('src', src);\n            document.head.appendChild(el);\n        }\n    }\n}\n\n// Create Worker\nvar worker = false, tasks = [], sequence = 0;\n// Worker will create only for first child process and\n// Gost implementation libraries not yet loaded\nif (!root.importScripts && !root.gostEngine) {\n\n    try {\n        worker = new Worker(baseUrl + 'gostEngine' + nameSuffix + '.js');\n\n        // Result of opertion\n        worker.onmessage = function (event) {\n            // Find task\n            var id = event.data.id;\n            for (var i = 0, n = tasks.length; i < n; i++)\n                if (tasks[i].id === id)\n                    break;\n            if (i < n) {\n                var task = tasks[i];\n                tasks.splice(i, 1);\n                // Reject if error or resolve with result\n                if (event.data.error)\n                    task.reject(new OperationError(event.data.error));\n                else\n                    task.resolve(event.data.result);\n            }\n        };\n\n        // Worker error - reject all waiting tasks\n        worker.onerror = function (event) {\n            for (var i = 0, n = tasks.length; i < n; i++)\n                tasks[i].reject(event.error);\n            tasks = [];\n        };\n\n    } catch (e) {\n        // Worker is't supported\n        worker = false;\n    }\n}\n\nif (!root.importScripts) {\n    // This procedure emulate load dependents as in Worker\n    root.importScripts = importScripts;\n\n}\n\nif (!worker) {\n    // Import main module\n    // Reason: we are already in worker process or Worker interface is not\n    // yet supported\n    root.gostEngine || require('./gostEngine');\n}\n\n// Executor for any method\nfunction execute(algorithm, method, args) {\n    return new Promise(function (resolve, reject) {\n        try {\n            if (worker) {\n                var id = ++sequence;\n                tasks.push({\n                    id: id,\n                    resolve: resolve,\n                    reject: reject\n                });\n                worker.postMessage({\n                    id: id, algorithm: algorithm,\n                    method: method, args: args\n                });\n            } else {\n                if (root.gostEngine)\n                    resolve(root.gostEngine.execute(algorithm, method, args));\n                else\n                    reject(new OperationError('Module gostEngine not found'));\n            }\n        } catch (error) {\n            reject(error);\n        }\n    });\n}\n\n// Self resolver\nfunction call(callback) {\n    try {\n        callback();\n    } catch (e) {\n    }\n}\n\n// </editor-fold>\n\n/*\n    * WebCrypto common class references\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n/**\n * The Algorithm object is a dictionary object [WebIDL] which is used to\n * specify an algorithm and any additional parameters required to fully\n * specify the desired operation.<br>\n * <pre>\n *  dictionary Algorithm {\n *      DOMString name;\n *  };\n * </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary}\n * @class Algorithm\n * @param {DOMString} name The name of the registered algorithm to use.\n */\n\n/**\n * AlgorithmIdentifier - Algorithm or DOMString name of algorithm<br>\n * <pre>\n *  typedef (Algorithm or DOMString) AlgorithmIdentifier;\n * </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary}\n * @class AlgorithmIdentifier\n */\n\n/**\n * The KeyAlgorithm interface represents information about the contents of a\n * given Key object.\n * <pre>\n *  interface KeyAlgorithm {\n *      readonly attribute DOMString name\n *  };\n * </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-algorithm-interface}\n * @class KeyAlgorithm\n * @param {DOMString} name The name of the algorithm used to generate the Key\n */\n\n/**\n * The type of a key. The recognized key type values are \"public\", \"private\"\n * and \"secret\". Opaque keying material, including that used for symmetric\n * algorithms, is represented by \"secret\", while keys used as part of asymmetric\n * algorithms composed of public/private keypairs will be either \"public\" or \"private\".\n * <pre>\n *  typedef DOMString KeyType;\n * </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface}\n * @class KeyType\n */\n\n/**\n * Sequence of operation type that may be performed using a key. The recognized\n * key usage values are \"encrypt\", \"decrypt\", \"sign\", \"verify\", \"deriveKey\",\n * \"deriveBits\", \"wrapKey\" and \"unwrapKey\".\n * <pre>\n *  typedef DOMString[] KeyUsages;\n * </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface}\n * @class KeyUsages\n */\n\n/**\n * The Key object represents an opaque reference to keying material that is\n * managed by the user agent.<br>\n * This specification provides a uniform interface for many different kinds of\n * keying material managed by the user agent. This may include keys that have\n * been generated by the user agent, derived from other keys by the user agent,\n * imported to the user agent through user actions or using this API,\n * pre-provisioned within software or hardware to which the user agent has\n * access or made available to the user agent in other ways. The term key refers\n * broadly to any keying material including actual keys for cryptographic\n * operations and secret values obtained within key derivation or exchange operations.<br>\n * The Key object is not required to directly interface with the underlying key\n * storage mechanism, and may instead simply be a reference for the user agent\n * to understand how to obtain the keying material when needed, eg. when performing\n * a cryptographic operation.\n * <pre>\n *  interface Key {\n *      readonly attribute KeyType type;\n *      readonly attribute boolean extractable;\n *      readonly attribute KeyAlgorithm algorithm;\n *      readonly attribute KeyUsages usages;\n *  };\n * </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface}\n * @class Key\n * @param {KeyType} type The type of a key. The recognized key type values are \"public\", \"private\" and \"secret\".\n * @param {boolean} extractable Whether or not the raw keying material may be exported by the application.\n * @param {KeyAlgorithm} algorithm The Algorithm used to generate the key.\n * @param {KeyUsages} usages Key usage array: type of operation that may be performed using a key.\n */\n\n/**\n * The KeyPair interface represents an asymmetric key pair that is comprised of both public and private keys.\n * <pre>\n *  interface KeyPair {\n *      readonly attribute Key publicKey;\n *      readonly attribute Key privateKey;\n *  };\n * </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#keypair}\n * @class KeyPair\n * @param {Key} privateKey Private key\n * @param {Key} publicKey Public key\n */\n\n/**\n * Specifies a serialization format for a key. The recognized key format values are:\n *  <ul>\n *      <li>'raw' - An unformatted sequence of bytes. Intended for secret keys.</li>\n *      <li>'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.</li>\n *      <li>'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.</li>\n *      <li>'jwk' - The key is represented as JSON according to the JSON Web Key format.</li>\n *  </ul>\n *  <pre>\n *  typedef DOMString KeyFormat;\n *  </pre>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface}\n *  @class KeyFormat\n */\n\n/**\n * Binary data\n *  <pre>\n *  typedef (ArrayBuffer or ArrayBufferView) CryptoOperationData;\n *  </pre>\n * @class CryptoOperationData\n */\nvar CryptoOperationData = ArrayBuffer;\n\n/**\n * DER-encoded ArrayBuffer or PEM-encoded DOMString constains ASN.1 object<br>\n * <pre>\n *  typedef (ArrayBuffer or DOMString) FormatedData;\n * </pre>\n * @class FormatedData\n */\n// </editor-fold>\n\n/**\n * The gostCrypto provide general purpose cryptographic functionality for\n * GOST standards including a cryptographically strong pseudo-random number\n * generator seeded with truly random values.\n *\n * @namespace gostCrypto\n */\nvar gostCrypto = {};\n\n/**\n * The SubtleCrypto class provides low-level cryptographic primitives and algorithms.\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface}\n *\n * @class SubtleCrypto\n */ // <editor-fold>\nfunction SubtleCrypto() {\n}\n\n/**\n * The encrypt method returns a new Promise object that will encrypt data\n * using the specified algorithm identifier with the supplied Key.\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST 28147-ECB</b> \"prostaya zamena\" (ECB) mode (default)</li>\n *      <li><b>GOST 28147-CFB</b> \"gammirovanie s obratnoj svyaziyu po shifrotekstu\" (CFB) mode</li>\n *      <li><b>GOST 28147-OFB</b> \"gammirovanie s obratnoj svyaziyu po vyhodu\" (OFB) mode</li>\n *      <li><b>GOST 28147-CTR</b> \"gammirovanie\" (counter) mode</li>\n *      <li><b>GOST 28147-CBC</b> Cipher-Block-Chaining (CBC) mode</li>\n *      <li><b>GOST R 34.12-ECB</b> \"prostaya zamena\" (ECB) mode (default)</li>\n *      <li><b>GOST R 34.12-CFB</b> \"gammirovanie s obratnoj svyaziyu po shifrotekstu\" (CFB) mode</li>\n *      <li><b>GOST R 34.12-OFB</b> \"gammirovanie s obratnoj svyaziyu po vyhodu\" (OFB) mode</li>\n *      <li><b>GOST R 34.12-CTR</b> \"gammirovanie\" (counter) mode</li>\n *      <li><b>GOST R 34.12-CBC</b> Cipher-Block-Chaining (CBC) mode</li>\n *  </ul>\n *  For more information see {@link GostCipher}\n *\n * @memberOf SubtleCrypto\n * @method encrypt\n * @instance\n * @param {AlgorithmIdentifier} algorithm Algorithm identifier\n * @param {Key} key Key object\n * @param {CryptoOperationData} data Operation data\n * @returns {Promise} Promise that resolves with {@link CryptoOperationData}\n */\nSubtleCrypto.prototype.encrypt = function (algorithm, key, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.encrypt(algorithm, key, data);\n\n        algorithm = normalize(algorithm, 'encrypt');\n        return execute(algorithm, 'encrypt',\n                [extractKey('encrypt', algorithm, key), data]);\n    });\n}; // </editor-fold>\n\n/**\n * The decrypt method returns a new Promise object that will decrypt data\n * using the specified algorithm identifier with the supplied Key.\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-decrypt}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST 28147-ECB</b> \"prostaya zamena\" (ECB) mode (default)</li>\n *      <li><b>GOST 28147-CFB</b> \"gammirovanie s obratnoj svyaziyu po shifrotekstu\" (CFB) mode</li>\n *      <li><b>GOST 28147-OFB</b> \"gammirovanie s obratnoj svyaziyu po vyhodu\" (OFB) mode</li>\n *      <li><b>GOST 28147-CTR</b> \"gammirovanie\" (counter) mode</li>\n *      <li><b>GOST 28147-CBC</b> Cipher-Block-Chaining (CBC) mode</li>\n *      <li><b>GOST R 34.12-ECB</b> \"prostaya zamena\" (ECB) mode (default)</li>\n *      <li><b>GOST R 34.12-CFB</b> \"gammirovanie s obratnoj svyaziyu po shifrotekstu\" (CFB) mode</li>\n *      <li><b>GOST R 34.12-OFB</b> \"gammirovanie s obratnoj svyaziyu po vyhodu\" (OFB) mode</li>\n *      <li><b>GOST R 34.12-CTR</b> \"gammirovanie\" (counter) mode</li>\n *      <li><b>GOST R 34.12-CBC</b> Cipher-Block-Chaining (CBC) mode</li>\n *  </ul>\n *  For additional modes see {@link GostCipher}\n *\n * @memberOf SubtleCrypto\n * @method decrypt\n * @instance\n * @param {AlgorithmIdentifier} algorithm Algorithm identifier\n * @param {Key} key Key object\n * @param {CryptoOperationData} data Operation data\n * @returns {Promise} Promise that resolves with {@link CryptoOperationData}\n */\nSubtleCrypto.prototype.decrypt = function (algorithm, key, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.decrypt(algorithm, key, data);\n\n        algorithm = normalize(algorithm, 'decrypt');\n        return execute(algorithm, 'decrypt',\n                [extractKey('decrypt', algorithm, key), data]);\n    });\n}; // </editor-fold>\n\n/**\n * The sign method returns a new Promise object that will sign data using\n * the specified algorithm identifier with the supplied Key.\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-sign}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.10-94</b> GOST Signature</li>\n *      <li><b>GOST R 34.10-94/GOST R 34.11-94</b> GOST Signature with Hash</li>\n *      <li><b>GOST R 34.10</b> ECGOST Signature</li>\n *      <li><b>GOST R 34.10/GOST R 34.11-94</b> ECGOST Signature with Old-Style Hash</li>\n *      <li><b>GOST R 34.10/GOST R 34.11</b> ECGOST Signature with Streebog Hash</li>\n *      <li><b>GOST 28147-MAC</b> MAC base on GOST 28147</li>\n *      <li><b>GOST R 34.12-MAC</b> MAC base on GOST R 43.12</li>\n *      <li><b>GOST R 34.11-HMAC</b> HMAC base on GOST 34.11</li>\n *      <li><b>SHA-HMAC</b> HMAC base on SHA</li>\n *  </ul>\n *  For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}\n *\n * @memberOf SubtleCrypto\n * @method sign\n * @instance\n * @param {AlgorithmIdentifier} algorithm Algorithm identifier\n * @param {Key} key Key object\n * @param {CryptoOperationData} data Operation data\n * @returns {Promise} Promise that resolves with {@link CryptoOperationData}\n */\nSubtleCrypto.prototype.sign = function (algorithm, key, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.sign(algorithm, key, data);\n\n        algorithm = normalize(algorithm, 'sign');\n        var value = execute(algorithm, 'sign',\n                [extractKey('sign', algorithm, key), data]).then(function (data) {\n            if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') {\n                data = gostCrypto.asn1.GostSignature.encode(data);\n            }\n            return data;\n        });\n        return value;\n    });\n}; // </editor-fold>\n\n/**\n * The verify method returns a new Promise object that will verify data\n * using the specified algorithm identifier with the supplied Key.\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-verify}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.10-94</b> GOST Signature</li>\n *      <li><b>GOST R 34.10-94/GOST R 34.11-94</b> GOST Signature with Hash</li>\n *      <li><b>GOST R 34.10</b> ECGOST Signature</li>\n *      <li><b>GOST R 34.10/GOST R 34.11-94</b> ECGOST Signature with Old-Style Hash</li>\n *      <li><b>GOST R 34.10/GOST R 34.11</b> ECGOST Signature with Streebog Hash</li>\n *      <li><b>GOST 28147-MAC</b> MAC base on GOST 28147</li>\n *      <li><b>GOST R 34.12-MAC</b> MAC base on GOST R 34.12</li>\n *      <li><b>GOST R 34.11-HMAC</b> HMAC base on GOST 34.11</li>\n *      <li><b>SHA-HMAC</b> HMAC base on SHA</li>\n *  </ul>\n *  For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}\n *\n * @memberOf SubtleCrypto\n * @method verify\n * @instance\n * @param {AlgorithmIdentifier} algorithm Algorithm identifier\n * @param {Key} key Key object\n * @param {CryptoOperationData} signature Signature data\n * @param {CryptoOperationData} data Operation data\n * @returns {Promise} Promise that resolves with boolean value of verification result\n */\nSubtleCrypto.prototype.verify = function (algorithm, key, signature, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.verify(algorithm, key, signature, data);\n\n        algorithm = normalize(algorithm, 'verify');\n        if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') {\n            var obj = gostCrypto.asn1.GostSignature.decode(signature);\n            signature = {r: obj.r, s: obj.s};\n        }\n        return execute(algorithm, 'verify',\n                [extractKey('verify', algorithm, key), signature, data]);\n    });\n}; // </editor-fold>\n\n/**\n * The digest method returns a new Promise object that will digest data\n * using the specified algorithm identifier.\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-digest}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.11-94</b> Old-Style GOST Hash</li>\n *      <li><b>GOST R 34.11</b> GOST Streebog Hash</li>\n *      <li><b>SHA</b> SHA Hash</li>\n *  </ul>\n *  For additional modes see {@link GostDigest}\n *\n * @memberOf SubtleCrypto\n * @method digest\n * @instance\n * @param {AlgorithmIdentifier} algorithm Algorithm identifier\n * @param {CryptoOperationData} data Operation data\n * @returns {Promise} Promise that resolves with {@link CryptoOperationData}\n */\nSubtleCrypto.prototype.digest = function (algorithm, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.digest(algorithm, data);\n\n        algorithm = normalize(algorithm, 'digest');\n        return execute(algorithm, 'digest', [data]);\n    });\n}; // </editor-fold>\n\n/**\n * The generateKey method returns a new Promise object that will key(s) using\n * the specified algorithm identifier. Key can be used in according with\n * KeyUsages sequence. The recognized key usage values are \"encrypt\", \"decrypt\",\n * \"sign\", \"verify\", \"deriveKey\", \"deriveBits\", \"wrapKey\" and \"unwrapKey\".\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.10</b> ECGOST Key Pairs</li>\n *      <li><b>GOST 28147</b> Key for encryption GOST 28147 modes</li>\n *      <li><b>GOST 28147-KW</b> Key for wrapping GOST 28147 modes</li>\n *      <li><b>GOST R 34.12</b> Key for encryption GOST R 34.12 modes</li>\n *      <li><b>GOST R 34.12-KW</b> Key for wrapping GOST R 34.12 modes</li>\n *      <li><b>GOST R 34.11-KDF</b> Key for Derivation Algorithm</li>\n *  </ul>\n *  For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}<br>\n *  Note: Generation key for GOST R 34.10-94 not supported.\n *\n * @memberOf SubtleCrypto\n * @method generateKey\n * @instance\n * @param {AlgorithmIdentifier} algorithm Key algorithm identifier\n * @param {boolean} extractable Whether or not the raw keying material may be exported by the application\n * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key\n * @returns {Promise} Promise that resolves with {@link Key} or {@link KeyPair} in according to key algorithm\n */\nSubtleCrypto.prototype.generateKey = function (algorithm, extractable, keyUsages) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.generateKey(algorithm, extractable, keyUsages);\n\n        var privateAlgorithm = algorithm.privateKey,\n                publicAlgorithm = algorithm.publicKey;\n        algorithm = normalize(algorithm, 'generateKey');\n        if (privateAlgorithm)\n            privateAlgorithm = normalize(privateAlgorithm, 'generateKey');\n        else\n            privateAlgorithm = algorithm;\n        if (publicAlgorithm)\n            publicAlgorithm = normalize(publicAlgorithm, 'generateKey');\n        else\n            publicAlgorithm = algorithm;\n        return execute(algorithm, 'generateKey', []).then(function (data) {\n            if (data.publicKey && data.privateKey)\n                return convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, data.publicKey, data.privateKey);\n            else\n                return convertKey(algorithm, extractable, keyUsages, data);\n        });\n    });\n}; // </editor-fold>\n\n/**\n * The deriveKey method returns a new Promise object that will key(s) using\n * the specified algorithm identifier. Key can be used in according with\n * KeyUsage sequence. The recognized key usage values are \"encrypt\", \"decrypt\",\n * \"sign\", \"verify\", \"deriveKey\", \"deriveBits\", \"wrapKey\" and \"unwrapKey\".\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveKey}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.10-DH</b> ECDH Key Agreement mode</li>\n *      <li><b>GOST R 34.11-KDF</b> Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-PBKDF2</b> Password Based Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-PFXKDF</b> PFX Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-CPKDF</b> Password Based Key for CryptoPro Derivation Algorithm</li>\n *      <li><b>SHA-PBKDF2</b> Password Based Key for Derivation Algorithm</li>\n *      <li><b>SHA-PFXKDF</b> PFX Key for Derivation Algorithm</li>\n *  </ul>\n *  For additional modes see {@link GostSign} and {@link GostDigest}\n *\n * @memberOf SubtleCrypto\n * @method deriveKey\n * @instance\n * @param {AlgorithmIdentifier} algorithm Algorithm identifier\n * @param {Key} baseKey Derivation key object\n * @param {AlgorithmIdentifier} derivedKeyType Derived key algorithm identifier\n * @param {boolean} extractable Whether or not the raw keying material may be exported by the application\n * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key\n * @returns {Promise} Promise that resolves with {@link Key}\n */\nSubtleCrypto.prototype.deriveKey = function (algorithm, baseKey,\n        derivedKeyType, extractable, keyUsages) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.deriveKey(algorithm, baseKey,\n                    derivedKeyType, extractable, keyUsages);\n\n        algorithm = normalize(algorithm, 'deriveKey');\n        derivedKeyType = normalize(derivedKeyType, 'generateKey');\n        algorithm.keySize = derivedKeyType.keySize;\n        if (algorithm['public']) {\n            algorithm['public'].algorithm = normalize(algorithm['public'].algorithm);\n            algorithm['public'] = extractKey('deriveKey', algorithm, algorithm['public']);\n        }\n        return execute(algorithm, 'deriveKey', [extractKey('deriveKey', algorithm, baseKey)]).then(function (data) {\n            return convertKey(derivedKeyType, extractable, keyUsages, data);\n        });\n    });\n}; // </editor-fold>\n\n/**\n * The deriveBits method returns length bits on baseKey using the\n * specified algorithm identifier.\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveBits}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.10-DH</b> ECDH Key Agreement mode</li>\n *      <li><b>GOST R 34.11-KDF</b> Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-PBKDF2</b> Password Based Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-PFXKDF</b> PFX Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-CPKDF</b> Password Based Key for CryptoPro Derivation Algorithm</li>\n *      <li><b>SHA-PBKDF2</b> Password Based Key for Derivation Algorithm</li>\n *      <li><b>SHA-PFXKDF</b> PFX Key for Derivation Algorithm</li>\n *  </ul>\n *  For additional modes see {@link GostSign} and {@link GostDigest}\n *\n * @memberOf SubtleCrypto\n * @method deriveBits\n * @instance\n * @param {AlgorithmIdentifier} algorithm Algorithm identifier\n * @param {Key} baseKey Derivation key object\n * @param {number} length Length bits\n * @returns {Promise} Promise that resolves with {@link CryptoOperationData}\n */\nSubtleCrypto.prototype.deriveBits = function (algorithm, baseKey, length) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.deriveBits(algorithm, baseKey, length);\n\n        algorithm = normalize(algorithm, 'deriveBits');\n        if (algorithm['public'])\n            algorithm['public'] = extractKey('deriveBits', algorithm, algorithm['public']);\n        return execute(algorithm, 'deriveBits', [extractKey('deriveBits', algorithm, baseKey), length]);\n    });\n}; // </editor-fold>\n\n/**\n * The importKey method returns a new Promise object that will key(s) using\n * the specified algorithm identifier. Key can be used in according with\n * KeyUsage sequence. The recognized key usage values are \"encrypt\", \"decrypt\",\n * \"sign\", \"verify\", \"deriveKey\", \"deriveBits\", \"wrapKey\" and \"unwrapKey\".<br><br>\n * Parameter keyData contains data in defined format.\n * The suppored key format values are:\n *  <ul>\n *      <li>'raw' - An unformatted sequence of bytes. Intended for secret keys.</li>\n *      <li>'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.</li>\n *      <li>'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.</li>\n *  </ul>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-importKey}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.10-94</b> GOST Private and Public keys</li>\n *      <li><b>GOST R 34.10</b> ECGOST Private and Public keys</li>\n *      <li><b>GOST 28147</b> Key for encryption GOST 28147 modes</li>\n *      <li><b>GOST 28147-KW</b> Key for key wrapping GOST 28147 modes</li>\n *      <li><b>GOST R 34.12</b> Key for encryption GOST 34.12 modes</li>\n *      <li><b>GOST R 34.12-KW</b> Key for key wrapping GOST 34.12 modes</li>\n *      <li><b>GOST R 34.11-KDF</b> Key for Derivation Algorithm</li>\n *  </ul>\n *  For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}<br>\n *\n * @memberOf SubtleCrypto\n * @method importKey\n * @instance\n * @param {KeyFormat} format Key format Format specifies a serialization format for a key\n * @param {CryptoOperationData} keyData\n * @param {AlgorithmIdentifier} algorithm Key algorithm identifier\n * @param {boolean} extractable Whether or not the raw keying material may be exported by the application\n * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key\n * @returns {Promise} Promise that resolves with {@link Key}\n */\nSubtleCrypto.prototype.importKey = function (format, keyData, algorithm, extractable, keyUsages) // <editor-fold defaultstate=\"collapsed\">\n{\n    var type;\n    return new Promise(call).then(function () {\n        if (checkNative(algorithm))\n            return rootCrypto.subtle.importKey(format, keyData, algorithm, extractable, keyUsages);\n\n        if (format === 'raw') {\n            algorithm = normalize(algorithm, 'importKey');\n            if (keyUsages && keyUsages.indexOf) {\n                var name = algorithm.name.toUpperCase().replace(/[\\.\\s]/g, '');\n                if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0)\n                    type = 'private';\n                else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0)\n                    type = 'public';\n            }\n            return keyData;\n        } else {\n            var key;\n            if (format === 'pkcs8')\n                key = gostCrypto.asn1.GostPrivateKeyInfo.decode(keyData).object;\n            else if (format === 'spki')\n                key = gostCrypto.asn1.GostSubjectPublicKeyInfo.decode(keyData).object;\n            else\n                throw new NotSupportedError('Key format not supported');\n\n            algorithm = normalize(key.algorithm, 'importKey');\n            type = key.type;\n            if (extractable !== false)\n                extractable = extractable || key.extractable;\n            if (keyUsages) {\n                for (var i = 0; i < keyUsages.length; i++) {\n                    if (key.usages.indexOf(keyUsages[i]) < 0)\n                        throw DataError('Key usage not valid for this key');\n                }\n            } else\n                keyUsages = key.usages;\n            var data = key.buffer, keySize = algorithm.keySize, dataLen = data.byteLength;\n            if (type === 'public' || keySize === dataLen)\n                return data;\n            else {\n                // Remove private key masks\n                if (dataLen % keySize > 0)\n                    throw new DataError('Invalid key size');\n                algorithm.mode = 'MASK';\n                algorithm.procreator = 'VN';\n                var chain = [];\n                for (var i = keySize; i < dataLen; i += keySize) {\n                    chain.push((function (mask) {\n                        return function (data) {\n                            return execute(algorithm, 'unwrapKey', [mask, data]).then(function (data) {\n                                var next = chain.pop();\n                                if (next)\n                                    return next(data);\n                                else {\n                                    delete algorithm.mode;\n                                    return data;\n                                }\n                            });\n                        };\n                    })(new Uint8Array(data, i, keySize)));\n                }\n                return chain.pop()(new Uint8Array(data, 0, keySize));\n            }\n        }\n    }).then(function (data) {\n        return convertKey(algorithm, extractable, keyUsages, data, type);\n    });\n}; // </editor-fold>\n\n/**\n * The exportKey method returns a new Promise object that will key data in\n * defined format. <br><br>\n * The suppored key format values are:\n *  <ul>\n *      <li>'raw' - An unformatted sequence of bytes. Intended for secret keys.</li>\n *      <li>'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.</li>\n *      <li>'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.</li>\n *  </ul>\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST R 34.10-94</b> GOST Private and Public keys</li>\n *      <li><b>GOST R 34.10</b> ECGOST Private and Public keys</li>\n *      <li><b>GOST 28147</b> Key for encryption GOST 28147 modes</li>\n *      <li><b>GOST 28147-KW</b> Key for key wrapping GOST 28147 modes</li>\n *      <li><b>GOST R 34.12</b> Key for encryption GOST R 34.12 modes</li>\n *      <li><b>GOST R 34.12-KW</b> Key for key wrapping GOST R 34.12 modes</li>\n *      <li><b>GOST R 34.11-KDF</b> Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-PBKDF2</b> Import Password for Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-PFXKDF</b> Import PFX Key for Derivation Algorithm</li>\n *      <li><b>GOST R 34.11-CPKDF</b> Import Password Key for CryptoPro Derivation Algorithm</li>\n *      <li><b>SHA-PBKDF2</b> Import Password for Key for Derivation Algorithm</li>\n *      <li><b>SHA-PFXKDF</b> Import PFX Key for Derivation Algorithm</li>\n *  </ul>\n *  For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}<br>\n *\n * @memberOf SubtleCrypto\n * @method exportKey\n * @instance\n * @param {KeyFormat} format Format specifies a serialization format for a key\n * @param {Key} key Key object\n * @returns {Promise} Promise that resolves with {@link CryptoOperationData}\n */\nSubtleCrypto.prototype.exportKey = function (format, key) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (key && checkNative(key.algorithm))\n            return rootCrypto.subtle.exportKey(format, key);\n\n        if (!key.extractable)\n            throw new InvalidAccessError('Key not extractable');\n\n        var raw = extractKey(null, null, key);\n        if (format === 'raw')\n            return raw;\n        else if (format === 'pkcs8' && key?.algorithm?.id) {\n            if (key.algorithm.procreator === 'VN') {\n                // Add masks for ViPNet\n                var algorithm = key.algorithm, mask;\n                algorithm.mode = 'MASK';\n                return execute(algorithm, 'generateKey').then(function (data) {\n                    mask = data;\n                    return execute(algorithm, 'wrapKey', [mask, key.buffer]);\n                }).then(function (data) {\n                    delete algorithm.mode;\n                    var d = new Uint8Array(data.byteLength + mask.byteLength);\n                    d.set(new Uint8Array(data, 0, data.byteLength));\n                    d.set(new Uint8Array(mask, 0, mask.byteLength), data.byteLength);\n                    var buffer = d.buffer;\n                    buffer.enclosed = true;\n                    return gostCrypto.asn1.GostPrivateKeyInfo.encode({\n                        algorithm: algorithm,\n                        buffer: buffer\n                    });\n                });\n            } else\n                return gostCrypto.asn1.GostPrivateKeyInfo.encode(key);\n        } else if (format === 'spki' && key?.algorithm?.id)\n            return gostCrypto.asn1.GostSubjectPublicKeyInfo.encode(key);\n        else\n            throw new NotSupportedError('Key format not supported');\n    });\n}; // </editor-fold>\n\n/**\n * The wrapKey method returns a new Promise object that will wrapped key(s).\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-wrapKey}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST 28147-KW</b> Key Wrapping GOST 28147 modes</li>\n *      <li><b>GOST R 34.12-KW</b> Key Wrapping GOST R 34.12 modes</li>\n *      <li><b>GOST 28147-MASK</b> Key Mask GOST 28147 modes</li>\n *      <li><b>GOST R 34.12-MASK</b> Key Mask GOST R 34.12 modes</li>\n *      <li><b>GOST R 34.10-MASK</b> Key Mask GOST R 34.10 modes</li>\n *  </ul>\n *  For additional modes see {@link GostCipher}<br>\n *\n * @memberOf SubtleCrypto\n * @method wrapKey\n * @instance\n * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format.\n * @param {Key} key Key object\n * @param {Key} wrappingKey Wrapping key object\n * @param {AlgorithmIdentifier} wrapAlgorithm Algorithm identifier\n * @returns {Promise} Promise that resolves with {@link CryptoOperationData}\n */\nSubtleCrypto.prototype.wrapKey = function (format, key, wrappingKey, wrapAlgorithm) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(wrapAlgorithm))\n            return rootCrypto.subtle.wrapKey(format, key, wrappingKey, wrapAlgorithm);\n\n        wrapAlgorithm = normalize(wrapAlgorithm, 'wrapKey');\n        var keyData = extractKey(null, null, key);\n        if (wrapAlgorithm.procreator === 'SC' && key.type === 'private')\n            keyData = swapBytes(keyData);\n        return execute(wrapAlgorithm, 'wrapKey',\n                [extractKey('wrapKey', wrapAlgorithm, wrappingKey), keyData]).then(function (data) {\n            if (format === 'raw')\n                return data;\n            else\n                throw new NotSupportedError('Key format not supported');\n        });\n    });\n}; // </editor-fold>\n\n/**\n * The unwrapKey method returns a new Promise object that will unwrapped key(s).\n * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey}<br><br>\n *\n * Supported algorithm names:\n *  <ul>\n *      <li><b>GOST 28147-KW</b> Key Wrapping GOST 28147 modes</li>\n *      <li><b>GOST R 34.12-KW</b> Key Wrapping GOST R 34.12 modes</li>\n *      <li><b>GOST 28147-MASK</b> Key Mask GOST 28147 modes</li>\n *      <li><b>GOST R 34.12-MASK</b> Key Mask GOST R 34.12 modes</li>\n *      <li><b>GOST R 34.10-MASK</b> Key Mask GOST R 34.10 modes</li>\n *  </ul>\n *  For additional modes see {@link GostCipher}<br>\n *\n * @memberOf SubtleCrypto\n * @method unwrapKey\n * @instance\n * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format.\n * @param {CryptoOperationData} wrappedKey Wrapped key data\n * @param {Key} unwrappingKey Unwrapping key object\n * @param {AlgorithmIdentifier} unwrapAlgorithm Algorithm identifier\n * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm Key algorithm identifier\n * @param {boolean} extractable Whether or not the raw keying material may be exported by the application\n * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key\n * @returns {Promise} Promise that resolves with {@link Key}\n */\nSubtleCrypto.prototype.unwrapKey = function (format, wrappedKey, unwrappingKey,\n        unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) // <editor-fold defaultstate=\"collapsed\">\n{\n    return new Promise(call).then(function () {\n        if (checkNative(unwrapAlgorithm))\n            return rootCrypto.subtle.unwrapKey(format, wrappedKey, unwrappingKey,\n                    unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages);\n\n        unwrapAlgorithm = normalize(unwrapAlgorithm, 'unwrapKey');\n        unwrappedKeyAlgorithm = normalize(unwrappedKeyAlgorithm, 'importKey');\n        if (format !== 'raw')\n            throw new NotSupportedError('Key format not supported');\n\n        return execute(unwrapAlgorithm, 'unwrapKey', [extractKey('unwrapKey', unwrapAlgorithm, unwrappingKey), wrappedKey]).then(function (data) {\n            var type;\n            if (unwrappedKeyAlgorithm && unwrappedKeyAlgorithm.name) {\n                var name = unwrappedKeyAlgorithm.name.toUpperCase().replace(/[\\.\\s]/g, '');\n                if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0)\n                    type = 'private';\n                else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0)\n                    type = 'public';\n            }\n            if (unwrapAlgorithm.procreator === 'SC' && type === 'private')\n                data = swapBytes(data);\n            return convertKey(unwrappedKeyAlgorithm, extractable, keyUsages, data, type);\n        });\n    });\n}; // </editor-fold>\n\n/**\n * The subtle attribute provides an instance of the SubtleCrypto\n * interface which provides low-level cryptographic primitives and\n * algorithms.\n *\n * @memberOf gostCrypto\n * @type SubtleCrypto\n */\ngostCrypto.subtle = new SubtleCrypto();\n\n/**\n * The getRandomValues method generates cryptographically random values.\n *\n * First try to use Web Crypto random genereator. Next make random\n * bytes based on standart Math.random mixed with time and mouse pointer\n *\n * @memberOf gostCrypto\n * @param {(CryptoOperationData)} array Destination buffer for random data\n */\ngostCrypto.getRandomValues = function (array) // <editor-fold defaultstate=\"collapsed\">\n{\n    // Execute randomizer\n    GostRandom = GostRandom || root.GostRandom;\n    var randomSource = GostRandom ? new GostRandom() : rootCrypto;\n    if (randomSource.getRandomValues)\n        randomSource.getRandomValues(array);\n    else\n        throw new NotSupportedError('Random generator not found');\n}; // </editor-fold>\n// </editor-fold>\n\nexport default gostCrypto;\n"
  },
  {
    "path": "src/core/vendor/gost/gostDigest.mjs",
    "content": "/**\n * GOST R 34.11-94 / GOST R 34.11-12 implementation\n * 1.76\n * 2014-2016, Rudolf Nickolaev. All rights reserved.\n *\n * Exported for CyberChef by mshwed [m@ttshwed.com]\n */\n\n/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Converted to JavaScript from source https://www.streebog.net/\n * Copyright (c) 2013, Alexey Degtyarev.\n * All rights reserved.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n import GostRandom from './gostRandom.mjs';\n import GostCipher from './gostCipher.mjs';\n import crypto from 'crypto';\n\n/*\n    * GOST R 34.11\n    * Common methods\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\nvar root = {};\nvar rootCrypto = crypto\n\nvar DataError = Error,\n        NotSupportedError = Error;\n\n// Copy len values from s[sOfs] to d[dOfs]\nfunction arraycopy(s, sOfs, d, dOfs, len) {\n    for (var i = 0; i < len; i++)\n        d[dOfs + i] = s[sOfs + i];\n}\n\n// Swap bytes in buffer\nfunction swap(s) {\n    var src = new Uint8Array(s),\n            dst = new Uint8Array(src.length);\n    for (var i = 0, n = src.length; i < n; i++)\n        dst[n - i - 1] = src[i];\n    return dst.buffer;\n}\n\n// Convert BASE64 string to Uint8Array\n// for decompression of constants and precalc values\nfunction b64decode(s) {\n    // s = s.replace(/[^A-Za-z0-9\\+\\/]/g, '');\n    var n = s.length,\n            k = n * 3 + 1 >> 2, r = new Uint8Array(k);\n\n    for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) {\n        m4 = i & 3;\n        var c = s.charCodeAt(i);\n\n        c = c > 64 && c < 91 ?\n                c - 65 : c > 96 && c < 123 ?\n                c - 71 : c > 47 && c < 58 ?\n                c + 4 : c === 43 ?\n                62 : c === 47 ?\n                63 : 0;\n\n        u24 |= c << 18 - 6 * m4;\n        if (m4 === 3 || n - i === 1) {\n            for (m3 = 0; m3 < 3 && j < k; m3++, j++) {\n                r[j] = u24 >>> (16 >>> m3 & 24) & 255;\n            }\n            u24 = 0;\n\n        }\n    }\n    return r.buffer;\n}\n\n// Random seed\nfunction getSeed(length) {\n    GostRandom = GostRandom || root.GostRandom;\n    var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto;\n    if (randomSource.getRandomValues) {\n        var d = new Uint8Array(Math.ceil(length / 8));\n        randomSource.getRandomValues(d);\n        return d;\n    } else\n        throw new NotSupportedError('Random generator not found');\n}\n\n// Check buffer\nfunction buffer(d) {\n    if (d instanceof ArrayBuffer)\n        return d;\n    else if (d && d?.buffer instanceof ArrayBuffer)\n        return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ?\n                d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer;\n    else\n        throw new DataError('ArrayBuffer or ArrayBufferView required');\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.11 or GOST R 34.11-12<br><br>\n *\n * http://tools.ietf.org/html/rfc6986\n *\n * The digest method returns digest data in according to GOST R 4311-2012.<br>\n * Size of digest also defines in algorithm name.\n *  <ul>\n *      <li>GOST R 34.11-256-12 - 256 bits digest</li>\n *      <li>GOST R 34.11-512-12 - 512 bits digest</li>\n *  </ul>\n *\n * @memberOf GostDigest\n * @method digest\n * @instance\n * @param {(ArrayBuffer|TypedArray)} data Data\n * @returns {ArrayBuffer} Digest of data\n */\nvar digest2012 = (function () // <editor-fold defaultstate=\"collapsed\">\n{\n    // Constants\n    var buffer0 = new Int32Array(16); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n\n    var buffer512 = new Int32Array(16); // [512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n    buffer512[0] = 512;\n\n    // Constant C\n    var C = (function (s) {\n        var h = new Int32Array(b64decode(s)),\n                r = new Array(12);\n        for (var i = 0; i < 12; i++)\n            r[i] = new Int32Array(h.buffer, i * 64, 16);\n        return r;\n    })(\n            'B0Wm8lllgN0jTXTMNnR2BRXTYKQIKkKiAWlnkpHgfEv8xIV1jbhOcRbQRS5DdmovH3xlwIEvy+vp2soe2lsIsbebsSFwBHnmVs3L1xui3VXKpwrbwmG1XFiZ1hJrF7WaMQG1Fg9e1WGYKyMKcur+89e1cA9GneNPGi+dqYq1o2+yCroK9ZYemTHbeoZD9LbCCdtiYDc6ycGxnjWQ5A/i03t7KbEUderyix+cUl9e8QY1hD1qKPw5Cscvzius3HT1LtHjhLy+DCLxN+iToepTNL4DUpMzE7fYddYD7YIs16k/NV5orRxynX08XDN+hY5I3eRxXaDhSPnSZhXos98f71f+bHz9WBdg9WPqqX6iVnoWGicjtwD/36P1OiVHF82/vf8PgNc1njVKEIYWHxwVf2MjqWwMQT+amUdHraxr6ktufWRGekBo+jVPkDZyxXG/tsa+wmYf8gq0t5oct6b6z8aO8Jq0mn8YbKRCUfnEZi3AOTB6O8Okb9nTOh2urk+uk9QUOk1WhojzSjyiTEUXNQQFSiiDaUcGNyyCLcWrkgnJk3oZMz5H08mHv+bHxp45VAkkv/6GrFHsxaruFg7H9B7nAr/UDX+k' +\n            '2ahRWTXCrDYvxKXRK43RaZAGm5LLK4n0msTbTTtEtIke3jaccfi3TkFBbgwCqucDp8mTTUJbH5vbWiODUURhcmAqH8uS3DgOVJwHppqKK3uxzrLbC0QKgIQJDeC3Vdk8JEKJJRs6fTreXxbs2JpMlJsiMRZUWo837ZxFmPvHtHTDtjsV0fqYNvRSdjswbB56SzNprwJn558DYTMbiuH/H9t4iv8c50GJ8/PkskjlKjhSbwWApt6+qxst84HNpMprXdhvwEpZot6Ybkd9Hc2678q5SOrvcR2KeWaEFCGAASBhB6vru2v62JT+WmPNxgIw+4nI79CezXsg1xvxSpK8SJkbstnVF/T6UijhiKqkHeeGzJEYne+AXZufITDUEiD4dx3fvDI8pM16sUkEsIAT0roxFvFn5443');\n\n    // Precalc Ax\n    var Ax = (function (s) {\n        return new Int32Array(b64decode(s));\n    })(\n            '5vh+XFtxH9Alg3eACST6FshJ4H6FLqSoW0aGoY8GwWoLMumi13tBbqvaN6RngVxm9heWqBpoZnb13AtwY5GVS0hi84235kvx/1ximmi9hcXLgn2m/NdXlWbTba9pufCJNWyfdEg9g7B8vOyxI4yZoTanAqwxxHCNnrao0C+839aLGfpR5bOuN5zPtUCKEn0LvAx4tQggj1rlM+OEIojs7c7Cx9N3wV/S7HgXtlBdD165TMLAgzaHHYwgXbTLCwStdjyFWyigiS9YjRt59v8yVz/s9p5DEZM+D8DTn4A6GMnuAQom9fOtgxDv6PRBGXmmXc2hDH3pOhBKG+4dEkjpLFO/8tshhHM5tPUMz6aiPQlftLyc2EeYzeiKLYsHHFb5f3dxaVp1apzF8C5xoLoevKZj+atCFeZyLrGeIt5fu3gNuc4PJZS6FIJSDmOXZk2ELwMeagII6phcfyFEob5r8Ho3yxzRY2Lbg+COK0sxHGTPcEebq5YOMoVrqYa53ucetUeMh3r1bOm4/kKIX2HW/RvdAVaWYjjIYiFXkj74qS78l/9CEUR2+J19NQhWRSzrTJDJsOCnElYjCFAt+8sBbC16A/qnpkhF' +\n            '9G6LOL/GxKu9vvj91HfeujqsTOvIB5t58JyxBeiHnQwn+moQrIpYy4lg58FAHQzqGm+BHko1aSiQxPsHc9GW/0NQGi9gnQqf96UW4MY/N5Yc5KazuNqSUhMkdSw44IqbpahkczvsFU8r8SRXVUmzP9dm2xVEDcXHp9F5455Ct5La3xUaYZl/04agNF7AJxQjONVRe22pOaRlGPB3EEADtAJ5HZClrqLdiNJniZxKXQqTD2bfCihlwk7p1CBFCbCLMlU4kWaFKSpBKQe/xTOoQrJ+K2JUTcZzbFMERWKV4Ada9AbpU1GQih8vO2vBI2Fvw3sJ3FJV5cY5Z9Ezsf5oRCmIOcfw5xHiQJuH9xlk+aLpOK3D20sHGQwLTkf5w+v0VTTVdtNriENGEKBa64sC2CDDzfWCMvJRbeGEDb7Cseeg6N4GsPodCHuFS1QNNDM7QuKaZ7zKW3/YpgiKxDfdDsY7s6nZQ+2BIXFNvV5lo7FnYe3nte6haSQx98jVc6v21R/GheGjZxpeBjzUBBDJLSg6uY8ssEACj+vAbLLy95AX1k8Rb6HTPOBzWfGpnuSqeE7WjHTNwAZuKhnVxztC2ocStBYccEXD' +\n            'NxWC5O2TIW2s45BBSTn2/H7F8SGGIjt8wLCUBCusFvv510U3mlJ+v3N8Py6jtoFoM+e42brSeMqpoyo0wi/+u+SBY8z+370NjllAJG6lpnBRxu9LhCrR5CK60GUnnFCM2RSIwhhgjO4xnqVJH3zaF9OU4SgTTJxgCUv0MnLV47Ob9hKlpKrXkcy72kPSb/0PNN4fPJRq0lBPW1RomV7ha9+fr2/qj3eUJkjqWHDdCSu/x+Vtcdl8Z93msv9PIdVJPCdrRjroYAORdntPr4bHH2ihPng11LmgtowRXwMMn9QUHdLJFlggAZg9j33dUySsZKpwP8wXUlTCyYmUjgK0Jj5edtafRsLeUHRvA1h9gARF2z2CknLx5WBYSgKbVgvz+65Ypz/83GKhWl5ObK1M6EupblXOH7jMCPl0eq6CslPBAhRM9/tHG58EKJjz6442BosnrfLv+3rtypf+jApevneOBRP099jPMCwlAcMri/eNkt38F1xVTfhlxX9GBS9f6vMwG6Ky9CSqaLfsu9YNhpmPDzUBBHVMAAAAAAAAAADxLjFNNNDM7HEFIr4GGCO1rygNmTDABcGX/VziXWk8ZRmkHMYzzJoV' +\n            'lYRBcvjHnrjcVDK3k3aEqZQ2wTokkM9YgCsT8zLI71nEQq45fO1PXPoc2O/jq42C8uWslU0pP9Fq2CPokHobfU0iSfg88EO2A8ud2Hn58z3eLS8nNtgmdCpDpB+JHuLfb5iZnRtsEzrUrUbNPfQ2+rs131AmmCXAlk/cqoE+bYXrQbBTfuWlxAVAunWLFghHpBrkO+e7RK/juMQp0GcXl4GZk7vun765rpqN0eyXVCHzVyzdkX5uMWOT19rir/jOR6IgEjfcUzijI0PeyQPuNXn8VsSompHmAbKASNxXUeASlvVk5Lfbe3X3GINRWXoS222VUr3OLjMenbsjHXQwj1INcpP90yLZ4gpEYQwwRnf+7uLStOrUJcow/e4ggAZ1YerKSkcBWhPnSv4UhyZOMCzIg7J78RmlFmTPWbP2gtyoEap8HnivWx1WJvtkjcOytz6RF99bzjTQX3zwarVvXf0lfwrNEycYV03I5nbFKp4HOaflLriqmlSGVT4PPNmjVv9IrqqSe36+dWUlrY4th30ObPn/28hBOx7MoxRQyplpE74w6YPoQK1REAmVbqccsbW2ui20NU5Eab3KTiWgBRWvUoHKD3Hh' +\n            'dEWYy40OK/JZP5sxKqhjt++zim4ppPxja2qjoEwtSp09lesO5r8x46KRw5YVVL/VGBacju+by/URXWi8nU4oRrqHXxj6z3Qg0e38uLbiPr2wBzby8eNkroTZKc5libb+cLei9tpPclUOclPXXG1JKQTyOj1XQVmnCoBp6gssEI5J0HPFa7EaEYqrehk55P/XzQlaCw44rO/J+2A2WXn1SJK95pfWfzQix4kz4QUUvGHhwdm5dcm1StImYWDPG82AmkSS7Xj9hnGzzKsqiBqXk3LOv2Z/4dCI1tRbXZhalCfIEagFjD9V3mX1tDGWtQYZ90+WsdZwbkOFnR6Ly0PTNlqrioXM+j2E+ce/mcKV/P2iH9Wh3ktjD82z73Y7i0VtgD9Z+Hz3w4WyfHO+XzGRPJjjrGYzsEghv2FnTCa4+BgP+8mVxMEwyKqghiAQdhqYYFfzQiEBFqr2PHYMBlTMNS3bRcxmfZBCvPRalkvUA4Jo6KDD7zxvPae9ktJp/3O8KQriAgHtIoe33jTN6IWBj9kB7qfdYQWb1vonMhmgNVPVbxrodMzOyeoxJFwug/VUcDRVXaB75JnOJtKsVue+9/0WGFelBU44' +\n            'ag59pFJ0NtFb2Go4HN6f8sr3dWIxdwwysJqu2eJ5yNBd7xCRxgZ02xEQRqJRXlBFI1Ns5HKYAvzFDLz39bY8+nOhaIfNFx8DfSlBr9nyjb0/Xj60Wk87nYTu/jYbZ3FAPbjj0+cHYnEaOij58g/SSH68fHW0nnYndOXyk8frVlwY3PWeT0eLpAxu9E+prctSxpmBLZjax2B4iwbcbkadDvxl+Op1IexOMKX3IZ6OC1Ur7D9lvKV7a93QSWm68bdemZBM2+OU6lcUsgHR5upA9ruwwIJBKErdUPIEY7+PHf/o1/k7k8usuE2Mto5HfIbowd0bOZImjj98WqESCdYvyy89mKvbNcmuZxNpViv9X/UVweFsNs7igB1+su3485sX2pTTfbAN/gGHe8PsdguK2suEld/hU65EBaJHc7e0ELMShXt4PDKr3463cNBoElE7U2c5udLj5mVYTVficbJkaNeJx4/JhJclqTW7+n0a4QKLFTej36ZBiNDNXZvDeN56Ssgsmk2Az7dCd38bg722IHLSiDodM711XnotS6tqj0H02qtruxyV2ZBc/+f9jTG2g6pkIhGbOB/ArvuEQgIsSaD5CMZjAzrj' +\n            'pCivCASTiCat5Bw0GopTx65xIe535qhdxH9cSiWSnoy1OOmqVc3YYwY3eqna2OspoYroe7MnmJVu39pqNeSEFGt9nRmCUJSn1Bz6VaTobL/lyu3J6kLFnKNsNRwOb8F5UYHk3m+rv4n/8MUwGE0X1J1B6xWEBFiSHA1SUCjXOWHxeOwYDKiFapoFcQGO+BHNQJGifD7178wZrxUjn2Mp0jR0UO/5HrmQ4RtKB43Sd1m5Vh3l/GATMZEvH1otqZPAFlTctluiGRo+Ld4JimuZ64pm1x4PguP+jFGtt9VaCNdFM+UPiUH/fwLm3We9SFns4Giqul321S/CSCbj/0p1pWw5Bw2IrN34ZIZUjEaRpG/Rvr0mE1x8DLMPkwOPFTNKgtmEn8G/mmmcMguoVCD65PpSgkOv+QdnntTWz+loowi4Jf1YLESxR5t2kbxe3LO7x+phkEj+ZRYQY6YfgXryM0fVOGg0CaaTY8LOmExt7TAqn9/YbIHZHXseOwYDKmaUZmCJ6/vZ/YMKWY7mc3UgewdEmhQK/ElfLKilcbZZMjQfmG+KRbvC+zgapKBQs3LCVCOjrdgfrzoXJzwLi4a7bP6DJY3IabWi' +\n            'KHkCv9HJgPH1qUvWazg3r4iACnmyyroSVVBDEAg7DUzfNpQOB7nusgTRp85nkLLFYSQT//EltNwm8SuXxSwST4YII1GmLyis75NjL5k35ec1B7BSKTob5ucsMK5XCpxw01hgQa4UJeDeRXSz151MxJK6IoBAxWha8AsMpdyMJxy+Eofx9pxabvOeMX+x4NyGSV0RQCDsNC1pm0B+PxjNS9yjqdRq1RUoDR0U8nmJaSQAAAAAAAAAAFk+t1+hlsYeLk54FgsRa9htSuewWIh/juZf0BOHLj4Gem3bu9MOxOKsl/yJyq7xsQnMszweGdvhifPqxGLuGGR3cM9JqoetxlbFfsplV/bWA5U92m1s+5o2ko2IRFbgfB7rjzeVn2CNMdYXnE6qqSNvrDrX5cAmYkMEn6ZTmRRWq9NmncBSuO6vAsFTp8IKKzzLA243I8AHk8nCPZDhyizDO8ZeL27X00z/VjOXWCSeselOZDJdaqY34W01lHJCCnn45mG+Yj94UhTZBALHRBNILvH98MiWWxP2m8XsFgmpDogpKBTlkr5OGYtUKhB9cszAD8vrr+cbG0nIRCIrcD4lZBZNqEDp1SDGUT4f9Plm' +\n            'usMgP5EM6Kvy7dHCYcR+8IFMuUWs02Hzlf64lEo5IQVcnPAsFiLWrZcYZfP3cXjpvYe6K5vwofREQAWyWWVdCe11vkgkf7wLdZYSLhfP9Cq0SwkXhel6FZZrhU4nVdqf7uCDkkkTR5EyQypGI8ZSuahGW0etPkN0+LRfJBKxXoskF/bweGRLo/shYv5/3aURS7vMJ52kbcEBc+C90CSidiIgjFmivKCKj8SQbbg2803kuQ10OmZn6nFHteBwX0bvJ4LLKhUIsDnsBl719FsefSG1sYPP0FsQ2+czwGApXHefpzZyOUwBfs9VMhGGwxyB2HIOGg1Fp+07j5l6Pd+JWDr8ecft+ysu6aQZhkPvDs5fCc32e04tN09qa+n6NN8Etq3UcDihI/mNIk0KBX6qocliSLhcG/eo4/2XYDCaLrULKm5bo1GCDetCxOH+p1cilI1YKZodg3N/z5zIZLrUUaVbT7XUtypQCL9Tgc49eZdGptjV5C0E5dIrgPx+MIeWV7aed7VzVKA5aUQdgJfQtDMwyvvz4vDP4o533eC+jMNisS4lnElPRqbOcm+529HKQeJCwe7RTbp2Ay/0eqMPsEWyaKk6zeTM' +\n            'r38L6IRUnQgEg1SzwUaCY5JUNcLIDv7S7k438n/f+6cWejOSDGDxTfsSO1LqA+WESgyrU/27kAed6vY4D3iKGctI7FWPDLMqtZ3Estb+9+Dc28oi9PPsthHfWBNUmpxA4z/e31aKztOgwcgSQyLpwwela4FY+m0NdyeVebHh893ZsYt0QirABLjsLZ//q8KU9Kz4qC11kU97v2mx7ytoeMT2L69Iesfhds6AnMZ+XQxnEdiPkuTBTGJ7mdkkPe3+I0qlw9+2i1GQmx8VJi2/bU9m6gVLYry1GuLPWlKqaui+oFP70M4BSO1oCMDmYxTJQ/4WzRWoJxDNBJIxoGlw9ue8imyXzEywM3zoNfyzucBl3vJYfMeA81IhTt5BMrtQlfFeQ5D0k9+HCDliXdLg8UExPBr7i2avkXIK8FGyEbxHfUJ+1O6lcy47TO72474lgmJ4NOsLzEOcA+PdeOckyCh3MorZhn35FLUZReJDsPJXSw+I9+uX4oi2+piapJQ6GcTwaMsWhYZQ7mQJrxH6733zF9XATqukelZ8VJi0xqm2u/uAT0IYjjzCK887xc0L0EM26qo5dxPwL6wb7DMTLCUG26fw00iN' +\n            '1+Zda/LDGh5eubIWH/gg9YQuBlDEbg+fcWvrHZ6EMAGpM3WMqzFe1D/kFP2ieSJlJ8nxcB7wCTJzpMHKcKdxvpQYS6bnaz0OQNgp/4wUyH4PvsP6x3Z0yzYWqWNKapVyjxORGcJe+Tf1Re1NWuo/nugCSZZQujh7ZDfnvQtYLiLmVZ+J4FPiYYCtUuMFKI38bcVaI+NLmTXeFOD1GtCtCcY5BXimWYZeltdhcQlIfLHi1ss6IRVgAgHpFeV3n67RrbAhP2p33LeYgLduuaGmq12fjSSGRM+b/V5FNsVmJljxxrn+m6y9/erNY0G+mXnE76ciFwhAVXZRB3Hs2I5UPsK6UctnHwQ9CtSCrHGvWHn+eHoEXNrJNrI4rzOOBJrtvYZsyUly7iZhXabrvYECkDKV/dCLLBcR+DQEYHO/CurzCZMpdY/8QhyusT59z6k0uiMHSBGIgysk785Ch0zmXA5X1h+w6doas9G61vmbNDzAdXsciTxFgitRDbhAOpKXXHaYwfHbYUo+DQEY1eaMtNYPSI6FXLTPrpYeDfPLM9k6jlWrFKAO10IXAyhiN4nBg4tt0ZyUYpKJX+997Ts668/LuOZOSjFJ' +\n            'Bkx+ZC9lw9w9Kz4qTFpj2lvT80CpIQxHtHTRV6FhWTGsWTTaHehyZm7jZRF693ZbyG7TZxawXESbpohcIB1JxbkFOHqINGxFExByxLq53f+/SUYep1GvmdUpd7wc4FuhsPeF5GAn21JUbTC6bld4jDBa1wdlD1auyYfGgmEv8pWlq4lE9fvFcX7VKOdZ8kTKjdy7zix9uIiqFUq+Mo2xuh5hm+mT7OiLCfK9nugTtxd0AapLKF0csyGFjxQxlcruSMOBhBOY0bj8t1DTsvmIiTmoapmNHOG5H4iODORzRlp4mVaDdpeHFgLPKtfuI0G/hccTtbPxoU7/kW/hK0Vn53waAjC30QV1DJj8yF7Km6Wj5/cg2p4GrWpgMaK7sfQ4lz50lH7X0mAs9GY5GMD/ml9Qp/NoZ44kNNmDtKRJ1M1orxt1VZK1h388PQIubeobq/xfW0USH2sNcektKVU1dN/99RBtTwPYCBuoe5+MGcbbfqGjrAmBu7vKEq1mFy36eXBDZgEIKccXkyZ3e/9fnAAAAAAAAAAA6yR2pMkG1xVyTdQvBzjfb7dS7mU43bZfN/+8hj31O6OO+oT8tcFX5unrXHMnJZaq' +\n            'GwvavyU1xDmG4SyHKk1OIJlpoovOPgh6+vsut52cS1UFakFWttksslo65qXevqKWIqOwJqgpJYBTyFs7Nq0VgbEekAEXuHWDxR86Sj/laTDgGeHtzzYhveyBHSWR/LoYRFt9TE1SSh2o2mBp3K7wBVj1zHIwneMp1MBiWWt/9XDOIq0DOdWfmFkc2ZdHAk34i5DFqgMYe1T2Y9J/w1bQ8NhYnpE1tW7VNTCWUdPWehwS+WchzSZzLtKMHD1EGjasSSqUYWQHf2ktHXPcb19RS28KcPQNaNiKYLSzDsoerEHTZQnYM4WYfQs9l0kGMPaonszJCpbEZXeiDuLFrQGofOSatV4OcKPepEKcoYJka6Dal7RG25Yvaszth9TX9t4nKrgYXTelPEafJdzv4VvLpsGcbvn+o+tTp2SjkxvYhM4v0lkLgXwQ9FaiGm2AdDkz5XOgu3nvDQ8VXAygldweI2wsT8aU1DfkEDZN9iMFMpHdMt/Hg2xCZwMmPzKZvO9uZvjNauV7b52MNa4rW+IWWTGzwuISkPh/k70gJ7+RUANpRg6QIg0bVimeJ2+uGdMoY5KMPFOiQy9wgv746Rue0LxveSw+7UD3' +\n            'TEDVN9LeU9t16L+uX8KyYk2pwNKlQf0KTo//4Dz9EmQmIOSVaW+n4+Hw9Ai4qY9s0aojD92m2cLH0BCd0cYoj4p50E90h9WFRpRXm6NxC6I4QX98+oNPaB1HpNsKUAflIGya8UYKZD+hKN33NL1HEoFERwZytyMt8uCGzAIQUpMYLeWNvIkrV8qh+bD4kx37a4kkR8wuWun53RGFBCCkO0vlvraKJD7WVYQlXxnI1l07Z0BOYz+gBqaNtnZsRyof94rHmrTJfiHDU0QuEICq7JpPnblXgucUBbp7yCybMiAxpUZl+LZeT7G2Ufd1R/TUi/oNhXukZoKFqWxaoWqYu5kPrvkI63nJoV43okf0pi12hX3NXSd0HvjFC4AKGCC8vmXcsgH3orRmbRuYb5Qm50zJIb9TxOZIlUEKD5PZykIgzcyqZHuk70KaQGCJChhxDE6k9psys4vM2jYt3jVM05bcI7x8Wy+pwwm7aKqFGrPSYTGnNkjgEwIdxSlB/E2yzVrat3BL5IqneWXZhO1x5jI4b9YXNLuk6C1t1TirckVcIUfqYXe0sV2hq3DPCRzorJB/znK4vf9XyF39lyJ4qKTkTGprb5QN' +\n            'OFGZW08f3+RiV4zK7XG8ntmIK7DAHSwKkXudXRE8UDuiwx4RqHZDxuRjySOjmcHO9xaGxX6odtyHtKlz4JbVCa8NVn2dOlgUtAwqP1ncxvQ2AviEldEh3dPh3T2YNkhK+UXnGqRmiOV1GFR+sqWR9ZNmWHRQwB2JnqgQGGWMBltPVAgMvEYDoy0DhMZRN7893DJQeOyGHirqMKj8eVc/9yFNIDDKBQy2ZfAyK4AWwwxpvpbdGyRwh9uV7pmB4WG40fwYFNnKBfiCDtK7zA3nKWPXYFBDDxTHO8yw6KCdOg+OQHZNVz9UojnRdcHhYXe9EvWjfHNPH0urN8EvH9/CbVZIsWc5XNDxbATtFTe/QqftlxYdFDBAZX1sZ9qrcrgH7Bf6h7pO6Dzfr3nLAwT7wXM/BgVxvEY+eNYcEofpiifQfPSOd7StobnCYlNskN0m4kSbWGCAFgWPwJrX+UH8+/rYzqlL5G0Oo0PyiwYI65+bEmvQSRc0e5qSh0rnaZwiGwF8QsTmnuA6TFxyDuOSVktun14+o5naa6NT9FrYPTXn/uCQTBskJSLQCYMlh+ldhCmAwA8UMOLGs8Cghh4okwh0M6QZ1yny' +\n            'NB89rdQtbG/uCj+u+7Kljkruc8SQ3TGDqrcttbGhajSpKgQGXiOP33tLNaFoa2/MaiO/bvSmlWwZHLlrhRrTUlXVmNTW3jUayWBN5fKufvMcpsKjqYHhct4vlVGtelOYMCWq/1bI9hYVUh2dHihg2VBv4xz6RQc6GJxV8StkewsBgOyarn6oWXzsi0AFDBBeI1DlGYv5QQTvitM0VcwN1wenvuFtZ3+S5eMluQ3naZdaBhWRom5jerYR7xYYIItGCfTfPrepgaseuweK6H2swLeRA4y2XiMfD9ONRXSwVmBn7fcCweqOvrpfS+CDEjjN48R3ws7+vlwNzkhsNUwb0oxds2QWwxkQJuqe0adicyQDnSmz74Ll658o/ILL8q4CqKronPBdJ4ZDGqz6J3SwKM9HH54xt6k4WBvQuOOSLsi8eBmbQAvvBpD7cce/QvhiHzvrEEYDBJloPnpHtVrY3piPQmOmldGQ2AjHKm5jhFMGJ1J7wxnXy+uwRGbXKZeu5n4MCuJljHwU0vEHsFbIgHEiwywwQAuMinrhH9Xaztug3ts46YoOdK0Qk1TcxhWmC+kaF/ZVzBmN3V/+uL2xSb/lMCiviQrt' +\n            '1lum9bStemp5VvCIKZcifhDoZlUys1L5DlNh39rO/jnOx/MEn8kBYf9itWFnf18ul1zPJtIlh/BR7w+GVDuvYy8eQe8Qy/KPUnImNbu5SoiujbrnM0TwTUEHadNmiP2as6uU3jS7uWaAExeSjfGqm6VkoPDFETxU8THUvr2xoRd/caLz6o71tUCHhUnI9lXDfvFOaUTwXezURmPc9VE32PKs/Q1SM0T8AAAAAAAAAABfvG5ZjvVRWhbPNC7xqoUysDa9bds5XI0TdU/m3TG3Ervfp3otbJCUiefIrDpYKzA8aw4JzfpFncSuBYnH4mUhSXNad39f1GjK/WRWHSybGNoVAgMvn8nhiGckNpQmg2k3ghQeO6+JhJy11TEkcEvp19tKbxrT0jOm+YlDKpPZv501OauKDuOwU/LKrxXH4tFuGSg8dkMPFT3r4pNjhO3EXjyCwyCL+QMzuINMuUoT/WRw3rEuaGtVNZ/RN3pTxDZhyqV5AvNZdQQ6l1KC5Zp5/X9wSCaDEpzFLukTaZzNeCi5/w59rI0dVFV0TnignUPLfYjMs1IzQUS9EhtKE8+6TUnNJf26ThE+dssgjAYILz/2J7oieKB2' +\n            'wolX8gT7supFPf6B5G1n45TB5pU9p2IbLINoXP9JF2TzLBGX/E3spSsk1r2SLmj2sit4RJrFET9I87bt0SF8MS6erXW+tVrWF0/YtF/ULWtO1OSWEjir+pLmtO7+vrXQRqDXMgvvgghHIDuopZEqUST3W/jmnj6W8LE4JBPPCU7+4ln7yQH3dydqcksJHNt9vfj1Ae51R19ZmzwiTeyGkW2EAY+Zwer+dJi45BzbOazgWV5xIXxbtyqkOic8UMCv9QtD7D9UO26Djj4hYnNPcMCUkttFB/9Ycr/qn9/C7mcRaIrPnM36oBqBkNhqmDa5esvZO8YVx5XHMyw6KGCAyoY0RelO6H1Q9pZqX9DW3oXprYFPltXaHHCiL7aePqPVCmn2jVgrZEC4Qo7Jwu51f2BKSeOsjfEsW4b5CwwQyyPh2bLrjwLz7ik5E5TT0iVEyOChf1zQ1qq1jMal96JurYGT+wgjjwLC1caPRlsvn4H8/5zSiP26xXcFkVfzWdxHHSYuOQf/SSv7WCIz5ZrFV92yvOJC+LZzJXe3Ykjgls9vmcSm2D2nTMEUfkHreVcB9IuvdpEqkzc+8p0kmywKGenhYyK2+GIv' +\n            'VTaZQEd1f3qfTVbVpHsLM4IlZ0ZqoRdMuPUFfesIL7LMSMEL9EdfUzcwiNQnXew6lo9DJRgK7RAXPSMs9wFhUa5O0J+Ub8wT/UtHQcRTmHMbWz8N2ZM3ZS/8sJZ7ZEBS4CN20gqJhAyjrjpwMpsY10GcvSM13oUm+v6/EVt8MZkDlwdPhaqbDcWK1PtINrlwvsYL4/xBBKge/zbcS3CHchMf3DPthFO2CETjPjQXZNMP8RtuqzjNOWQ1Hwp3YbhaO1aU9QnPug4whXCEuHJF0Eevs70il6488rpcL29rVUp0vcR2H09w4c/fxkRx7cRe5hB4TB3ArxZ6yinWPBE/KC3tQRd2qFmvrF8hHpmj1e7UhPlJqH7zOzzjbKWW4BPk0SDwmDqdQyxrxARk3Fl1Y2nV9eXRlWyemulfBDaYuyTJ7MjaZqTvRNaVCMilsurGxAwiNcBQO4A4wZO6jGUhAxzux11GvJ6P0zEBGTdRWtHY4uVohuylD7E3EI1XecmRcJ87aQXKQgZP61CDFoDK7+xFavMkG9I4WNZzr+GBq74kL1Tnytm/jAIR8YENzBn9kLxNuw9DxgqVGERqnaB2HaG/y/E/VwEq' +\n            'K95PiWHhcrUnuFOoT3MkgbCx5kPfH0thGMw4Qlw5rGjSt/fXvzfYITEDhkowFMcgFKokY3Kr+lxuYA21TrrFdDlHZXQEA6PzCcIV8Lxx5iMqWLlH6YfwRXtM3xi0d73Ylwm165Bsb+BzCDwmgGDZC/7cQA5B+QN+KElIxuRL6bhyjsroCAZb+wYzDp4XSSsaWVCFYWnnKU665PT85sQ2T8p7z5XjDnRJfX/RhqM+lsJSg2EQ2FrWkE36oQIbTNMSkTq7dYclRPrdRuy5FA8VGD1lmmsehpEUwj8sq9cZEJrXE/4GLdRoNtCmBlay+8HcIhxaed2QlJbv0m28obFJNQ537aAjXk/Jy/05W2to9rkN4OrvpvTUxAQi/x8ahTLn+Wm4Xt7WqpR/biAHrvKPPzrQYjuBqTj+ZiTui3qtoae2gujdyFZge6eMxW8oHiowx5slekX6oI1bQXTgZCsws19ji/9+rgJUS8mvnAwF+AjOWTCK+YtGro/FjanMVcOIgDSWx2dtDrHzPKrh5w3XurtiAjJuorS/1QIPhyAYccudXKdUqbcSzoQWadh96DxWimGEeF62c59CC7pssHQeK/EtW2Dqwc5H' +\n            'dqw19xKDaRwsa7fZ/s7bX/zNsY9MNRqDH3nAEsMWBYLwq62uYqdMt+GlgByC7wb8Z6IYRfLLI1dRFGZfXfBNnb9A/S10J4ZYoDk9P7cxg9oFpAnRkuOwF6n7KM8LQGX5JamiKUK/PXzbdeInA0Y+ArMm4QxatdBs55aOgpWmLea5c/OzY26tQt9XHTgZwwzl7lSbcinXy8USmSr9ZeLRRvjvTpBWsChktwQeE0Aw4ovALt0q2tUJZ5MrSvSK6V0Hb+b7e8bcR4Qjmqy3VfYWZkAaS+29uAfWSF6o04mvYwWkG8IgrbSxPXU7MriXKfIRmX5YS7MyICkdaDGTztocf/9atsDJn4GOFrvV4n9n46GlnTTuJdIzzZj4roU7VKLZbfcK+ssQXnl5XS6ZubukJY5De2dEM0F4AYb2zohmgvDr8JKjuzR70rzX+mLxjR1VrdnX0BHFVx4L0+Rxsb3/3qpsL4CO6v70XuV9MfbIgKT1D6R/8ET8oBrdycNR9bWV6nZkbTNS+SIAAAAAAAAAAIWQnxb1jr6mRilFc6rxLMwKVRK/Odt9Lnjb2Fcx3SbVKc++CGwta0ghi102WDoPmxUs0q36zXis' +\n            'g6ORiOLHlbzDudplX3+Sap7LoBssHYnDB7X4UJ8vqep+6NbJJpQNzza2fhqvO27KhgeYWXAkJav7eEnf0xqzaUx8V8yTKlHi2WQTpg6KJ/8mPqVmxxWmcWxx/DRDdtyJSk9ZUoRjevja8xTpiyC88lcnaMFKuWaHEIjbfGguyLuIcHX5U3pqYi56RljzAsKiYZEW2+WCCE2ofd4BgybnCdzAGnecaZfo7cOcPax9UMimCjOhoHiowMGoK+RSs4uXP3Rr6hNKiOmiKMy+uv2aJ6vq2U4GjHwE9IlSsXgiflBc9Iyw+wSZWWAX4BVt5Iq9RDi08qc9NTGMUormSf9YhbUV75JN/Pt2DGYcIS6SVjS0kxlcxZp5hpzaUZoh0ZA+MpSBBbW+XC0ZSs6M1F8umEONTKI4Epzbm2+pyr7+OdSBsmAJ7wuMQd7R6/aRpY4VTm2mTZ7mSB9UsG+OzxP9iknYXh0ByeH1r8gmURwJTuP2mKMwde5nrVrHgi7sTbJDjdR8KMGZ2nWJ9oM32xzoks3ON8V8Id2jUwWX3lA8VGBqQvKqVD/3k11yen5zYhup4jKHUwdFnfFWoZ4Pwt/kd8Yd07TNnCJ9' +\n            '5Yd/A5hqNBuUnrKkFcb07WIGEZRgKJNAY4DnWuhOEbCL53K21tDxb1CSkJHVls9t6GeV7D6e4N98+SdIK1gUMshqPhTuwm20cRnNp42swPbkAYnNEAy265KtvDoCj9/3sqAXwtLTUpwgDav40FyNazSnj5ui93c347RxnY8jHwFFvkI8L1u3wfceVf79iOVdaFMDK1nz7m5ls+nE/wc6qncqwzma5evsh4Ful/hCp1sRDi2y4EhKSzMSd8s92N7dvVEMrHnrn6U1IXlVKpH1x4qwqWhG4GptQ8foC0vwszoIybNUaxYe5TnxwjXrqZC+wb7yN2YGx7IsIJIzYUVpqusBUjtvwyialGlTq5Nazt0nKDj2PhM0DosEVeyhK6BSd6GyxJeP+KKlUSLKE+VAhiJ2E1hi0/HN243f3gi3bP5dHhLInkoXig5WgWsDlphn7l95lTMD7Vmv7XSLq3jXHW2Sny35PlPu9dio+Lp5jCr2GbFpjjnPa5Xdry90kQTi7CqcgOCIZCfOXI/YgluV6sTg2Zk6xgJxRpnDpRcwdvk9GxUfUKKfQp7VBeorx1lGNGZaz9x/S5hhsftTKSNC98chwAgOhkEw' +\n            'hpPNFpb9e3SHJzGScTaxS9NEbIpjoXIbZpo16KZoDkrKtljyOVCaFqTl3k70Loq5N6dDXug/CNkTTmI54mx/loJ5Gjwt9nSIP27wCoMpFjyOWn5C/etlkVyq7kx5gd21GfI0eFrx6A0lXd3j7Zi9cFCJijKpnMysKMpFGdpOZlauWYgPTLMdIg2XmPo31tsmMvlo8LT/zRqgDwlkTyWFRfo61RdeJN5y9GxUfF2yRhVxPoD7/w9+IHhDzytz0qr6vRfqNq7fYrT9ERus0W+Sz0q6p9vHLWfgs0FrXa1J+tO8oxaySRSoixXRUAaK7PkU4nwd6+Me/EBP5Ix1m+2iI37c/RQbUix4TlBw8XwmaBzmlsrBWBXzvDXSpks7tIGngAz/Kf59/fYe2frD1bqksGwmY6ke9ZnRA8EZkTRAQ0H3rU3tafIFVM2dlkm2G9aryMO95+rbE2jRMYmfsCr7ZR0Y41Lh+ufx2jkjWu98psGhu/XgqO5PepE3eAXPmgseMThxYYC/jlvZ+DrL2zzlgAJ15RXTi4l+Ry0/IfD7vMYtlG63ho6jlbo8JI0hlC4J5yI2Rb/eOYP/ZP65AuQbscl3QWMNENlX' +\n            'w8sXIrWNTsyieuxxnK4MO5n+y1GkjBX7FGWsgm0nMyvhvQR6116/AXn3M6+UGWDFZy7JbEGjxHXCf+umUkaE82Tv0P1144c07Z5gBAdDrhj7jimTue8UTThFPrEMYlqBaXhIB0I1XBJIz0LOFKbunhysH9YGMS3Oe4LWukeS6budFBx7H4caB1YWuA3BHEouuEnBmPIfp3d8qRgByNmlBrE0jkh+wnOtQbINHph7OkR0YKtVo8+744TmKANFdvIKG4fRbYl6YXMP4n3v5F1SWIPN5rjKPb63DCNkftAdERl6Nio+oFkjhLYfQPPxiT8QddRX0UQEcdxFWNo0I3A1uNymEWWH/CBDjZtn08mrJtArC1yI7g4lF2/nejgqtdqQJpzEctnY/jFjxB5G+qjLibervHcWQvUvfR3khS8SbzmoxrowJDOboGAFB9fO6IjIj+6Cxhogr65XokSJJteAEfyl5yg2pFjwByvOu49LTL1Je75K820koTyv6Zu3aVV9EvqevQWntanowEuqW4Nr20JzFI+sO3kFkIOEgShRwSHlV9NQbFWw/XL/mWrLTz1hPtoMjmTi3APwhoNW5rlJ6QTq1yq7Cw/8' +\n            'F6S1E1lncGrjyOFvBNU2f/hPMAKNr1cMGEbI/L06IjJbgSD39sqRCNRvojHs6j6mM02UdFM0ByVYQDlmworSSb7W86eanyH1aMy0g6X+li3QhXUbV+ExWv7QAj3lL9GOSw5bXyDmrd8aMy3pbrGrTKPOEPV7ZcYEEI97qNYsPNerB6OhEHPY4WsNrRKRvtVs8vNmQzUywJcuVXcmss7g1AAAAAAAAAAAywKkdt6bUCnk4y/Ui556wnNLZe4shPdeblOGvM1+EK8BtPyE58vKP8/oc1xlkF/VNhO/2g/0wuYRO4csMef26C/hi6JVBSrr6XS3LrxIoeQKvFZBuJ2Xm7RqpeYiArZuROwmsMS7/4emkDtbJ6UDx39oAZD8meZHl6hKOqcajZzdEu3hYDfqfMVUJR3dDchOiMVMfZVr4xNNkWlgSGYrXbCAcsyZCbmStd5ZYsXJfFGBuAOtGbY3ybL1l9lKgjDsCwiqxV9WXaTxMn/SAXKD1q2YkZ54815jarlRlnZ1H1Mk6SFnClN3T7n9PRwV1G1IkvZhlPvaSF9aNdxzEQFbN97T9HBUd6k9wAoOs4HNDY27iNgJxl/kNhYQSZe+rLpV' +\n            'IbcKyVaTsoxZ9MXiJUEYdtXbXrULIfSZVdehnPVcCW+pcka0w/hRn4VS1IeivTg1VGNdGBKXw1Ajwu/chRg78p9h+W7MDJN5U0iTo53cj+1e3wtZqgpUy6wsbRqfOJRc1667oNiqfecqv6AMCcXvKNhMxk889y+/IAP2TbFYeLOnJMffwG7J+AafMj9ogIaCzClqzVHQHJQFXiuuXMDFw2Jw4sIdYwG2O4QnIDgiGcDS8JAOhGq4JFL8byd6F0XSxpU8jOlNiw/gCfj+MJV1PmVbLHmSKE0LmEo31UNH38Tqta6/iAjipZo/0sCQzFa6nKDg//hM0DhMJZXkr63hYt9nCPSzvGMCv2IPI31U68qTQp0QHBGCYAl9T9CM3dTajC+bVy5g7O9winx/GMS0Hzow26Tf6dP/QAbxmn+w8Htfa/fdTcGe9B9tBkcycW6P+fvMhmpknTMwjI3lZ3REZIlxsPlyoCks1hpHJD9ht9jv64UR1MgnZpYctr5A0UejqrNfJfe4Et52FU5AcEQynVE9drZOVwaT80eax9L5Cqibiy5EdwechSl+uZ09haxpfjfmLfx9QMN3byWk7pOeW+BFyFDdj7Wt' +\n            'hu1bpxH/GVLpHQvZz2FrNTfgqyVuQI/7lgf2wDECWnoLAvXhFtI8nfPYSGv7UGUMYhz/J8QIdfV9QMtx+l/TSm2qZhbaopBin181SSPshOLshHw9xQfDswJaNmgEPOIFqL+ebE2sCxn6gIvi6b67lLW5nFJ3x0+jeNm8lfA5e8zjMuUM260mJMdPzhKTMnl+Fyns6y6nCavC1rn2mVTR+F2JjL+6uFUahZp2+xfditsb6FiGNi9/tfZBP4/xNs2K0xEPpbu341wKL+7VFMxNEegwEO3Nfxq5oedd5V9C1YHu3kpVwTshtvL1U1/5ThSADMG0bRiIdh684V/bZSmROy0l6JdacYHCcYF/HOLXpVQuUsXLXFMSS/n3pr7vnCgdnnIufSHy9W7OFw2bgdyn5g6bggUctJQbHnEvYjxJ1zMh5Fz6Qvn33MuOen+Lug9gjpiDGgEPtkZHTM8NjolbI6mShVhPsnqVjMK1cgUzVENC1bjphO/zpQEtGzQCHnGMV6Ziaq50GAv/GfwG49gTEjW6nU1qfG3+ydRMF4+G7WVQZSPmoC5SiAN3LVwGIpOJiwH0/gtpHsD42r2K7YJZkUxOOuyYW2e+' +\n            'sQ3wgn+/lqlqaSea1Pja4eeGidzT1f8ugS4aKx+lU9H7rZDW66DKGBrFQ7I0MQ45FgT33yy5eCemJBxpURifAnU1E8zqr3xeZPKln8hMTvokfSseSJ9fWttk1xirR0xIefSnofInCkAVc9qDKpvrrjSXhnloYhxyUUg40qIwIwTwr2U3/XL2hR0GAj46a0S6Z4WIw85u3XNmqJP3zHCs/9TSTim17anfOFYyFHDqamwHw0GMDlpKgyvLsi9WNbrNBLRs0Ah42QoG7lq4DEQ7DzshH0h2yPnlCVjDiRLu3pjRSznNv4sBWTl7KSBy9Bvgh8BAkxPhaN6tJumIR8qjn04UDIScZ4W71f9VHbfz2FOgykbRXVykDc1gIMeH/jRvhLdtzxXD+1fe/aD8oSHkzkuNe2CWAS09msZCrSmKLGQIddi9EPCvFLNXxup7g3SsTWMh2JpFFjLtqWcJxxmyP/dsJLvzKLwGxmLVJpEsCPI84l7EeJKzZrl4KD9vTzm9wIyPnp1oM/1PORewnnn0N1k94G+ywIwQ1oh4QbHRS9oZsm7uMhOdsLSUh2Z12T4vglk3dxmHwFiQ6ax4PUZhdfGCfgP/bIcJ' +\n            'lF3AqDU+uH9FFvllirW5Jj+Vc5h+sCDvuFUzC21RSDEq5qkbVCvLQWMx5BPGFgR5QI+OgYDTEaDv81FhwyVQOtBmIvm9lXDViHbZog1LjUmlUzE1VzoMi+Fo02TfkcQh9BsJ5/UKL48SsJsPJMGhLdpJzCypWT3EH1w0Vj5Xpr9U0U82qFaLgq983+BD9kGa6momhclD+Lzl3L+01+kdK7J63d55nQUga0Q8rtbmq217rpHJ9hvoRT64aKx8rlFjEce2UyLjMqTSPBSRuamS0I+1mC4DEcfKcKxkKODJ1NiJW8KWD1X8xXZCPpDsje/Xb/BQft6ecmc9z0XweozC6kqgYFSUH1yxWBD7W7De/Zxe/qHjvJrGk27dS0rcgAPrdBgI+OixDdIUXsG3KIWaIii8n3NQFylEJwoGQk69zNOXKu30Mxwr9gWZd+QKZqiGJVAwKkqBLtbdio2gpwN3R8UV+HqXDpt7MCPqqWAaxXi346o6c/utpg+2mTEequWXAAAAAAAAAAAxDvGdYgS09CKTcaZE22RVDeyvWRqWB5JcpJeLuKYklhwrGQo4dTU2QaKVtYLNYCwyedzBZCYnfcGhlKqfdkJx' +\n            'E52AOybf0KGuUcTUQegwFtgT+kStZd/BrAvyvEXU0hMjvmqSRsUV2UnXTQiSPc84nQUDISfQZucvf97/Xk1jx6R+KgFVJH0HmbFv8S+ov+1GYdQ5jJcqr9/Qu8ijP5VC3KeWlKUdBsuwIOu2faHnJboPBWNpbao05PGkgNX3bKfEOONOlRDq95OegSQ7ZPL8je+uRgctJc8sCPOjWG/wTtelY3WzzzpWIMlHzkDnhlBD+KPdhvGCKVaLeV6sammHgAMBHx27Il31NhLT9xReAxifddowDew8lXDbnDcgyfO7Ih5Xa3PbuHL2UkDk9TbdRDviUYiryKriH/442bNXqP1Dym7n5PEXyqNhS4mkfuz+NOcy4cZinoN0LEMbmbHUzzoWr4PC1mqq5agESZDpHCYnHXZMo71fkcS3TD9YEPl8bdBF+EGixn8a/Rn+YzFPyPlXI42YnOmnCQddUwbujlX8VAKqSPoOSPpWPJAjvrRl376rylI/dmyHfSLYvOHuzE0784XgReO+u2mzYRVzPhDqrWcg/UMots6xDnHl3Cq9zETvZzfgt1I/FY6kErCNmJx0xS22zmGb61mZK5Rd6Ios78oJd29M' +\n            'o71rjVt+N4TrRz2xy12JMMP7osKbSqB0nCgYFSXOF2toMxHy0MQ45F/Tute+hLcf/G7RWuX6gJs2zbARbF7+dymRhEdSCVjIopBwuVlgRghTEg66pgzBAToMBHx01ohpaR4KxtLaSWhz20l05utHUXqDiv30BZnJWkrNM7TiH5lgRslPwDSX8OarkujRy46iM1TH9WY4VvHZPuFwr3uuTWFr0nvCKuZ8krOaEDl6g3CryLMwS46YkL+WcodjCwKyW2fWB7b8bhXQMcOXzlU/5ha6WwGwBrUlqJut5ilucMhqH1Jdd9NDW24QNXBXPfoLZg77Khf8lat2Mnqel2NL9kutnWRiRYv18YMMrtvD90jFyPVCZpEx/5UEShzcSLDLiSli3zz4uGawueII6TDBNaFPs/BhGnZ8jSYF8hwWATbWtxki/sxUnjcIlDilkH2LC12jjlgD1JxaW8yc6m88vO2uJG07c//l0rh+D94i7c5eVKuxyoGF7B3n+I/oBWG5rV4ahwE1oIwvKtvWZc7MdleAtaeC9YNYPtyKLu3kez/J2Vw1Br7nD4O+ER1sTgXupgO5CVk2dBAQPIG0gJ/eXSxptgJ9DHdK' +\n            'OZCA19XIeVMJ1B4WSHQGtM3WOxgmUF5f+Z3C9JsCmOic0FQKlDy2f7yoS3+JHxfFcj0ds7eN8qZ4qm5x5ztPLhQz5pmgcWcNhPIb5FRiB4KY3zMntNIPL/BJ3OLTdp5c22xgGZZW63pkh0ayB4tHgzLNI1mNy63PHqSVW/DH2oXpoUNAG51Gtf2Spdm77CG4yBOMeQ4Ljhsu4AuabXulYvhXEriTt/H86yj+2AvqlJ1WSmXrikDqTGyZiOhHSigjRTWJixIdjy2r2MAyMazL9Loukcq5hny9eWC+Pe+OJjoMEal3YC/W8MtQ4a0WyTUn6uIulANf/YkoZtEvXeLOGv8bGEGrm/OQn5M53oz+DUOWRyfIxIoL91JFAsaqrlMcm5xe86wQtBNPovpJQqsypT8WWmLlURIrx0FI2nbm49eSSEDl5GSyp9NyrkPWl4TaIztyoQXhGoakigSRSUGmOLS2hSXJ3nhl3eq6rKbPgAIKl3PCULa9iMKE/7tevTOTi6DfRyyPak4q72y3TZUcMkJ5g3IqMY1Bc/fN/784m7IHTAr5OCwCbIpqDwskOgNab9rlPF+Ikx/Gi5iWflOKw0T/WccaqOY5' +\n            '4vzgzkOekimiDN4kedjNQBnon6LI69jp9Ea7z/OYJwxDs1M+IoTkVdgvDc2OlFBGUQZvErJs6CDnOVeva8VCbQgezlpAwW+gOxk9T8W/q3t/5mSI3xdNQg6YFO9wWATYgTeshXw518axczJE4YWoIWlcP4lvEfhn9s8GV+Pv9SQaq/J20Clj1S2jZk51uR5eAom9mBB30iiQwf199BNgjzxVN7b9k6kXqhIQfjkZouAGhtq1MJlreNqmsFWe44Juw04v91YIWodtU1ikT/9BN/xYdZWzWUisfKUJXMfV9n77FH9si3VKwL/rJquR3az5aJbvxWekkXPKmjHhHnxcM7vkQYaxMxWpDdt5O2iav+RwtKArp/ogjuR6OntzB/lRjOzVvhSjaCLu7Um5I7FE2Rdwi024s9wxYIghnydl/tOz+o/c8fJ6CZELLTH8pgmbD1LEo3jtbcxQzL9eutmBNGvVghF/ZipPlM6aUNT92d8rJbz7RSB1JmfEK2YfSfy/SSQg/HIyWd0DQ23UGMK7PB9uRRf4crORoIVjvGmvH2jUPqS67ruGtgHK0EwItWkUrJTKywmAyZhUw9hzmjc4ZCb+xcAtusrC' +\n            '3qnXeL4NOz4ED2ctIO65UOWw6jd7spBF8wqxNsu0JWBiAZwHNxIs++hrkwwTKC+hzBzrVC7lN0tTj9KKohs6CBthIjrYnArBNsJEdK0lFJ96I9Pp90ydBr4h9ueZaMXtz1+GgDYnjHf3BdYb61qcME0rR9FS3OCNX557/cI07Pgkd3hYPc0Y6oZ7pnxEFdWqTOGXnVppiZkAAAAAAAAAAOxk9CEzxpbxtXxVacFrEXHBx5JvRn+Ir2VNlv4PPi6XFfk21ajEDhm4pyxSqfGulalRfaoh2xncWNJxBPoY7pRZGKFI8q2HgFzdFina9lfEgnTBUWT7bPrR+xPbxuBW8n1v2RDPYJ9qtj84vdmpqk09n+f69SbAA3S7xwaHFJne32MHNLa4Uio60+0DzQrCb/reryCDwCPUwA1CI07K4buFOMuoXNdulsQCJQ5uJFjrR7w0EwJqXQWv16cfEUJypJeN94TMP2LjuW38HqFEx4Ehss85FZbIrjGOTo2VCRbzzpVWzD6S5WM4WlCb3X0QRzWBKaC156+j5vOH42NwK3ngdV1WU+lAAXvpA6X/+fQSErU8LJDoDHUzB/MVhX7E24+vuGoMYdMe' +\n            '2eXdgYYhOVJ3+KrSn9Yi4iW9qBQ1eHH+dXEXSo+h8MoTf+xgmF1lYTBEnsGdvH/npUDU3UH0zyzcIGrgrnrpFluRHNDi2lWosjBfkPlHEx00S/nsvVLGt10XxmXSQz7QGCJP7sBesf2eWemShEtkV5pWjr+kpd0Ho8YOaHFtpFR+LLTE16IkVoexdjBMoLy+QTrupjLzNn2ZFeNrvGdmO0DwPuo6Rl9pHC0ow+CwCK1OaCoFSh5bsQXFt2EoW9BE4b+NGltcKRXywGF6wwFMdLf16PHRHMNZY8tMSz+nRe+dGoRGnInfa+M2MIJLK/s91fR09uYO76L1jGuD+y1OGEZ25F8K3zQRIHgfdR0jobq9Ypszgap+0a4dd1MZ9xuw/tHIDaMumoRVCQg/koJRcCmsAWNVV6cOp8lpRVGDHQSOZWgmBNS6ChH2UfiIKrdJ133JbvZ5PYrvJ5n1KwQtzUju8LB6hzDJIvGi7Q1Uc5JhQvHTL9CXx0pnTShq8OLhgP18yXSMvtJxfnBnr09JmpOCkKns0duziOOykzRN0XInNBWMJQ+j1g'); //==\n\n    // Variables\n    var sigma, N, h;\n\n    // 64bit tools\n    function get8(x, i) {\n        return (x[i >> 2] >> ((i & 3) << 3)) & 0xff;\n    }\n\n    // 512bit tools\n    function add512(x, y) {\n        var CF = 0, w0, w1;\n        for (var i = 0; i < 16; i++) {\n            w0 = (x[i] & 0xffff) + (y[i] & 0xffff) + (CF || 0);\n            w1 = (x[i] >>> 16) + (y[i] >>> 16) + (w0 >>> 16);\n            x[i] = (w0 & 0xffff) | (w1 << 16);\n            CF = (w1 >>> 16);\n        }\n    }\n\n    function get512(d) {\n        return new Int32Array(d.buffer, d.byteOffset, 16);\n    }\n\n\n    function copy512(r, d) {\n        for (var i = 0; i < 16; i++)\n            r[i] = d[i];\n    }\n\n    function new512() {\n        return new Int32Array(16);\n    }\n\n    // Core private algorithms\n    function xor512(x, y) {\n        for (var i = 0; i < 16; i++)\n            x[i] = x[i] ^ y[i];\n    }\n\n\n    var r = new512();\n    function XLPS(x, y) {\n        copy512(r, x);\n        xor512(r, y);\n        for (var i = 0; i < 8; i++) {\n            var z0, z1, k = get8(r, i) << 1;\n            z0 = Ax[k];\n            z1 = Ax[k + 1];\n            for (var j = 1; j < 8; j++) {\n                k = (j << 9) + (get8(r, (j << 3) + i) << 1);\n                z0 = z0 ^ Ax[k];\n                z1 = z1 ^ Ax[k + 1];\n            }\n            x[i << 1] = z0;\n            x[(i << 1) + 1] = z1;\n        }\n    }\n\n    var data = new512(), Ki = new512();\n    function g(h, N, m)\n    {\n        var i;\n\n        copy512(data, h);\n        XLPS(data, N);\n\n        /* Starting E() */\n        copy512(Ki, data);\n        XLPS(data, m);\n\n        for (i = 0; i < 11; i++) {\n            XLPS(Ki, C[i]);\n            XLPS(data, Ki);\n        }\n\n        XLPS(Ki, C[11]);\n        xor512(data, Ki);\n        /* E() done */\n\n        xor512(h, data);\n        xor512(h, m);\n    }\n\n    // Stages\n    function stage2(d) {\n        var m = get512(d);\n        g(h, N, m);\n\n        add512(N, buffer512);\n        add512(sigma, m);\n    }\n\n    function stage3(d) {\n        var n = d.length;\n        if (n > 63)\n            return;\n\n        var b0 = new Int32Array(16);\n        b0[0] = n << 3;\n\n        var b = new Uint8Array(64);\n        for (var i = 0; i < n; i++)\n            b[i] = d[i];\n        b[n] = 0x01;\n\n        var m = get512(b), m0 = get512(b0);\n        g(h, N, m);\n\n        add512(N, m0);\n        add512(sigma, m);\n\n        g(h, buffer0, N);\n        g(h, buffer0, sigma);\n    }\n\n    return function (data) {\n\n        // Cleanup\n        sigma = new512();\n        N = new512();\n\n        // Initial vector\n        h = new512();\n        for (var i = 0; i < 16; i++)\n            if (this.bitLength === 256)\n                h[i] = 0x01010101;\n\n        // Make data\n        var d = new Uint8Array(buffer(data));\n\n        var n = d.length;\n        var r = n % 64, q = (n - r) / 64;\n\n        for (var i = 0; i < q; i++)\n            stage2.call(this, new Uint8Array(d.buffer, i * 64, 64));\n\n        stage3.call(this, new Uint8Array(d.buffer, q * 64, r));\n\n        var digest;\n        if (this.bitLength === 256) {\n            digest = new Int32Array(8);\n            for (var i = 0; i < 8; i++)\n                digest[i] = h[8 + i];\n        } else {\n            digest = new Int32Array(16);\n            for (var i = 0; i < 16; i++)\n                digest[i] = h[i];\n        }\n        // Swap hash for SignalCom\n        if (this.procreator === 'SC' || this.procreator === 'VN')\n            return swap(digest.buffer);\n        else\n            return digest.buffer;\n    };\n} // </editor-fold>\n)();\n\n/**\n * Algorithm name GOST R 34.11-94<br><br>\n *\n * http://tools.ietf.org/html/rfc5831\n *\n * The digest method returns digest data in according to GOST R 34.11-94.\n * @memberOf GostDigest\n * @method digest\n * @instance\n * @param {(ArrayBuffer|TypedArray)} data Data\n * @returns {ArrayBuffer} Digest of data\n */\nvar digest94 = (function () // <editor-fold defaultstate=\"collapsed\">\n{\n    var C, H, M, Sum;\n\n    // (i + 1 + 4(k - 1)) = 8i + k      i = 0-3, k = 1-8\n    function P(d) {\n        var K = new Uint8Array(32);\n\n        for (var k = 0; k < 8; k++) {\n            K[4 * k] = d[k];\n            K[1 + 4 * k] = d[ 8 + k];\n            K[2 + 4 * k] = d[16 + k];\n            K[3 + 4 * k] = d[24 + k];\n        }\n\n        return K;\n    }\n\n    //A (x) = (x0 ^ x1) || x3 || x2 || x1\n    function A(d)\n    {\n        var a = new Uint8Array(8);\n\n        for (var j = 0; j < 8; j++)\n        {\n            a[j] = (d[j] ^ d[j + 8]);\n        }\n\n        arraycopy(d, 8, d, 0, 24);\n        arraycopy(a, 0, d, 24, 8);\n\n        return d;\n    }\n\n    // (in:) n16||..||n1 ==> (out:) n1^n2^n3^n4^n13^n16||n16||..||n2\n    function fw(d) {\n        var wS = new Uint16Array(d.buffer, 0, 16);\n        var wS15 = wS[0] ^ wS[1] ^ wS[2] ^ wS[3] ^ wS[12] ^ wS[15];\n        arraycopy(wS, 1, wS, 0, 15);\n        wS[15] = wS15;\n    }\n\n    //Encrypt function, ECB mode\n    function encrypt(key, s, sOff, d, dOff) {\n        var t = new Uint8Array(8);\n        arraycopy(d, dOff, t, 0, 8);\n        var r = new Uint8Array(this.cipher.encrypt(key, t));\n        arraycopy(r, 0, s, sOff, 8);\n    }\n\n    // block processing\n    function process(d, dOff) {\n        var S = new Uint8Array(32), U = new Uint8Array(32),\n                V = new Uint8Array(32), W = new Uint8Array(32);\n\n        arraycopy(d, dOff, M, 0, 32);\n\n        //key step 1\n\n        // H = h3 || h2 || h1 || h0\n        // S = s3 || s2 || s1 || s0\n        arraycopy(H, 0, U, 0, 32);\n        arraycopy(M, 0, V, 0, 32);\n        for (var j = 0; j < 32; j++)\n        {\n            W[j] = (U[j] ^ V[j]);\n        }\n        // Encrypt GOST 28147-ECB\n        encrypt.call(this, P(W), S, 0, H, 0); // s0 = EK0 [h0]\n\n        //keys step 2,3,4\n        for (var i = 1; i < 4; i++) {\n            var tmpA = A(U);\n            for (var j = 0; j < 32; j++) {\n                U[j] = (tmpA[j] ^ C[i][j]);\n            }\n            V = A(A(V));\n            for (var j = 0; j < 32; j++) {\n                W[j] = (U[j] ^ V[j]);\n            }\n            // Encrypt GOST 28147-ECB\n            encrypt.call(this, P(W), S, i * 8, H, i * 8); // si = EKi [hi]\n        }\n\n        // x(M, H) = y61(H^y(M^y12(S)))\n        for (var n = 0; n < 12; n++) {\n            fw(S);\n        }\n        for (var n = 0; n < 32; n++) {\n            S[n] = (S[n] ^ M[n]);\n        }\n\n        fw(S);\n\n        for (var n = 0; n < 32; n++) {\n            S[n] = (H[n] ^ S[n]);\n        }\n        for (var n = 0; n < 61; n++) {\n            fw(S);\n        }\n        arraycopy(S, 0, H, 0, H.length);\n    }\n\n\n    //  256 bitsblock modul -> (Sum + a mod (2^256))\n    function summing(d)\n    {\n        var carry = 0;\n        for (var i = 0; i < Sum.length; i++)\n        {\n            var sum = (Sum[i] & 0xff) + (d[i] & 0xff) + carry;\n\n            Sum[i] = sum;\n\n            carry = sum >>> 8;\n        }\n    }\n\n    // reset the chaining variables to the IV values.\n    var C2 = new Uint8Array([\n        0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,\n        0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,\n        0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF,\n        0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF\n    ]);\n\n    return function (data) {\n\n        // Reset buffers\n        H = new Uint8Array(32);\n        M = new Uint8Array(32);\n        Sum = new Uint8Array(32);\n\n        // Reset IV value\n        C = new Array(4);\n        for (var i = 0; i < 4; i++)\n            C[i] = new Uint8Array(32);\n        arraycopy(C2, 0, C[2], 0, C2.length);\n\n        // Make data\n        var d = new Uint8Array(buffer(data));\n\n        var n = d.length;\n        var r = n % 32, q = (n - r) / 32;\n\n        // Proccess full blocks\n        for (var i = 0; i < q; i++) {\n            var b = new Uint8Array(d.buffer, i * 32, 32);\n\n            summing.call(this, b); // calc sum M\n            process.call(this, b, 0);\n        }\n\n        // load d the remadder with padding zero;\n        if (r > 0) {\n            var b = new Uint8Array(d.buffer, q * 32),\n                    c = new Uint8Array(32);\n            arraycopy(b, 0, c, 0, r);\n            summing.call(this, c); // calc sum M\n            process.call(this, c, 0);\n\n        }\n\n        // get length into L (byteCount * 8 = bitCount) in little endian.\n        var L = new Uint8Array(32), n8 = n * 8, k = 0;\n        while (n8 > 0) {\n            L[k++] = n8 & 0xff;\n            n8 = Math.floor(n8 / 256);\n        }\n        process.call(this, L, 0);\n        process.call(this, Sum, 0);\n\n        var h = H.buffer;\n\n        // Swap hash for SignalCom\n        if (this.procreator === 'SC')\n            h = swap(h);\n\n        return h;\n    };\n\n} // </editor-fold>\n)();\n\n/**\n * Algorithm name SHA-1<br><br>\n *\n * https://tools.ietf.org/html/rfc3174\n *\n * The digest method returns digest data in according to SHA-1.<br>\n *\n * @memberOf GostDigest\n * @method digest\n * @instance\n * @param {(ArrayBuffer|TypedArray)} data Data\n * @returns {ArrayBuffer} Digest of data\n */\nvar digestSHA1 = (function () // <editor-fold defaultstate=\"collapsed\">\n{\n\n    // Create a buffer for each 80 word block.\n    var state, block = new Uint32Array(80);\n\n    function common(a, e, w, k, f) {\n        return (f + e + w + k + ((a << 5) | (a >>> 27))) >>> 0;\n    }\n\n    function f1(a, b, c, d, e, w) {\n        return common(a, e, w, 0x5A827999, d ^ (b & (c ^ d)));\n    }\n\n    function f2(a, b, c, d, e, w) {\n        return common(a, e, w, 0x6ED9EBA1, b ^ c ^ d);\n    }\n\n    function f3(a, b, c, d, e, w) {\n        return common(a, e, w, 0x8F1BBCDC, (b & c) | (d & (b | c)));\n    }\n\n    function f4(a, b, c, d, e, w) {\n        return common(a, e, w, 0xCA62C1D6, b ^ c ^ d);\n    }\n\n    function cycle(state, block) {\n        var a = state[0],\n                b = state[1],\n                c = state[2],\n                d = state[3],\n                e = state[4];\n\n        // Partially unroll loops so we don't have to shift variables.\n        var fn = f1;\n        for (var i = 0; i < 80; i += 5) {\n            if (i === 20) {\n                fn = f2;\n            }\n            else if (i === 40) {\n                fn = f3;\n            }\n            else if (i === 60) {\n                fn = f4;\n            }\n            e = fn(a, b, c, d, e, block[i]);\n            b = ((b << 30) | (b >>> 2)) >>> 0;\n            d = fn(e, a, b, c, d, block[i + 1]);\n            a = ((a << 30) | (a >>> 2)) >>> 0;\n            c = fn(d, e, a, b, c, block[i + 2]);\n            e = ((e << 30) | (e >>> 2)) >>> 0;\n            b = fn(c, d, e, a, b, block[i + 3]);\n            d = ((d << 30) | (d >>> 2)) >>> 0;\n            a = fn(b, c, d, e, a, block[i + 4]);\n            c = ((c << 30) | (c >>> 2)) >>> 0;\n        }\n        state[0] += a;\n        state[1] += b;\n        state[2] += c;\n        state[3] += d;\n        state[4] += e;\n    }\n\n    // Swap bytes for 32bits word\n    function swap32(b) {\n        return ((b & 0xff) << 24)\n                | ((b & 0xff00) << 8)\n                | ((b >> 8) & 0xff00)\n                | ((b >> 24) & 0xff);\n    }\n\n    // input is a Uint8Array bitstream of the data\n    return function (data) {\n        var d = new Uint8Array(buffer(data)), dlen = d.length;\n\n        // Pad the input string length.\n        var len = dlen + 9;\n        if (len % 64) {\n            len += 64 - (len % 64);\n        }\n\n        state = new Uint32Array(5);\n        state[0] = 0x67452301;\n        state[1] = 0xefcdab89;\n        state[2] = 0x98badcfe;\n        state[3] = 0x10325476;\n        state[4] = 0xc3d2e1f0;\n\n        for (var ofs = 0; ofs < len; ofs += 64) {\n\n            // Copy input to block and write padding as needed\n            for (var i = 0; i < 64; i++) {\n                var b = 0,\n                        o = ofs + i;\n                if (o < dlen) {\n                    b = d[o];\n                }\n                else if (o === dlen) {\n                    b = 0x80;\n                }\n                else {\n                    // Write original bit length as a 64bit big-endian integer to the end.\n                    var x = len - o - 1;\n                    if (x >= 0 && x < 4) {\n                        b = (dlen << 3 >>> (x * 8)) & 0xff;\n                    }\n                }\n\n                // Interpret the input bytes as big-endian per the spec\n                if (i % 4 === 0) {\n                    block[i >> 2] = b << 24;\n                }\n                else {\n                    block[i >> 2] |= b << ((3 - (i % 4)) * 8);\n                }\n            }\n\n            // Extend the block\n            for (var i = 16; i < 80; i++) {\n                var w = block[i - 3] ^ block[i - 8] ^ block[i - 14] ^ block[i - 16];\n                block[i] = (w << 1) | (w >>> 31);\n            }\n\n            cycle(state, block);\n\n        }\n\n        // Swap the bytes around since they are big endian internally\n        for (var i = 0; i < 5; i++)\n            state[i] = swap32(state[i]);\n        return state.buffer;\n    };\n\n} // </editor-fold>\n)();\n\n/**\n * Algorithm name GOST R 34.11-HMAC<br><br>\n *\n * HMAC with the specified hash function.\n * @memberOf GostDigest\n * @method sign\n * @instance\n * @param {ArrayBuffer} key The key for HMAC.\n * @param {Hash} data Data\n */\nfunction signHMAC(key, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    // GOST R 34.11-94 - B=32b, L=32b\n    // GOST R 34.11-256 - B=64b, L=32b\n    // GOST R 34.11-512 - B=64b, L=64b\n    var b = (this.digest === digest94) ? 32 : 64,\n            l = this.bitLength / 8,\n            k = buffer(key),\n            d = buffer(data), k0;\n    if (k.byteLength === b)\n        k0 = new Uint8Array(k);\n    else {\n        var k0 = new Uint8Array(b);\n        if (k.byteLength > b) {\n            k0.set(new Uint8Array(this.digest(k)));\n        } else {\n            k0.set(new Uint8Array(k));\n        }\n    }\n    var s0 = new Uint8Array(b + d.byteLength),\n            s1 = new Uint8Array(b + l);\n    for (var i = 0; i < b; i++) {\n        s0[i] = k0[i] ^ 0x36;\n        s1[i] = k0[i] ^ 0x5C;\n    }\n    s0.set(new Uint8Array(d), b);\n    s1.set(new Uint8Array(this.digest(s0)), b);\n    return this.digest(s1);\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.11-HMAC<br><br>\n *\n * Verify HMAC based on GOST R 34.11 hash\n *\n * @memberOf GostDigest\n * @method verify\n * @instance\n * @param {(ArrayBuffer|TypedArray)} key Key which used for HMAC generation\n * @param {(ArrayBuffer|TypedArray)} signature generated HMAC\n * @param {(ArrayBuffer|TypedArray)} data Data\n * @returns {boolean} HMAC verified = true\n */\nfunction verifyHMAC(key, signature, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    var hmac = new Uint8Array(this.sign(key, data)),\n            test = new Uint8Array(signature);\n    if (hmac.length !== test.length)\n        return false;\n    for (var i = 0, n = hmac.length; i < n; i++)\n        if (hmac[i] !== test[i])\n            return false;\n    return true;\n} // </editor-fold>\n\n\n/**\n * Algorithm name GOST R 34.11-KDF<br><br>\n *\n * Simple generate key 256/512 bit random seed for derivation algorithms\n *\n * @memberOf GostDigest\n * @method generateKey\n * @instance\n * @returns {ArrayBuffer} Generated key\n */\nfunction generateKey() // <editor-fold defaultstate=\"collapsed\">\n{\n    return getSeed(this.bitLength).buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.11-PFXKDF<br><br>\n *\n * Derive bits from password (PKCS12 mode)\n *  <ul>\n *      <li>algorithm.salt - random value, salt</li>\n *      <li>algorithm.iterations - number of iterations</li>\n *  </ul>\n * @memberOf GostDigest\n * @method deriveBits\n * @instance\n * @param {ArrayBuffer} baseKey - password after UTF-8 decoding\n * @param {number} length output bit-length\n * @returns {ArrayBuffer} result\n */\nfunction deriveBitsPFXKDF(baseKey, length) // <editor-fold defaultstate=\"collapsed\">\n{\n    if (length % 8 > 0)\n        throw new DataError('Length must multiple of 8');\n    var u = this.bitLength / 8, v = (this.digest === digest94) ? 32 : 64,\n            n = length / 8, r = this.iterations;\n    //   1.  Construct a string, D (the \"diversifier\"), by concatenating v/8\n    //       copies of ID.\n    var ID = this.diversifier, D = new Uint8Array(v);\n    for (var i = 0; i < v; i++)\n        D[i] = ID;\n    //   2.  Concatenate copies of the salt together to create a string S of\n    //       length v(ceiling(s/v)) bits (the final copy of the salt may be\n    //       truncated to create S).  Note that if the salt is the empty\n    //       string, then so is S.\n    var S0 = new Uint8Array(buffer(this.salt)), s = S0.length,\n            slen = v * Math.ceil(s / v), S = new Uint8Array(slen);\n    for (var i = 0; i < slen; i++)\n        S[i] = S0[i % s];\n    //   3.  Concatenate copies of the password together to create a string P\n    //       of length v(ceiling(p/v)) bits (the final copy of the password\n    //       may be truncated to create P).  Note that if the password is the\n    //       empty string, then so is P.\n    var P0 = new Uint8Array(buffer(baseKey)), p = P0.length,\n            plen = v * Math.ceil(p / v), P = new Uint8Array(plen);\n    for (var i = 0; i < plen; i++)\n        P[i] = P0[i % p];\n    //   4.  Set I=S||P to be the concatenation of S and P.\n    var I = new Uint8Array(slen + plen);\n    arraycopy(S, 0, I, 0, slen);\n    arraycopy(P, 0, I, slen, plen);\n    //   5.  Set c=ceiling(n/u).\n    var c = Math.ceil(n / u);\n    //   6.  For i=1, 2, ..., c, do the following:\n    var A = new Uint8Array(c * u);\n    for (var i = 0; i < c; i++) {\n        //  A.  Set A2=H^r(D||I). (i.e., the r-th hash of D||1,\n        //      H(H(H(... H(D||I))))\n        var H = new Uint8Array(v + slen + plen);\n        arraycopy(D, 0, H, 0, v);\n        arraycopy(I, 0, H, v, slen + plen);\n        for (var j = 0; j < r; j++)\n            H = new Uint8Array(this.digest(H));\n        arraycopy(H, 0, A, i * u, u);\n        //  B.  Concatenate copies of Ai to create a string B of length v\n        //      bits (the final copy of Ai may be truncated to create B).\n        var B = new Uint8Array(v);\n        for (var j = 0; j < v; j++)\n            B[j] = H[j % u];\n        //  C.  Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit\n        //      blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by\n        //      setting I_j=(I_j+B+1) mod 2^v for each j.\n        var k = (slen + plen) / v;\n        for (j = 0; j < k; j++) {\n            var cf = 1, w;\n            for (var l = v - 1; l >= 0; --l) {\n                w = I[v * j + l] + B[l] + cf;\n                cf = w >>> 8;\n                I[v * j + l] = w & 0xff;\n            }\n        }\n    }\n    //   7.  Concatenate A_1, A_2, ..., A_c together to form a pseudorandom\n    //       bit string, A.\n    //   8.  Use the first n bits of A as the output of this entire process.\n    var R = new Uint8Array(n);\n    arraycopy(A, 0, R, 0, n);\n    return R.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.11-KDF<br><br>\n *\n * Derive bits for KEK deversification in 34.10-2012 algorithm\n * KDF(KEK, UKM, label) = HMAC256 (KEK,  0x01|label|0x00|UKM|0x01|0x00)\n * Default label = 0x26|0xBD|0xB8|0x78\n *\n * @memberOf GostDigest\n * @method deriveBits\n * @instance\n * @param {(ArrayBuffer|TypedArray)} baseKey base key for deriviation\n * @param {number} length output bit-length\n * @returns {ArrayBuffer} result\n */\nfunction deriveBitsKDF(baseKey, length) // <editor-fold defaultstate=\"collapsed\">\n{\n    if (length % 8 > 0)\n        throw new DataError('Length must be multiple of 8');\n    var rlen = length / 8, label, context = new Uint8Array(buffer(this.context)),\n            blen = this.bitLength / 8, n = Math.ceil(rlen / blen);\n    if (this.label)\n        label = new Uint8Array(buffer(this.label));\n    else\n        label = new Uint8Array([0x26, 0xBD, 0xB8, 0x78]);\n    var result = new Uint8Array(rlen);\n    for (var i = 0; i < n; i++) {\n        var data = new Uint8Array(label.length + context.length + 4);\n        data[0] = i + 1;\n        data.set(label, 1);\n        data[label.length + 1] = 0x00;\n        data.set(context, label.length + 2);\n        data[data.length - 2] = length >>> 8;\n        data[data.length - 1] = length & 0xff;\n        result.set(new Uint8Array(signHMAC.call(this, baseKey, data), 0,\n                i < n - 1 ? blen : rlen - i * blen), i * blen);\n    }\n    return result.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.11-PBKDF1<br><br>\n *\n * Derive bits from password\n *  <ul>\n *      <li>algorithm.salt - random value, salt</li>\n *      <li>algorithm.iterations - number of iterations</li>\n *  </ul>\n * @memberOf GostDigest\n * @method deriveBits\n * @instance\n * @param {ArrayBuffer} baseKey - password after UTF-8 decoding\n * @param {number} length output bit-length\n * @returns {ArrayBuffer} result\n */\nfunction deriveBitsPBKDF1(baseKey, length) // <editor-fold defaultstate=\"collapsed\">\n{\n    if (length < this.bitLength / 2 || length % 8 > 0)\n        throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8');\n    var hLen = this.bitLength / 8, dkLen = length / 8,\n            c = this.iterations,\n            P = new Uint8Array(buffer(baseKey)),\n            S = new Uint8Array(buffer(this.salt)),\n            slen = S.length, plen = P.length,\n            T = new Uint8Array(plen + slen),\n            DK = new Uint8Array(dkLen);\n    if (dkLen > hLen)\n        throw new DataError('Invalid parameters: Length value');\n    arraycopy(P, 0, T, 0, plen);\n    arraycopy(S, 0, T, plen, slen);\n    for (var i = 0; i < c; i++)\n        T = new Uint8Array(this.digest(T));\n    arraycopy(T, 0, DK, 0, dkLen);\n    return DK.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.11-PBKDF2<br><br>\n *\n * Derive bits from password\n *  <ul>\n *      <li>algorithm.salt - random value, salt</li>\n *      <li>algorithm.iterations - number of iterations</li>\n *  </ul>\n * @memberOf GostDigest\n * @method deriveBits\n * @instance\n * @param {ArrayBuffer} baseKey - password after UTF-8 decoding\n * @param {number} length output bit-length\n * @returns {ArrayBuffer} result\n */\nfunction deriveBitsPBKDF2(baseKey, length) // <editor-fold defaultstate=\"collapsed\">\n{\n    var diversifier = this.diversifier || 1; // For PKCS12 MAC required 3*length\n    length = length * diversifier;\n    if (length < this.bitLength / 2 || length % 8 > 0)\n        throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8');\n    var hLen = this.bitLength / 8, dkLen = length / 8,\n            c = this.iterations,\n            P = new Uint8Array(buffer(baseKey)),\n            S = new Uint8Array(buffer(this.salt));\n    var slen = S.byteLength,\n            data = new Uint8Array(slen + 4);\n    arraycopy(S, 0, data, 0, slen);\n\n    if (dkLen > (0xffffffff - 1) * 32)\n        throw new DataError('Invalid parameters: Length value');\n    var n = Math.ceil(dkLen / hLen),\n            DK = new Uint8Array(dkLen);\n    for (var i = 1; i <= n; i++) {\n        data[slen] = i >>> 24 & 0xff;\n        data[slen + 1] = i >>> 16 & 0xff;\n        data[slen + 2] = i >>> 8 & 0xff;\n        data[slen + 3] = i & 0xff;\n\n        var U = new Uint8Array(signHMAC.call(this, P, data)), Z = U;\n        for (var j = 1; j < c; j++) {\n            U = new Uint8Array(signHMAC.call(this, P, U));\n            for (var k = 0; k < hLen; k++)\n                Z[k] = U[k] ^ Z[k];\n        }\n        var ofs = (i - 1) * hLen;\n        arraycopy(Z, 0, DK, ofs, Math.min(hLen, dkLen - ofs));\n    }\n    if (diversifier > 1) {\n        var rLen = dkLen / diversifier, R = new Uint8Array(rLen);\n        arraycopy(DK, dkLen - rLen, R, 0, rLen);\n        return R.buffer;\n    } else\n        return DK.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.11-CPKDF<br><br>\n *\n * Derive bits from password. CryptoPro algorithm\n *  <ul>\n *      <li>algorithm.salt - random value, salt</li>\n *      <li>algorithm.iterations - number of iterations</li>\n *  </ul>\n * @memberOf GostDigest\n * @method deriveBits\n * @instance\n * @param {ArrayBuffer} baseKey - password after UTF-8 decoding\n * @param {number} length output bit-length\n * @returns {ArrayBuffer} result\n */\nfunction deriveBitsCP(baseKey, length) {\n    if (length > this.bitLength || length % 8 > 0)\n        throw new DataError('Length can\\'t be more than ' + this.bitLength + ' bits and multiple of 8');\n    // GOST R 34.11-94 - B=32b, L=32b\n    // GOST R 34.11-256 - B=64b, L=32b\n    // GOST R 34.11-512 - B=64b, L=64b\n    var b = (this.digest === digest94) ? 32 : 64,\n            l = this.bitLength / 8,\n            p = baseKey && baseKey.byteLength > 0 ? new Uint8Array(buffer(baseKey)) : false,\n            plen = p ? p.length : 0,\n            iterations = this.iterations,\n            salt = new Uint8Array(buffer(this.salt)),\n            slen = salt.length,\n            d = new Uint8Array(slen + plen);\n    arraycopy(salt, 0, d, 0, slen);\n    if (p)\n        arraycopy(p, 0, d, slen, plen);\n\n    var h = new Uint8Array(this.digest(d)),\n            k = new Uint8Array(b),\n            s0 = new Uint8Array(b),\n            s1 = new Uint8Array(b);\n    var c = 'DENEFH028.760246785.IUEFHWUIO.EF';\n    for (var i = 0; i < c.length; i++)\n        k[i] = c.charCodeAt(i);\n\n    d = new Uint8Array(2 * (b + l));\n    for (var j = 0; j < iterations; j++) {\n        for (var i = 0; i < b; i++) {\n            s0[i] = k[i] ^ 0x36;\n            s1[i] = k[i] ^ 0x5C;\n            k[i] = 0;\n        }\n        arraycopy(s0, 0, d, 0, b);\n        arraycopy(h, 0, d, b, l);\n        arraycopy(s1, 0, d, b + l, b);\n        arraycopy(h, 0, d, b + l + b, l);\n        arraycopy(new Uint8Array(this.digest(d)), 0, k, 0, l);\n    }\n    for (var i = 0; i < l; i++) {\n        s0[i] = k[i] ^ 0x36;\n        s1[i] = k[i] ^ 0x5C;\n        k[i] = 0;\n    }\n    d = new Uint8Array(2 * l + slen + plen);\n    arraycopy(s0, 0, d, 0, l);\n    arraycopy(salt, 0, d, l, slen);\n    arraycopy(s1, 0, d, l + slen, l);\n    if (p)\n        arraycopy(p, 0, d, l + slen + l, plen);\n    h = this.digest(this.digest(d));\n    if (length === this.bitLength)\n        return h;\n    else {\n        var rlen = length / 8, r = new Uint8Array(rlen);\n        arraycopy(h, 0, r, 0, rlen);\n        return r.buffer;\n    }\n}\n\n/**\n * Algorithm name GOST R 34.11-KDF or GOST R 34.11-PBKDF2 or other<br><br>\n *\n * Derive key from derive bits subset\n *\n * @memberOf GostDigest\n * @method deriveKey\n * @instance\n * @param {ArrayBuffer} baseKey\n * @returns {ArrayBuffer}\n */\nfunction deriveKey(baseKey) // <editor-fold defaultstate=\"collapsed\">\n{\n    return this.deriveBits(baseKey, this.keySize * 8);\n} // </editor-fold>\n\n/**\n * GOST R 34.11 Algorithm<br><br>\n *\n * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}<br><br>\n *\n * Normalized algorithm identifier common parameters:\n *\n *  <ul>\n *      <li><b>name</b> Algorithm name 'GOST R 34.11'</li>\n *      <li><b>version</b> Algorithm version\n *          <ul>\n *              <li><b>1994</b> old-style 256 bits digest based on GOST 28147-89</li>\n *              <li><b>2012</b> 256 ro 512 bits digest algorithm \"Streebog\" GOST R 34.11-2012 (default)</li>\n *          </ul>\n *      </li>\n *      <li><b>length</b> Digest length\n *          <ul>\n *              <li><b>256</b> 256 bits digest</li>\n *              <li><b>512</b> 512 bits digest, valid only for algorithm \"Streebog\"</li>\n *          </ul>\n *      </li>\n *      <li><b>mode</b> Algorithm mode\n *          <ul>\n *              <li><b>HASH</b> simple digest mode (default)</li>\n *              <li><b>HMAC</b> HMAC algorithm based on GOST R 34.11</li>\n *              <li><b>KDF</b> Derive bits for KEK deversification</li>\n *              <li><b>PBKDF2</b> Password based key dirivation algorithms PBKDF2 (based on HMAC)</li>\n *              <li><b>PFXKDF</b> Password based PFX key dirivation algorithms</li>\n *              <li><b>CPKDF</b> CpyptoPro Password based key dirivation algorithms</li>\n *          </ul>\n *      </li>\n *      <li><b>sBox</b> Paramset sBox for GOST 28147-89. Used only if version = 1994</li>\n *  </ul>\n *\n * Supported algorithms, modes and parameters:\n *\n *  <ul>\n *      <li>Digest HASH mode (default)</li>\n *      <li>Sign/Verify HMAC modes parameters depends on version and length\n *          <ul>\n *              <li><b>version: 1994</b> HMAC parameters (B = 32, L = 32)</li>\n *              <li><b>version: 2012, length: 256</b> HMAC parameters (B = 64, L = 32)</li>\n *              <li><b>version: 2012, length: 512</b> HMAC parameters  (B = 64, L = 64)</li>\n *          </ul>\n *      </li>\n *      <li>DeriveBits/DeriveKey KDF mode\n *          <ul>\n *              <li><b>context</b> {@link CryptoOperationData} Context of the key derivation</li>\n *              <li><b>label</b> {@link CryptoOperationData} Label that identifies the purpose for the derived keying material</li>\n *          </ul>\n *      </li>\n *      <li>DeriveBits/DeriveKey PBKDF2 mode\n *          <ul>\n *              <li><b>salt</b> {@link CryptoOperationData} Random salt as input for HMAC algorithm</li>\n *              <li><b>iterations</b> Iteration count. GOST recomended value 1000 (default) or 2000</li>\n *              <li><b>diversifier</b> Deversifier, ID=1 - key material for performing encryption or decryption, ID=3 - integrity key for MACing</li>\n *          </ul>\n *      </li>\n *      <li>DeriveBits/DeriveKey PFXKDF mode\n *          <ul>\n *              <li><b>salt</b> {@link CryptoOperationData} Random salt as input for HMAC algorithm</li>\n *              <li><b>iterations</b> Iteration count. GOST recomended value 1000 (default) or 2000</li>\n *              <li><b>diversifier</b> Deversifier, ID=1 - key material for performing encryption or decryption,\n *              ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing</li>\n *          </ul>\n *      </li>\n *      <li>DeriveBits/DeriveKey CPKDF mode\n *          <ul>\n *              <li><b>salt</b> {@link CryptoOperationData} Random salt as input for HMAC algorithm</li>\n *              <li><b>iterations</b> Iteration count. GOST recomended value 1000 (default) or 2000</li>\n *          </ul>\n *      </li>\n *  </ul>\n *\n * @class GostDigest\n * @param {AlgorithmIdentifier} algorithm WebCryptoAPI algorithm identifier\n */\nfunction GostDigest(algorithm) // <editor-fold defaultstate=\"collapsed\">\n{\n\n    algorithm = algorithm || {};\n\n    this.name = (algorithm.name || 'GOST R 34.10') + '-' + ((algorithm.version || 2012) % 100) +\n            ((algorithm.version || 2012) > 1 ? '-' + (algorithm.length || 256) : '') +\n            (((algorithm.mode || 'HASH') !== 'HASH') ? '-' + algorithm.mode : '') +\n            (algorithm.procreator ? '/' + algorithm.procreator : '') +\n            (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : '');\n\n    // Algorithm procreator\n    this.procreator = algorithm.procreator;\n\n    // Bit length\n    this.bitLength = algorithm.length || 256;\n\n    switch (algorithm.version || 2012) {\n        case 1: // SHA-1\n            this.digest = digestSHA1;\n            this.bitLength = 160;\n            break;\n        case 1994:\n            this.digest = digest94;\n            // Define chiper algorithm\n            this.sBox = (algorithm.sBox || (algorithm.procreator === 'SC' ? 'D-SC' : 'D-A')).toUpperCase();\n\n            //if (!GostCipher)\n            //    GostCipher = root.GostCipher;\n            if (!GostCipher)\n                throw new NotSupportedError('Object GostCipher not found');\n\n            this.cipher = new GostCipher({\n                name: 'GOST 28147',\n                block: 'ECB',\n                sBox: this.sBox,\n                procreator: this.procreator\n            });\n\n            break;\n        case 2012:\n            this.digest = digest2012;\n            break;\n        default:\n            throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported');\n    }\n\n    // Key size\n    this.keySize = algorithm.keySize || (algorithm.version <= 2 ? this.bitLength / 8 : 32);\n\n    switch (algorithm.mode || 'HASH') {\n        case 'HASH':\n            break;\n        case 'HMAC':\n            this.sign = signHMAC;\n            this.verify = verifyHMAC;\n            this.generateKey = generateKey;\n            break;\n        case 'KDF':\n            this.deriveKey = deriveKey;\n            this.deriveBits = deriveBitsKDF;\n            this.label = algorithm.label;\n            this.context = algorithm.context;\n            break;\n        case 'PBKDF2':\n            this.deriveKey = deriveKey;\n            this.deriveBits = deriveBitsPBKDF2;\n            this.generateKey = generateKey;\n            this.salt = algorithm.salt;\n            this.iterations = algorithm.iterations || 2000;\n            this.diversifier = algorithm.diversifier || 1;\n            break;\n        case 'PFXKDF':\n            this.deriveKey = deriveKey;\n            this.deriveBits = deriveBitsPFXKDF;\n            this.generateKey = generateKey;\n            this.salt = algorithm.salt;\n            this.iterations = algorithm.iterations || 2000;\n            this.diversifier = algorithm.diversifier || 1;\n            break;\n        case 'CPKDF':\n            this.deriveKey = deriveKey;\n            this.deriveBits = deriveBitsCP;\n            this.generateKey = generateKey;\n            this.salt = algorithm.salt;\n            this.iterations = algorithm.iterations || 2000;\n            break;\n        default:\n            throw new NotSupportedError('Algorithm mode ' + algorithm.mode + ' not supported');\n    }\n} // </editor-fold>\n\nexport default GostDigest;\n"
  },
  {
    "path": "src/core/vendor/gost/gostEngine.mjs",
    "content": "/**\n * @file GOST 34.10-2012 signature function with 1024/512 bits digest\n * @version 1.76\n * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved.\n */\n\n/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *    \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n * \n */\n\nimport GostRandom from './gostRandom.mjs';\nimport GostCipher from './gostCipher.mjs';\nimport GostDigest from './gostDigest.mjs';\nimport GostSign from './gostSign.mjs';\n\n/*\n    * Engine definition base on normalized algorithm identifier\n    * \n    */ // <editor-fold defaultstate=\"collapsed\">\n\nvar root = {};\n\n// Define engine\nfunction defineEngine(method, algorithm) {\n    if (!algorithm)\n        throw new (root.SyntaxError || Error)('Algorithm not defined');\n\n    if (!algorithm.name)\n        throw new (root.SyntaxError || Error)('Algorithm name not defined');\n\n    var name = algorithm.name, mode = algorithm.mode;\n    if ((name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2') && (method === 'generateKey' ||\n            (mode === 'MAC' && (method === 'sign' || method === 'verify')) ||\n            ((mode === 'KW' || mode === 'MASK') && (method === 'wrapKey' || method === 'unwrapKey')) ||\n            ((!mode || mode === 'ES') && (method === 'encrypt' || method === 'decrypt')))) {\n        return 'GostCipher';\n\n    } else if ((name === 'GOST R 34.11' || name === 'SHA') && (method === 'digest' ||\n            (mode === 'HMAC' && (method === 'sign' || method === 'verify' || method === 'generateKey')) ||\n            ((mode === 'KDF' || mode === 'PBKDF2' || mode === 'PFXKDF' || mode === 'CPKDF') &&\n                    (method === 'deriveKey' || method === 'deriveBits' || method === 'generateKey')))) {\n        return 'GostDigest';\n\n    } else if (name === 'GOST R 34.10' && (method === 'generateKey' ||\n            ((!mode || mode === 'SIGN') && (method === 'sign' || method === 'verify')) ||\n            (mode === 'MASK' && (method === 'wrapKey' || method === 'unwrapKey')) ||\n            (mode === 'DH' && (method === 'deriveKey' || method === 'deriveBits')))) {\n        return 'GostSign';\n    } else\n        throw new (root.NotSupportedError || Error)('Algorithm ' + name + '-' + mode + ' is not valid for ' + method);\n} // </editor-fold>\n\n/**\n * Object implements dedicated Web Workers and provide a simple way to create \n * and run GOST cryptographic algorithms in background thread. \n * \n * Object provide interface to GOST low-level cryptogric classes:\n *  <ul>\n *      <li>GostCipher - implementation of GOST 28147, GOST R 34.12, GOST R 34.13 Encryption algorithms. Reference {@link http://tools.ietf.org/html/rfc5830}</li>\n *      <li>GostDigest - implementation of GOST R 34.11 Hash Function algorithms. References {@link http://tools.ietf.org/html/rfc5831} and {@link http://tools.ietf.org/html/rfc6986}</li>\n *      <li>GostSign - implementation of GOST R 34.10 Digital Signature algorithms. References {@link http://tools.ietf.org/html/rfc5832} and {@link http://tools.ietf.org/html/rfc7091}</li>\n *  </ul>\n * @namespace gostEngine\n */\nvar gostEngine = {\n    /**\n     * gostEngine.execute(algorithm, method, args) Entry point to execution \n     * all low-level GOST cryptographic methods\n     * \n     *  <ul>\n     *      <li>Determine the appropriate engine for a given execution method</li>\n     *      <li>Create cipher object for determineted engine</li>\n     *      <li>Execute method of cipher with given args</li>\n     *  </ul>\n     * \n     * @memberOf gostEngine\n     * @param {AlgorithmIndentifier} algorithm Algorithm identifier\n     * @param {string} method Crypto method for execution\n     * @param {Array} args Method arguments (keys, data, additional parameters)\n     * @returns {(CryptoOperationData|Key|KeyPair|boolean)} Result of method execution\n     */\n    execute: function (algorithm, method, args) // <editor-fold defaultstate=\"collapsed\">\n    {\n        // Define engine for GOST algorithms\n        var engine = defineEngine(method, algorithm);\n        // Create cipher \n        var cipher = this['get' + engine](algorithm);\n        // Execute method\n        return cipher[method].apply(cipher, args);\n    }, // </editor-fold>\n    /**\n     * gostEngine.getGostCipher(algorithm) returns GOST 28147 / GOST R 34.12 cipher instance<br><br>\n     * \n     * GOST 28147-89 / GOST R 34.12-15 Encryption Algorithm<br><br> \n     * When keys and initialization vectors are converted to/from byte arrays, \n     * little-endian byte order is assumed.<br><br>\n     * \n     * Normalized algorithm identifier common parameters:\n     * \n     *  <ul>\n     *      <li><b>name</b> Algorithm name 'GOST 28147' or 'GOST R 34.12'</li>\n     *      <li><b>version</b> Algorithm version, number\n     *          <ul>\n     *              <li><b>1989</b> Current version of standard</li>\n     *              <li><b>2015</b> New draft version of standard</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>length</b> Block length\n     *          <ul>\n     *              <li><b>64</b> 64 bits length (default)</li>\n     *              <li><b>128</b> 128 bits length (only for version 2015)</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>mode</b> Algorithm mode, string\n     *          <ul>\n     *              <li><b>ES</b> Encryption mode (default)</li>\n     *              <li><b>MAC</b> \"imitovstavka\" (MAC) mode</li>\n     *              <li><b>KW</b> Key wrapping mode</li>\n     *              <li><b>MASK</b> Key mask mode</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>sBox</b> Paramset sBox for GOST 28147-89, string. Used only if version = 1989</li>\n     *  </ul>\n     *  \n     * Supported algorithms, modes and parameters:\n     * \n     *  <ul>\n     *      <li>Encript/Decrypt mode (ES)\n     *          <ul>\n     *              <li><b>block</b> Block mode, string. Default ECB</li>\n     *              <li><b>keyMeshing</b> Key meshing mode, string. Default NO</li>\n     *              <li><b>padding</b> Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others</li>\n     *              <li><b>iv</b> {@link CryptoOperationData} Initial vector with length of block. Default - zero block</li>\n     *          </ul>\n     *      </li>\n     *      <li>Sign/Verify mode (MAC)\n     *          <ul>\n     *              <li><b>macLength</b> Length of mac in bits (default - 32 bits)</li>\n     *              <li><b>iv</b> {@link CryptoOperationData} Initial vector with length of block. Default - zero block</li>\n     *          </ul>\n     *      </li>\n     *      <li>Wrap/Unwrap key mode (KW)\n     *          <ul>\n     *              <li><b>keyWrapping</b> Mode of keywrapping, string. Default NO - standard GOST key wrapping</li>\n     *              <li><b>ukm</b> {@link CryptoOperationData} User key material. Default - random generated value</li>\n     *          </ul>\n     *      </li>\n     *      <li>Wrap/Unwrap key mode (MASK)</li>\n     *  </ul>\n     *      \n     * Supported paramters values:\n     *      \n     *  <ul>\n     *      <li>Block modes (parameter 'block')\n     *          <ul>\n     *              <li><b>ECB</b> \"prostaya zamena\" (ECB) mode (default)</li>\n     *              <li><b>CFB</b> \"gammirovanie s obratnoj svyaziyu\" (64-bit CFB) mode</li>\n     *              <li><b>CTR</b> \"gammirovanie\" (counter) mode</li>\n     *              <li><b>CBC</b> Cipher-Block-Chaining (CBC) mode</li>\n     *          </ul>\n     *      </li>\n     *      <li>Key meshing modes (parameter 'keyMeshing')\n     *          <ul>\n     *              <li><b>NO</b> No key wrapping (default)</li>\n     *              <li><b>CP</b> CryptoPor Key key meshing</li>\n     *          </ul>\n     *      </li>\n     *      <li>Padding modes (parameter 'padding')\n     *          <ul>\n     *              <li><b>NO</b> No padding only for CFB and CTR modes</li>\n     *              <li><b>PKCS5</b> PKCS#5 padding mode</li>\n     *              <li><b>ZERO</b> Zero bits padding mode</li>\n     *              <li><b>RANDOM</b> Random bits padding mode</li>\n     *          </ul>\n     *      </li>\n     *      <li>Wrapping key modes (parameter 'keyWrapping')\n     *          <ul>\n     *              <li><b>NO</b> Ref. rfc4357 6.1 GOST 28147-89 Key wrapping</li>\n     *              <li><b>CP</b> CryptoPro Key wrapping mode</li>\n     *              <li><b>SC</b> SignalCom Key wrapping mode</li>\n     *          </ul>\n     *      </li>\n     *  </ul>\n     * \n     * @memberOf gostEngine\n     * @param {AlgorithmIndentifier} algorithm Algorithm identifier\n     * @returns {GostCipher} Instance of GostCipher\n     */\n    getGostCipher: function (algorithm) // <editor-fold defaultstate=\"collapsed\">\n    {\n        return new (GostCipher || (GostCipher = root.GostCipher))(algorithm);\n    }, // </editor-fold>\n    /**\n     * gostEngine.getGostDigest(algorithm) returns GOST R 34.11 cipher instance<br><br>\n     * \n     * Normalized algorithm identifier common parameters:\n     * \n     *  <ul>\n     *      <li><b>name</b> Algorithm name 'GOST R 34.11'</li>\n     *      <li><b>version</b> Algorithm version\n     *          <ul>\n     *              <li><b>1994</b> old-style 256 bits digest based on GOST 28147-89</li>\n     *              <li><b>2012</b> 256 ro 512 bits digest algorithm \"Streebog\" GOST R 34.11-2012 (default)</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>length</b> Digest length\n     *          <ul>\n     *              <li><b>256</b> 256 bits digest</li>\n     *              <li><b>512</b> 512 bits digest, valid only for algorithm \"Streebog\"</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>mode</b> Algorithm mode\n     *          <ul>\n     *              <li><b>HASH</b> simple digest mode (default)</li>\n     *              <li><b>HMAC</b> HMAC algorithm based on GOST R 34.11</li>\n     *              <li><b>KDF</b> Derive bits for KEK deversification</li>\n     *              <li><b>PBKDF2</b> Password based key dirivation algorithms PBKDF2 (based on HMAC)</li>\n     *              <li><b>PFXKDF</b> PFX key dirivation algorithms PFXKDF</li>\n     *              <li><b>CPKDF</b> CryptoPro Password based key dirivation algorithms</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>sBox</b> Paramset sBox for GOST 28147-89. Used only if version = 1994</li>\n     *  </ul>\n     * \n     * Supported algorithms, modes and parameters:\n     * \n     *  <ul>\n     *      <li>Digest HASH mode (default)</li>\n     *      <li>Sign/Verify HMAC modes parameters depends on version and length\n     *          <ul>\n     *              <li><b>version: 1994</b> HMAC parameters (B = 32, L = 32)</li>\n     *              <li><b>version: 2012, length: 256</b> HMAC parameters (B = 64, L = 32)</li>\n     *              <li><b>version: 2012, length: 512</b> HMAC parameters  (B = 64, L = 64)</li>\n     *          </ul>\n     *      </li>\n     *      <li>DeriveBits/DeriveKey KDF mode\n     *          <ul>\n     *              <li><b>context</b> {@link CryptoOperationData} Context of the key derivation</li>\n     *              <li><b>label</b> {@link CryptoOperationData} Label that identifies the purpose for the derived keying material</li>\n     *          </ul>\n     *      </li>\n     *      <li>DeriveBits/DeriveKey PBKDF2 mode\n     *          <ul>\n     *              <li><b>salt</b> {@link CryptoOperationData} Random salt as input for HMAC algorithm</li>\n     *              <li><b>iterations</b> Iteration count. GOST recomended value 1000 (default) or 2000</li>\n     *          </ul>\n     *      </li>\n     *      <li>DeriveBits/DeriveKey PFXKDF mode\n     *          <ul>\n     *              <li><b>salt</b> {@link CryptoOperationData} Random salt as input for HMAC algorithm</li>\n     *              <li><b>iterations</b> Iteration count. GOST recomended value 1000 (default) or 2000</li>\n     *              <li><b>diversifier</b> Deversifier, ID=1 - key material for performing encryption or decryption, \n     *              ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing</li>\n     *          </ul>\n     *      </li>\n     *      <li>DeriveBits/DeriveKey CPKDF mode\n     *          <ul>\n     *              <li><b>salt</b> {@link CryptoOperationData} Random salt as input for HMAC algorithm</li>\n     *              <li><b>iterations</b> Iteration count. GOST recomended value 1000 (default) or 2000</li>\n     *          </ul>\n     *      </li>\n     *  </ul>\n     * \n     * @memberOf gostEngine\n     * @param {AlgorithmIndentifier} algorithm Algorithm identifier\n     * @returns {GostDigest} Instance of GostDigest\n     */\n    getGostDigest: function (algorithm) // <editor-fold defaultstate=\"collapsed\">\n    {\n        return new (GostDigest || (GostDigest = root.GostDigest))(algorithm);\n    }, // </editor-fold>\n    /**\n     * gostEngine.getGostSign(algorithm) returns GOST R 34.10 cipher instance<br><br>\n     * \n     * Normalized algorithm identifier common parameters:\n     * \n     *  <ul>\n     *      <li><b>name</b> Algorithm name 'GOST R 34.10'</li>\n     *      <li><b>version</b> Algorithm version\n     *          <ul>\n     *              <li><b>1994</b> - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash</li>\n     *              <li><b>2001</b> - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash</li>\n     *              <li><b>2012</b> - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>length</b> Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm\n     *          <ul>\n     *              <li><b>GOST R 34.10-256</b> - 256 bits digest, default mode</li>\n     *              <li><b>GOST R 34.10-512</b> - 512 bits digest only for GOST R 34.11-2012 hash</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>mode</b> Algorithm mode\n     *          <ul>\n     *              <li><b>SIGN</b> Digital signature mode (default)</li>\n     *              <li><b>DH</b> Diffie-Hellman key generation and key agreement mode</li>\n     *              <li><b>MASK</b> Key mask mode</li>\n     *          </ul>\n     *      </li>\n     *      <li><b>sBox</b> Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001</li>\n     *  </ul>\n     *  \n     * Supported algorithms, modes and parameters:\n     * \n     *  <ul>\n     *      <li>Sign/Verify mode (SIGN)</li>\n     *      <li>Wrap/Unwrap mode (MASK)</li>\n     *      <li>DeriveKey/DeriveBits mode (DH)\n     *          <ul>\n     *              <li>{@link CryptoOperationData} <b>ukm</b> User key material. Default - random generated value</li>\n     *              <li>{@link CryptoOperationData} <b>public</b> The peer's EC public key data</li>\n     *          </ul>\n     *      </li>\n     *      <li>GenerateKey mode (SIGN and DH and MASK) version = 1994\n     *          <ul>\n     *              <li><b>namedParam</b> Paramset for key generation algorithm. If specified no additianal parameters required</li>\n     *          </ul>\n     *          Additional parameters, if namedParam not specified\n     *          <ul>\n     *              <li><b>modulusLength</b> Bit length of p (512 or 1024 bits). Default = 1024</li>\n     *              <li><b>p</b> {@link CryptoOperationData} Modulus, prime number, 2^(t-1)<p<2^t</li>\n     *              <li><b>q</b> {@link CryptoOperationData} Order of cyclic group, prime number, 2^254<q<2^256, q is a factor of p-1</li>\n     *              <li><b>a</b> {@link CryptoOperationData} Generator, integer, 1<a<p-1, at that aq (mod p) = 1</li>\n     *          </ul>\n     *      </li>\n     *      <li>GenerateKey mode (SIGN and DH and MASK) version = 2001 or 2012\n     *          <ul>\n     *              <li><b>namedCurve</b> Paramset for key generation algorithm. If specified no additianal parameters required</li>\n     *          </ul>\n     *          Additional EC parameters, if namedCurve not specified\n     *          <ul>\n     *              <li><b>p</b> {@link CryptoOperationData} Prime number - elliptic curve modulus</li>\n     *              <li><b>a</b> {@link CryptoOperationData} Coefficients a of the elliptic curve E</li>\n     *              <li><b>b</b> {@link CryptoOperationData} Coefficients b of the elliptic curve E</li>\n     *              <li><b>q</b> {@link CryptoOperationData} Prime number - order of cyclic group</li>\n     *              <li><b>x</b> {@link CryptoOperationData} Base point p x-coordinate</li>\n     *              <li><b>y</b> {@link CryptoOperationData} Base point p y-coordinate</li>\n     *          </ul>\n     *      </li>\n     *  </ul>\n     *  \n     * @memberOf gostEngine\n     * @param {AlgorithmIndentifier} algorithm Algorithm identifier\n     * @returns {GostSign} Instance of GostSign\n     */\n    getGostSign: function (algorithm) // <editor-fold defaultstate=\"collapsed\">\n    {\n        return new (GostSign || (GostSign = root.GostSign))(algorithm);\n    } // </editor-fold>\n};\n\n/*\n    * Worker method execution\n    * \n    */ // <editor-fold defaultstate=\"collapsed\">\n\n// Worker for gostCripto method execution\nif (root.importScripts) {\n\n    /**\n     * Method called when {@link SubtleCrypto} calls its own postMessage() \n     * method with data parameter: algorithm, method and arg.<br>\n     * Call method execute and postMessage() results to onmessage event handler \n     * in the main process.<br>\n     * If error occurred onerror event handler executed in main process.\n     * \n     * @memberOf gostEngine\n     * @name onmessage\n     * @param {MessageEvent} event Message event with data {algorithm, method, args}\n     */\n    root.onmessage = function (event) {\n        try {\n            postMessage({\n                id: event.data.id,\n                result: gostEngine.execute(event.data.algorithm,\n                        event.data.method, event.data.args)});\n        } catch (e) {\n            postMessage({\n                id: event.data.id,\n                error: e.message\n            });\n        }\n    };\n} else {\n\n    // Load dependens\n    var baseUrl = '', nameSuffix = '';\n    // Try to define from DOM model\n    if (typeof document !== 'undefined') {\n        (function () {\n            var regs = /^(.*)gostCrypto(.*)\\.js$/i;\n            var list = document.querySelectorAll('script');\n            for (var i = 0, n = list.length; i < n; i++) {\n                var value = list[i].getAttribute('src');\n                var test = regs.exec(value);\n                if (test) {\n                    baseUrl = test[1];\n                    nameSuffix = test[2];\n                }\n            }\n        })();\n    }\n\n    // Local importScripts procedure for include dependens\n    var importScripts = function () {\n        for (var i = 0, n = arguments.length; i < n; i++) {\n            var name = arguments[i].split('.'),\n                    src = baseUrl + name[0] + nameSuffix + '.' + name[1];\n            var el = document.querySelector('script[src=\"' + src + '\"]');\n            if (!el) {\n                el = document.createElement('script');\n                el.setAttribute('src', src);\n                document.head.appendChild(el);\n            }\n        }\n    };\n\n    // Import engines\n    if (!GostRandom)\n        importScripts('gostRandom.js');\n    if (!GostCipher)\n        importScripts('gostCipher.js');\n    if (!GostDigest)\n        importScripts('gostDigest.js');\n    if (!GostSign)\n        importScripts('gostSign.js');\n} // </editor-fold>\n\nexport default gostEngine;\n\n"
  },
  {
    "path": "src/core/vendor/gost/gostRandom.mjs",
    "content": "/** \n * Implementation Web Crypto random generatore for GOST algorithms\n * 1.76\n * 2014-2016, Rudolf Nickolaev. All rights reserved.\n * \n * Exported for CyberChef by mshwed [m@ttshwed.com]\n */\n\n/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *    \n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n * \n */\nimport crypto from 'crypto';\n\n\n/**\n * The gostCrypto provide general purpose cryptographic functionality for\n * GOST standards including a cryptographically strong pseudo-random number \n * generator seeded with truly random values.\n * \n * @Class GostRandom\n * \n */ // <editor-fold defaultstate=\"collapsed\">\n\nvar root = {};\nvar rootCrypto = crypto;\n\nvar TypeMismatchError = Error;\nvar QuotaExceededError = Error;\n\n// Initialize mouse and time counters for random generator    \nvar randomRing = {\n    seed: new Uint8Array(1024),\n    getIndex: 0,\n    setIndex: 0,\n    set: function (x) {\n        if (this.setIndex >= 1024)\n            this.setIndex = 0;\n        this.seed[this.setIndex++] = x;\n    },\n    get: function () {\n        if (this.getIndex >= 1024)\n            this.getIndex = 0;\n        return this.seed[this.getIndex++];\n    }\n};\n\nif (typeof document !== 'undefined') {\n    try {\n        // Mouse move event to fill random array\n        document.addEventListener('mousemove', function (e) {\n            randomRing.set((Date.now() & 255) ^\n                    ((e.clientX || e.pageX) & 255) ^\n                    ((e.clientY || e.pageY) & 255));\n        }, false);\n    } catch (e) {\n    }\n\n    try {\n        // Keypress event to fill random array\n        document.addEventListener('keydown', function (e) {\n            randomRing.set((Date.now() & 255) ^\n                    (e.keyCode & 255));\n        }, false);\n    } catch (e) {\n    }\n} // </editor-fold>\n\nfunction GostRandom() {\n}\n\n/**\n * The getRandomValues method generates cryptographically random values. <br><br>\n * \n * Random generator based on JavaScript Web Crypto random genereator \n * (if it is possible) or  Math.random mixed with time and parameters of \n * mouse and keyboard events\n * \n * @memberOf GostRandom\n * @param {(ArrayBuffer|ArrayBufferView)} array Destination buffer for random data\n */\nGostRandom.prototype.getRandomValues = function (array) // <editor-fold defaultstate=\"collapsed\">\n{\n\n    if (!array.byteLength)\n        throw new TypeMismatchError('Array is not of an integer type (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, or Uint32Array)');\n\n    if (array.byteLength > 65536)\n        throw new QuotaExceededError('Byte length of array can\\'t be greate then 65536');\n\n    var u8 = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);\n    if (rootCrypto && rootCrypto.getRandomValues) {\n        // Native window cryptographic interface\n        rootCrypto.getRandomValues(u8);\n    } else {\n        // Standard Javascript method\n        for (var i = 0, n = u8.length; i < n; i++)\n            u8[i] = Math.floor(256 * Math.random()) & 255;\n    }\n\n    // Mix bio randomizator\n    for (var i = 0, n = u8.length; i < n; i++)\n        u8[i] = u8[i] ^ randomRing.get();\n    return array;\n}; // </editor-fold>\n\nexport default GostRandom;\n"
  },
  {
    "path": "src/core/vendor/gost/gostSign.mjs",
    "content": "/**\n * @file GOST 34.10-2012 signature function with 1024/512 bits digest\n * @version 1.76\n * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved.\n */\n\n/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Used library JSBN http://www-cs-students.stanford.edu/~tjw/jsbn/\n * Copyright (c) 2003-2005  Tom Wu (tjw@cs.Stanford.EDU)\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n import GostRandom from './gostRandom.mjs';\n import GostDigest from './gostDigest.mjs';\n\n import crypto from 'crypto';\n\n    /*\n     * Predefined curves and params collection\n     *\n     * http://tools.ietf.org/html/rfc5832\n     * http://tools.ietf.org/html/rfc7091\n     * http://tools.ietf.org/html/rfc4357\n     *\n     */ // <editor-fold defaultstate=\"collapsed\">\n\nvar root = {};\nvar rootCrypto = crypto;\nvar CryptoOperationData = ArrayBuffer;\n\nvar OperationError = Error,\n        DataError = Error,\n        NotSupportedError = Error;\n\n// Predefined named curve collection\nvar ECGostParams = {\n    'S-256-TEST': {\n        a: 7,\n        b: '0x5FBFF498AA938CE739B8E022FBAFEF40563F6E6A3472FC2A514C0CE9DAE23B7E',\n        p: '0x8000000000000000000000000000000000000000000000000000000000000431',\n        q: '0x8000000000000000000000000000000150FE8A1892976154C59CFC193ACCF5B3',\n        x: 2,\n        y: '0x8E2A8A0E65147D4BD6316030E16D19C85C97F0A9CA267122B96ABBCEA7E8FC8'\n    },\n    'S-256-A': {\n        a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94',\n        b: 166,\n        p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97',\n        q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893',\n        x: 1,\n        y: '0x8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14'\n    },\n    'S-256-B': {\n        a: '0x8000000000000000000000000000000000000000000000000000000000000C96',\n        b: '0x3E1AF419A269A5F866A7D3C25C3DF80AE979259373FF2B182F49D4CE7E1BBC8B',\n        p: '0x8000000000000000000000000000000000000000000000000000000000000C99',\n        q: '0x800000000000000000000000000000015F700CFFF1A624E5E497161BCC8A198F',\n        x: 1,\n        y: '0x3FA8124359F96680B83D1C3EB2C070E5C545C9858D03ECFB744BF8D717717EFC'\n    },\n    'S-256-C': {\n        a: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D7598',\n        b: 32858,\n        p: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D759B',\n        q: '0x9B9F605F5A858107AB1EC85E6B41C8AA582CA3511EDDFB74F02F3A6598980BB9',\n        x: 0,\n        y: '0x41ECE55743711A8C3CBF3783CD08C0EE4D4DC440D4641A8F366E550DFDB3BB67'\n    },\n    'P-256': {\n        p: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF',\n        a: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC',\n        b: '0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B',\n        x: '0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296',\n        y: '0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5',\n        q: '0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551'\n    },\n    'T-512-TEST': {\n        a: 7,\n        b: '0x1CFF0806A31116DA29D8CFA54E57EB748BC5F377E49400FDD788B649ECA1AC4361834013B2AD7322480A89CA58E0CF74BC9E540C2ADD6897FAD0A3084F302ADC',\n        p: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DF1D852741AF4704A0458047E80E4546D35B8336FAC224DD81664BBF528BE6373',\n        q: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DA82F2D7ECB1DBAC719905C5EECC423F1D86E25EDBE23C595D644AAF187E6E6DF',\n        x: '0x24D19CC64572EE30F396BF6EBBFD7A6C5213B3B3D7057CC825F91093A68CD762FD60611262CD838DC6B60AA7EEE804E28BC849977FAC33B4B530F1B120248A9A',\n        y: '0x2BB312A43BD2CE6E0D020613C857ACDDCFBF061E91E5F2C3F32447C259F39B2C83AB156D77F1496BF7EB3351E1EE4E43DC1A18B91B24640B6DBB92CB1ADD371E'\n    },\n    'T-512-A': {\n        p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7',\n        a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4',\n        b: '0xE8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760',\n        q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275',\n        x: 3,\n        y: '0x7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4'\n    },\n    'T-512-B': {\n        p: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006F',\n        a: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006C',\n        b: '0x687D1B459DC841457E3E06CF6F5E2517B97C7D614AF138BCBF85DC806C4B289F3E965D2DB1416D217F8B276FAD1AB69C50F78BEE1FA3106EFB8CCBC7C5140116',\n        q: '0x800000000000000000000000000000000000000000000000000000000000000149A1EC142565A545ACFDB77BD9D40CFA8B996712101BEA0EC6346C54374F25BD',\n        x: 2,\n        y: '0x1A8F7EDA389B094C2C071E3647A8940F3C123B697578C213BE6DD9E6C8EC7335DCB228FD1EDF4A39152CBCAAF8C0398828041055F94CEEEC7E21340780FE41BD'\n    }\n};\nECGostParams['X-256-A'] = ECGostParams['S-256-A'];\nECGostParams['X-256-B'] = ECGostParams['S-256-C'];\nECGostParams['T-256-TEST'] = ECGostParams['S-256-TEST'];\nECGostParams['T-256-A'] = ECGostParams['S-256-A'];\nECGostParams['T-256-B'] = ECGostParams['S-256-B'];\nECGostParams['T-256-C'] = ECGostParams['S-256-C'];\n\n\nvar GostParams = {\n    'S-TEST': {\n        modulusLength: 512, // bit length of p (512 or 1024 bits)\n        p: '0xEE8172AE8996608FB69359B89EB82A69854510E2977A4D63BC97322CE5DC3386EA0A12B343E9190F23177539845839786BB0C345D165976EF2195EC9B1C379E3',\n        q: '0x98915E7EC8265EDFCDA31E88F24809DDB064BDC7285DD50D7289F0AC6F49DD2D',\n        a: '0x9e96031500c8774a869582d4afde2127afad2538b4b6270a6f7c8837b50d50f206755984a49e509304d648be2ab5aab18ebe2cd46ac3d8495b142aa6ce23e21c'\n    },\n    'S-A': {\n        modulusLength: 1024,\n        p: '0xB4E25EFB018E3C8B87505E2A67553C5EDC56C2914B7E4F89D23F03F03377E70A2903489DD60E78418D3D851EDB5317C4871E40B04228C3B7902963C4B7D85D52B9AA88F2AFDBEB28DA8869D6DF846A1D98924E925561BD69300B9DDD05D247B5922D967CBB02671881C57D10E5EF72D3E6DAD4223DC82AA1F7D0294651A480DF',\n        q: '0x972432A437178B30BD96195B773789AB2FFF15594B176DD175B63256EE5AF2CF',\n        a: '0x8FD36731237654BBE41F5F1F8453E71CA414FFC22C25D915309E5D2E62A2A26C7111F3FC79568DAFA028042FE1A52A0489805C0DE9A1A469C844C7CABBEE625C3078888C1D85EEA883F1AD5BC4E6776E8E1A0750912DF64F79956499F1E182475B0B60E2632ADCD8CF94E9C54FD1F3B109D81F00BF2AB8CB862ADF7D40B9369A'\n    },\n    'S-B': {\n        modulusLength: 1024,\n        p: '0xC6971FC57524B30C9018C5E621DE15499736854F56A6F8AEE65A7A404632B1BCF0349FFCAFCB0A103177971FC1612ADCDB8C8CC938C70225C8FD12AFF01B1D064E0AD6FDE6AB9159166CB9F2FC171D92F0CC7B6A6B2CD7FA342ACBE2C9315A42D576B1ECCE77A963157F3D0BD96A8EB0B0F3502AD238101B05116334F1E5B7AB',\n        q: '0xB09D634C10899CD7D4C3A7657403E05810B07C61A688BAB2C37F475E308B0607',\n        a: '0x3D26B467D94A3FFC9D71BF8DB8934084137264F3C2E9EB16DCA214B8BC7C872485336744934FD2EF5943F9ED0B745B90AA3EC8D70CDC91682478B664A2E1F8FB56CEF2972FEE7EDB084AF746419B854FAD02CC3E3646FF2E1A18DD4BEB3C44F7F2745588029649674546CC9187C207FB8F2CECE8E2293F68395C4704AF04BAB5'\n    },\n    'S-C': {\n        modulusLength: 1024,\n        p: '0x9D88E6D7FE3313BD2E745C7CDD2AB9EE4AF3C8899E847DE74A33783EA68BC30588BA1F738C6AAF8AB350531F1854C3837CC3C860FFD7E2E106C3F63B3D8A4C034CE73942A6C3D585B599CF695ED7A3C4A93B2B947B7157BB1A1C043AB41EC8566C6145E938A611906DE0D32E562494569D7E999A0DDA5C879BDD91FE124DF1E9',\n        q: '0xFADD197ABD19A1B4653EECF7ECA4D6A22B1F7F893B641F901641FBB555354FAF',\n        a: '0x7447ED7156310599070B12609947A5C8C8A8625CF1CF252B407B331F93D639DDD1BA392656DECA992DD035354329A1E95A6E32D6F47882D960B8F10ACAFF796D13CD9611F853DAB6D2623483E46788708493937A1A29442598AEC2E0742022563440FE9C18740ECE6765AC05FAF024A64B026E7E408840819E962E7E5F401AE3'\n    },\n    'S-D': {\n        modulusLength: 1024,\n        p: '0x80F102D32B0FD167D069C27A307ADAD2C466091904DBAA55D5B8CC7026F2F7A1919B890CB652C40E054E1E9306735B43D7B279EDDF9102001CD9E1A831FE8A163EED89AB07CF2ABE8242AC9DEDDDBF98D62CDDD1EA4F5F15D3A42A6677BDD293B24260C0F27C0F1D15948614D567B66FA902BAA11A69AE3BCEADBB83E399C9B5',\n        q: '0xF0F544C418AAC234F683F033511B65C21651A6078BDA2D69BB9F732867502149',\n        a: '0x6BCC0B4FADB3889C1E06ADD23CC09B8AB6ECDEDF73F04632595EE4250005D6AF5F5ADE44CB1E26E6263C672347CFA26F9E9393681E6B759733784CDE5DBD9A14A39369DFD99FA85CC0D10241C4010343F34A91393A706CF12677CBFA1F578D6B6CFBE8A1242CFCC94B3B653A476E145E3862C18CC3FED8257CFEF74CDB205BF1'\n    },\n    'X-A': {\n        modulusLength: 1024,\n        p: '0xCA3B3F2EEE9FD46317D49595A9E7518E6C63D8F4EB4D22D10D28AF0B8839F079F8289E603B03530784B9BB5A1E76859E4850C670C7B71C0DF84CA3E0D6C177FE9F78A9D8433230A883CD82A2B2B5C7A3306980278570CDB79BF01074A69C9623348824B0C53791D53C6A78CAB69E1CFB28368611A397F50F541E16DB348DBE5F',\n        q: '0xCAE4D85F80C147704B0CA48E85FB00A9057AA4ACC44668E17F1996D7152690D9',\n        a: '0xBE27D652F2F1E339DA734211B85B06AE4DE236AA8FBEEB3F1ADCC52CD43853777E834A6A518138678A8ADBD3A55C70A7EAB1BA7A0719548677AAF4E609FFB47F6B9D7E45B0D06D83D7ADC53310ABD85783E7317F7EC73268B6A9C08D260B85D8485696CA39C17B17F044D1E050489036ABD381C5E6BF82BA352A1AFF136601AF'\n    },\n    'X-B': {\n        modulusLength: 1024,\n        p: '0x9286DBDA91ECCFC3060AA5598318E2A639F5BA90A4CA656157B2673FB191CD0589EE05F4CEF1BD13508408271458C30851CE7A4EF534742BFB11F4743C8F787B11193BA304C0E6BCA25701BF88AF1CB9B8FD4711D89F88E32B37D95316541BF1E5DBB4989B3DF13659B88C0F97A3C1087B9F2D5317D557DCD4AFC6D0A754E279',\n        q: '0xC966E9B3B8B7CDD82FF0F83AF87036C38F42238EC50A876CD390E43D67B6013F',\n        a: '0x7E9C3096676F51E3B2F9884CF0AC2156779496F410E049CED7E53D8B7B5B366B1A6008E5196605A55E89C3190DABF80B9F1163C979FCD18328DAE5E9048811B370107BB7715F82091BB9DE0E33EE2FED6255474F8769FCE5EAFAEEF1CB5A32E0D5C6C2F0FC0B3447072947F5B4C387666993A333FC06568E534AD56D2338D729'\n    },\n    'X-C': {\n        modulusLength: 1024,\n        p: '0xB194036ACE14139D36D64295AE6C50FC4B7D65D8B340711366CA93F383653908EE637BE428051D86612670AD7B402C09B820FA77D9DA29C8111A8496DA6C261A53ED252E4D8A69A20376E6ADDB3BDCD331749A491A184B8FDA6D84C31CF05F9119B5ED35246EA4562D85928BA1136A8D0E5A7E5C764BA8902029A1336C631A1D',\n        q: '0x96120477DF0F3896628E6F4A88D83C93204C210FF262BCCB7DAE450355125259',\n        a: '0x3F1817052BAA7598FE3E4F4FC5C5F616E122CFF9EBD89EF81DC7CE8BF56CC64B43586C80F1C4F56DD5718FDD76300BE336784259CA25AADE5A483F64C02A20CF4A10F9C189C433DEFE31D263E6C9764660A731ECCAECB74C8279303731E8CF69205BC73E5A70BDF93E5BB681DAB4EEB9C733CAAB2F673C475E0ECA921D29782E'\n    }\n}; // </editor-fold>\n\n/*\n    * BigInteger arithmetic tools\n    * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\n// Bits per one element\nvar DB = 28, DM = (1 << DB) - 1, DV = 1 << DB,\n        FV = Math.pow(2, 52), F1 = 52 - DB, F2 = 2 * DB - 52;\n\nfunction am(y, i, x, w, j, c, n) {\n    var xl = x & 0x3fff, xh = x >> 14;\n    while (--n >= 0) {\n        var l = y[i] & 0x3fff;\n        var h = y[i++] >> 14;\n        var m = xh * l + h * xl;\n        l = xl * l + ((m & 0x3fff) << 14) + w[j] + c;\n        c = (l >> 28) + (m >> 14) + xh * h;\n        w[j++] = l & 0xfffffff;\n    }\n    return c;\n}\n\nfunction nbi(words) {\n    var r = new Array(Math.ceil(words));\n    r.s = 0;\n    r.t = 0;\n    return r;\n}\n\nfunction copyTo(x, r) {\n    for (var i = x.t - 1; i >= 0; --i)\n        r[i] = x[i];\n    r.t = x.t;\n    r.s = x.s;\n    return r;\n}\n\nfunction copy(x) {\n    return copyTo(x, nbi(x.t));\n}\n\nfunction setInt(x, i) {\n    x.t = 1;\n    x.s = (i < 0) ? -1 : 0;\n    if (i > 0)\n        x[0] = i;\n    else if (i < -1)\n        x[0] = i + DV;\n    else\n        x.t = 0;\n    return x;\n}\n\nfunction nbv(i) {\n    var r = nbi(1);\n    setInt(r, i);\n    return r;\n}\n\nvar ZERO = nbv(0), ONE = nbv(1), THREE = nbv(3);\n\nfunction clamp(x) {\n    var c = x.s & DM;\n    while (x.t > 0 && x[x.t - 1] === c)\n        --x.t;\n    return x;\n}\n\nfunction subTo(x, a, r) {\n    var i = 0, c = 0, m = Math.min(a.t, x.t);\n    while (i < m) {\n        c += x[i] - a[i];\n        r[i++] = c & DM;\n        c >>= DB;\n    }\n    if (a.t < x.t) {\n        c -= a.s;\n        while (i < x.t) {\n            c += x[i];\n            r[i++] = c & DM;\n            c >>= DB;\n        }\n        c += x.s;\n    }\n    else {\n        c += x.s;\n        while (i < a.t) {\n            c -= a[i];\n            r[i++] = c & DM;\n            c >>= DB;\n        }\n        c -= a.s;\n    }\n    r.s = (c < 0) ? -1 : 0;\n    if (c < -1)\n        r[i++] = DV + c;\n    else if (c > 0)\n        r[i++] = c;\n    r.t = i;\n    return clamp(r);\n}\n\nfunction sub(x, y) {\n    return subTo(x, y, nbi(x.t));\n}\n\nfunction addTo(x, a, r) {\n    var i = 0, c = 0, m = Math.min(a.t, x.t);\n    while (i < m) {\n        c += x[i] + a[i];\n        r[i++] = c & DM;\n        c >>= DB;\n    }\n    if (a.t < x.t) {\n        c += a.s;\n        while (i < x.t) {\n            c += x[i];\n            r[i++] = c & DM;\n            c >>= DB;\n        }\n        c += x.s;\n    }\n    else {\n        c += x.s;\n        while (i < a.t) {\n            c += a[i];\n            r[i++] = c & DM;\n            c = c >> DB;\n        }\n        c += a.s;\n    }\n    r.s = (c < 0) ? -1 : 0;\n    if (c > 0)\n        r[i++] = c;\n    else if (c < -1)\n        r[i++] = DV + c;\n    r.t = i;\n    return clamp(r);\n}\n\nfunction add(x, y) {\n    return addTo(x, y, nbi(x.t));\n}\n\nfunction negTo(x, r) {\n    return subTo(ZERO, x, r);\n}\n\nfunction neg(x) {\n    return negTo(x, nbi(x.t));\n}\n\nfunction absTo(x, r) {\n    return (x.s < 0) ? negTo(r) : copyTo(r);\n}\n\nfunction abs(x) {\n    return (x.s < 0) ? neg(x) : x;\n}\n\nfunction compare(x, a) {\n    var r = x.s - a.s;\n    if (r !== 0)\n        return r;\n    var i = x.t;\n    r = i - a.t;\n    if (r !== 0)\n        return (x.s < 0) ? -r : r;\n    while (--i >= 0)\n        if ((r = x[i] - a[i]) !== 0)\n            return r;\n    return 0;\n}\n\nfunction equals(x, y) {\n    return(compare(x, y) === 0);\n}\n\nfunction min(x, y) {\n    return(compare(x, y) < 0) ? x : y;\n}\n\nfunction max(x, y) {\n    return(compare(x, y) > 0) ? x : y;\n}\n\nfunction nbits(x) {\n    var r = 1, t;\n    if ((t = x >>> 16) !== 0) {\n        x = t;\n        r += 16;\n    }\n    if ((t = x >> 8) !== 0) {\n        x = t;\n        r += 8;\n    }\n    if ((t = x >> 4) !== 0) {\n        x = t;\n        r += 4;\n    }\n    if ((t = x >> 2) !== 0) {\n        x = t;\n        r += 2;\n    }\n    if ((t = x >> 1) !== 0) {\n        x = t;\n        r += 1;\n    }\n    return r;\n}\n\nfunction dshlTo(x, n, r) {\n    var i;\n    for (i = x.t - 1; i >= 0; --i)\n        r[i + n] = x[i];\n    for (i = n - 1; i >= 0; --i)\n        r[i] = 0;\n    r.t = x.t + n;\n    r.s = x.s;\n    return r;\n}\nfunction dshrTo(x, n, r) {\n    for (var i = n; i < x.t; ++i)\n        r[i - n] = x[i];\n    r.t = Math.max(x.t - n, 0);\n    r.s = x.s;\n    return r;\n}\n\nfunction shlTo(x, n, r) {\n    var bs = n % DB;\n    var cbs = DB - bs;\n    var bm = (1 << cbs) - 1;\n    var ds = Math.floor(n / DB), c = (x.s << bs) & DM, i;\n    for (i = x.t - 1; i >= 0; --i) {\n        r[i + ds + 1] = (x[i] >> cbs) | c;\n        c = (x[i] & bm) << bs;\n    }\n    for (i = ds - 1; i >= 0; --i)\n        r[i] = 0;\n    r[ds] = c;\n    r.t = x.t + ds + 1;\n    r.s = x.s;\n    return clamp(r);\n}\n\nfunction shrTo(x, n, r) {\n    r.s = x.s;\n    var ds = Math.floor(n / DB);\n    if (ds >= x.t) {\n        r.t = 0;\n        return;\n    }\n    var bs = n % DB;\n    var cbs = DB - bs;\n    var bm = (1 << bs) - 1;\n    r[0] = x[ds] >> bs;\n    for (var i = ds + 1; i < x.t; ++i) {\n        r[i - ds - 1] |= (x[i] & bm) << cbs;\n        r[i - ds] = x[i] >> bs;\n    }\n    if (bs > 0)\n        r[x.t - ds - 1] |= (x.s & bm) << cbs;\n    r.t = x.t - ds;\n    return clamp(r);\n}\n\nfunction shl(x, n) {\n    var r = nbi(x.t);\n    if (n < 0)\n        shrTo(x, -n, r);\n    else\n        shlTo(x, n, r);\n    return r;\n}\n\nfunction shr(x, n) {\n    var r = nbi(x.t);\n    if (n < 0)\n        shlTo(x, -n, r);\n    else\n        shrTo(x, n, r);\n    return r;\n}\n\nfunction bitLength(x) {\n    if (x.t <= 0)\n        return 0;\n    return DB * (x.t - 1) + nbits(x[x.t - 1] ^ (x.s & DM));\n}\n\nfunction mulTo(b, a, r) {\n    var x = abs(b), y = abs(a);\n    var i = x.t;\n    r.t = i + y.t;\n    while (--i >= 0)\n        r[i] = 0;\n    for (i = 0; i < y.t; ++i)\n        r[i + x.t] = am(x, 0, y[i], r, i, 0, x.t);\n    r.s = 0;\n    if (b.s !== a.s)\n        subTo(ZERO, r, r);\n    return clamp(r);\n}\n\nfunction mul(x, y) {\n    return mulTo(x, y, nbi(x.t + y.t));\n}\n\nfunction sqrTo(a, r) {\n    var x = abs(a);\n    var i = r.t = 2 * x.t;\n    while (--i >= 0)\n        r[i] = 0;\n    for (i = 0; i < x.t - 1; ++i) {\n        var c = am(x, i, x[i], r, 2 * i, 0, 1);\n        if ((r[i + x.t] += am(x, i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) {\n            r[i + x.t] -= x.DV;\n            r[i + x.t + 1] = 1;\n        }\n    }\n    if (r.t > 0)\n        r[r.t - 1] += am(x, i, x[i], r, 2 * i, 0, 1);\n    r.s = 0;\n    return clamp(r);\n}\n\nfunction sqr(a) {\n    return sqrTo(a, nbi(a.t * 2));\n}\n\nfunction divRemTo(n, m, q, r) {\n    var pm = abs(m);\n    if (pm.t <= 0)\n        throw new OperationError('Division by zero');\n    var pt = abs(n);\n    if (pt.t < pm.t) {\n        if (q)\n            setInt(q, 0);\n        if (r)\n            copyTo(n, r);\n        return q;\n    }\n    if (!r)\n        r = nbi(m.t);\n    var y = nbi(m.t), ts = n.s, ms = m.s;\n    var nsh = DB - nbits(pm[pm.t - 1]);\n    if (nsh > 0) {\n        shlTo(pm, nsh, y);\n        shlTo(pt, nsh, r);\n    }\n    else {\n        copyTo(pm, y);\n        copyTo(pt, r);\n    }\n    var ys = y.t;\n    var y0 = y[ys - 1];\n    if (y0 === 0)\n        return q;\n    var yt = y0 * (1 << F1) + ((ys > 1) ? y[ys - 2] >> F2 : 0);\n    var d1 = FV / yt, d2 = (1 << F1) / yt, e = 1 << F2;\n    var i = r.t, j = i - ys, t = !q ? nbi(Math.max(n.t - m.t, 1)) : q;\n    dshlTo(y, j, t);\n    if (compare(r, t) >= 0) {\n        r[r.t++] = 1;\n        subTo(r, t, r);\n    }\n    dshlTo(ONE, ys, t);\n    subTo(t, y, y);\n    while (y.t < ys)\n        y[y.t++] = 0;\n    while (--j >= 0) {\n        var qd = (r[--i] === y0) ? DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2);\n        if ((r[i] += am(y, 0, qd, r, j, 0, ys)) < qd) {\n            dshlTo(y, j, t);\n            subTo(r, t, r);\n            while (r[i] < --qd)\n                subTo(r, t, r);\n        }\n    }\n    if (q) {\n        dshrTo(r, ys, q);\n        if (ts !== ms)\n            subTo(ZERO, q, q);\n    }\n    r.t = ys;\n    clamp(r);\n    if (nsh > 0)\n        shrTo(r, nsh, r);\n    if (ts < 0)\n        subTo(ZERO, r, r);\n    return q;\n}\n\nfunction modTo(b, a, r) {\n    divRemTo(abs(b), a, null, r);\n    if (b.s < 0 && compare(r, ZERO) > 0)\n        subTo(a, r, r);\n    return r;\n}\n\nfunction mod(b, a) {\n    return modTo(b, a, nbi(a.t));\n}\n\nfunction div(b, a) {\n    return divRemTo(b, a, nbi(Math.max(b.t - a.t, 1)), null);\n}\n\nfunction isEven(x) {\n\n    return ((x.t > 0) ? (x[0] & 1) : x.s) === 0;\n}\n\nfunction isZero(x) {\n    return equals(x, ZERO);\n}\n\nfunction sig(x) {\n    if (x.s < 0)\n        return -1;\n    else if (x.t <= 0 || (x.t === 1 && x[0] <= 0))\n        return 0;\n    else\n        return 1;\n}\n\nfunction invMod(x, m) {\n    var ac = isEven(m);\n    if ((isEven(x) && ac) || sig(m) === 0)\n        return ZERO;\n    var u = copy(m), v = copy(x);\n    var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1);\n    while (sig(u) !== 0) {\n        while (isEven(u)) {\n            shrTo(u, 1, u);\n            if (ac) {\n                if (!isEven(a) || !isEven(b)) {\n                    addTo(a, x, a);\n                    subTo(b, m, b);\n                }\n                shrTo(a, 1, a);\n            }\n            else if (!isEven(b))\n                subTo(b, m, b);\n            shrTo(b, 1, b);\n        }\n        while (isEven(v)) {\n            shrTo(v, 1, v);\n            if (ac) {\n                if (!isEven(c) || !isEven(d)) {\n                    addTo(c, x, c);\n                    subTo(d, m, d);\n                }\n                shrTo(c, 1, c);\n            }\n            else if (!isEven(d))\n                subTo(d, m, d);\n            shrTo(d, 1, d);\n        }\n        if (compare(u, v) >= 0) {\n            subTo(u, v, u);\n            if (ac)\n                subTo(a, c, a);\n            subTo(b, d, b);\n        }\n        else {\n            subTo(v, u, v);\n            if (ac)\n                subTo(c, a, c);\n            subTo(d, b, d);\n        }\n    }\n    if (compare(v, ONE) !== 0)\n        return ZERO;\n    if (compare(d, m) >= 0)\n        return subtract(d, m);\n    if (sig(d) < 0)\n        addTo(d, m, d);\n    else\n        return d;\n    if (sig(d) < 0)\n        return add(d, m);\n    else\n        return d;\n}\n\nfunction testBit(x, n) {\n    var j = Math.floor(n / DB);\n    if (j >= x.t)\n        return (x.s !== 0);\n    return ((x[j] & (1 << (n % DB))) !== 0);\n}\n\nfunction nothing(x) {\n    return x;\n}\n\nfunction extend(c, o) {\n    for (var i in o)\n        c.prototype[i] = o[i];\n} // </editor-fold>\n\n/*\n    * Classic, Barret, Mongomery reductions, optimized ExpMod algorithms\n    * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\n// Classic reduction\nvar Classic = function (m) {\n    this.m = m;\n};\n\nextend(Classic, {\n    convert: function (x) {\n        if (x.s < 0 || compare(x, this.m) >= 0)\n            return mod(x, this.m);\n        else\n            return x;\n    },\n    revert: nothing,\n    reduce: function (x) {\n        modTo(x, this.m, x);\n    },\n    sqrTo: function (x, r) {\n        sqrTo(x, r);\n        this.reduce(r);\n    },\n    mulTo: function (x, y, r) {\n        mulTo(x, y, r);\n        this.reduce(r);\n    }\n});\n\nfunction invDig(a) {\n    if (a.t < 1)\n        return 0;\n    var x = a[0];\n    if ((x & 1) === 0)\n        return 0;\n    var y = x & 3;\n    y = (y * (2 - (x & 0xf) * y)) & 0xf;\n    y = (y * (2 - (x & 0xff) * y)) & 0xff;\n    y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff;\n    y = (y * (2 - x * y % DV)) % DV;\n    return (y > 0) ? DV - y : -y;\n}\n\n// Montgomery reduction\nvar Montgomery = function (m) {\n    this.m = m;\n    this.mp = invDig(m);\n    this.mpl = this.mp & 0x7fff;\n    this.mph = this.mp >> 15;\n    this.um = (1 << (DB - 15)) - 1;\n    this.mt2 = 2 * m.t;\n};\n\nextend(Montgomery, {\n    // xR mod m\n    convert: function (x) {\n        var r = nbi(x.t);\n        dshlTo(abs(x), this.m.t, r);\n        divRemTo(r, this.m, null, r);\n        if (x.s < 0 && compare(r, ZERO) > 0)\n            subTo(this.m, r, r);\n        return r;\n    },\n    // x/R mod m\n    revert: function (x) {\n        var r = nbi(x.t);\n        copyTo(x, r);\n        this.reduce(r);\n        return r;\n    },\n    // x = x/R mod m (HAC 14.32)\n    reduce: function (x) {\n        while (x.t <= this.mt2)\n            x[x.t++] = 0;\n        for (var i = 0; i < this.m.t; ++i) {\n            var j = x[i] & 0x7fff;\n            var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & DM;\n            j = i + this.m.t;\n            x[j] += am(this.m, 0, u0, x, i, 0, this.m.t);\n            while (x[j] >= DV) {\n                x[j] -= DV;\n                x[++j]++;\n            }\n        }\n        clamp(x);\n        dshrTo(x, this.m.t, x);\n        if (compare(x, this.m) >= 0)\n            subTo(x, this.m, x);\n    },\n    // r = \"x^2/R mod m\"; x != r\n    sqrTo: function (x, r) {\n        sqrTo(x, r);\n        this.reduce(r);\n    },\n    // r = \"xy/R mod m\"; x,y != r\n    mulTo: function (x, y, r) {\n        mulTo(x, y, r);\n        this.reduce(r);\n    }\n});\n\nfunction dAddOffset(x, n, w) {\n    if (n === 0)\n        return;\n    while (x.t <= w)\n        x[x.t++] = 0;\n    x[w] += n;\n    while (x[w] >= DV) {\n        x[w] -= DV;\n        if (++w >= x.t)\n            x[x.t++] = 0;\n        ++x[w];\n    }\n}\n\nfunction mulLowerTo(x, a, n, r) {\n    var i = Math.min(x.t + a.t, n);\n    r.s = 0; // assumes a,x >= 0\n    r.t = i;\n    while (i > 0)\n        r[--i] = 0;\n    var j;\n    for (j = r.t - x.t; i < j; ++i)\n        r[i + x.t] = am(x, 0, a[i], r, i, 0, x.t);\n    for (j = Math.min(a.t, n); i < j; ++i)\n        am(x, 0, a[i], r, i, 0, n - i);\n    return clamp(r);\n}\n\nfunction mulUpperTo(x, a, n, r) {\n    --n;\n    var i = r.t = x.t + a.t - n;\n    r.s = 0; // assumes a,x >= 0\n    while (--i >= 0)\n        r[i] = 0;\n    for (i = Math.max(n - x.t, 0); i < a.t; ++i)\n        r[x.t + i - n] = am(x, n - i, a[i], r, 0, 0, x.t + i - n);\n    clamp(r);\n    return dshrTo(r, 1, r);\n}\n\n// Barrett modular reduction\nfunction Barrett(m) {\n    // setup Barrett\n    this.r2 = nbi(2 * m.t);\n    this.q3 = nbi(2 * m.t);\n    dshlTo(ONE, 2 * m.t, this.r2);\n    this.mu = div(this.r2, m);\n    this.m = m;\n}\n\nextend(Barrett, {\n    convert: function (x) {\n        if (x.s < 0 || x.t > 2 * this.m.t)\n            return mod(x, this.m);\n        else if (compare(x, this.m) < 0)\n            return x;\n        else {\n            var r = nbi(x.t);\n            copyTo(x, r);\n            this.reduce(r);\n            return r;\n        }\n    },\n    revert: function (x) {\n        return x;\n    },\n    // x = x mod m (HAC 14.42)\n    reduce: function (x) {\n        dshrTo(x, this.m.t - 1, this.r2);\n        if (x.t > this.m.t + 1) {\n            x.t = this.m.t + 1;\n            clamp(x);\n        }\n        mulUpperTo(this.mu, this.r2, this.m.t + 1, this.q3);\n        mulLowerTo(this.m, this.q3, this.m.t + 1, this.r2);\n        while (compare(x, this.r2) < 0)\n            dAddOffset(x, 1, this.m.t + 1);\n        subTo(x, this.r2, x);\n        while (compare(x, this.m) >= 0)\n            subTo(x, this.m, x);\n    },\n    // r = x^2 mod m; x != r\n    sqrTo: function (x, r) {\n        sqrTo(x, r);\n        this.reduce(r);\n    },\n    // r = x*y mod m; x,y != r\n    mulTo: function (x, y, r) {\n        mulTo(x, y, r);\n        this.reduce(r);\n    }\n\n});\n\n// x^e % m (HAC 14.85)\nfunction expMod(x, e, m) {\n    var i = bitLength(e), k, r = nbv(1), z;\n    if (i <= 0)\n        return r;\n    else if (i < 18)\n        k = 1;\n    else if (i < 48)\n        k = 3;\n    else if (i < 144)\n        k = 4;\n    else if (i < 768)\n        k = 5;\n    else\n        k = 6;\n    if (i < 8)\n        z = new Classic(m);\n    else if (isEven(m))\n        z = new Barrett(m);\n    else\n        z = new Montgomery(m);\n\n    // precomputation\n    var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1;\n    g[1] = z.convert(x);\n    if (k > 1) {\n        var g2 = nbi(m.t * 2);\n        z.sqrTo(g[1], g2);\n        while (n <= km) {\n            g[n] = nbi(m.t * 2);\n            z.mulTo(g2, g[n - 2], g[n]);\n            n += 2;\n        }\n    }\n\n    var j = e.t - 1, w, is1 = true, r2 = nbi(m.t * 2), t;\n    i = nbits(e[j]) - 1;\n    while (j >= 0) {\n        if (i >= k1)\n            w = (e[j] >> (i - k1)) & km;\n        else {\n            w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i);\n            if (j > 0)\n                w |= e[j - 1] >> (DB + i - k1);\n        }\n\n        n = k;\n        while ((w & 1) == 0) {\n            w >>= 1;\n            --n;\n        }\n        if ((i -= n) < 0) {\n            i += DB;\n            --j;\n        }\n        if (is1) {\t// ret == 1, don't bother squaring or multiplying it\n            copyTo(g[w], r);\n            is1 = false;\n        }\n        else {\n            while (n > 1) {\n                z.sqrTo(r, r2);\n                z.sqrTo(r2, r);\n                n -= 2;\n            }\n            if (n > 0)\n                z.sqrTo(r, r2);\n            else {\n                t = r;\n                r = r2;\n                r2 = t;\n            }\n            z.mulTo(r2, g[w], r);\n        }\n        while (j >= 0 && (e[j] & (1 << i)) == 0) {\n            z.sqrTo(r, r2);\n            t = r;\n            r = r2;\n            r2 = t;\n            if (--i < 0) {\n                i = DB - 1;\n                --j;\n            }\n        }\n    }\n    return z.revert(r);\n} // </editor-fold>\n\n/*\n    * EC Field Elements, Points, Curves\n    * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/ec.js\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\n// EC Field Elemets\nfunction newFE(a, x) {\n    a.r.reduce(x);\n    x.q = a.q;\n    x.r = a.r;\n    return x;\n}\n\nfunction copyFE(a, x) {\n    x.q = a.q;\n    x.r = a.r;\n    return x;\n}\n\nfunction negFE(a) {\n    return copyFE(a, sub(a.q, a));\n}\n\nfunction addFE(a, b) {\n    var r = add(a, b);\n    if (compare(r, a.q) > 0)\n        subTo(r, a.q, r);\n    return copyFE(a, r);\n}\n\nfunction subFE(a, b) {\n    var r = sub(a, b);\n    if (r.s < 0)\n        addTo(a.q, r, r);\n    return copyFE(a, r);\n}\n\nfunction mulFE(a, b) {\n    return newFE(a, mul(a, b));\n}\n\nfunction sqrFE(a) {\n    return newFE(a, sqr(a));\n}\n\nfunction shlFE(a, i) {\n    return newFE(a, shl(a, i));\n}\n\nfunction invFE(a) {\n    return copyFE(a, invMod(a, a.q));\n}\n\n// EC Points\nfunction newEC(curve, x, y, z) {\n    return {\n        curve: curve,\n        x: x,\n        y: y,\n        z: z || newFE(curve, ONE)\n    };\n}\n\nfunction getX(point) {\n    if (!point.zinv)\n        point.zinv = invFE(point.z);\n    return mulFE(point.x, point.zinv);\n}\n\nfunction getY(point) {\n    if (!point.zinv)\n        point.zinv = invFE(point.z);\n    return mulFE(point.y, point.zinv);\n}\n\nfunction isInfinity(a) {\n    if ((!a.x) && (!a.y))\n        return true;\n    return isZero(a.z) && !isZero(a.y);\n}\n\nfunction getInfinity(a) {\n    return a.curve.infinity;\n}\n\nfunction equalsEC(a, b) {\n    if (a === b)\n        return true;\n    if (isInfinity(a))\n        return isInfinity(b);\n    if (isInfinity(b))\n        return isInfinity(a);\n    var u, v;\n    // u = Y2 * Z1 - Y1 * Z2\n    u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z));\n    if (!isZero(u))\n        return false;\n    // v = X2 * Z1 - X1 * Z2\n    v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z));\n    return isZero(v);\n}\n\nfunction negEC(a) {\n    return newEC(a.curve, a.x, negFE(a.y), a.z);\n}\n\nfunction addEC(a, b) {\n    if (isInfinity(a))\n        return b;\n    if (isInfinity(b))\n        return a;\n\n    // u = Y2 * Z1 - Y1 * Z2\n    var u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z));\n    // v = X2 * Z1 - X1 * Z2\n    var v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z));\n\n    if (isZero(v)) {\n        if (isZero(u)) {\n            return twiceEC(a); // a == b, so double\n        }\n        return getInfinity(a); // a = -b, so infinity\n    }\n\n    var x1 = a.x;\n    var y1 = a.y;\n\n    var v2 = sqrFE(v);\n    var v3 = mulFE(v2, v);\n    var x1v2 = mulFE(x1, v2);\n    var zu2 = mulFE(sqrFE(u), a.z);\n\n    // x3 = v * (z2 * (z1 * u^2 - 2 * x1 * v^2) - v^3)\n    var x3 = mulFE(subFE(mulFE(subFE(zu2, shlFE(x1v2, 1)), b.z), v3), v);\n    // y3 = z2 * (3 * x1 * u * v^2 - y1 * v^3 - z1 * u^3) + u * v^3\n    var y3 = addFE(mulFE(subFE(subFE(mulFE(mulFE(x1v2, THREE), u), mulFE(y1, v3)), mulFE(zu2, u)), b.z), mulFE(u, v3));\n    // z3 = v^3 * z1 * z2\n    var z3 = mulFE(mulFE(v3, a.z), b.z);\n\n    return newEC(a.curve, x3, y3, z3);\n}\n\nfunction twiceEC(b) {\n    if (isInfinity(b))\n        return b;\n    if (sig(b.y) === 0)\n        return getInfinity(b);\n\n    var x1 = b.x;\n    var y1 = b.y;\n\n    var y1z1 = mulFE(y1, b.z);\n    var y1sqz1 = mulFE(y1z1, y1);\n    var a = b.curve.a;\n\n    // w = 3 * x1^2 + a * z1^2\n    var w = mulFE(sqrFE(x1), THREE);\n    if (!isZero(a)) {\n        w = addFE(w, mulFE(sqrFE(b.z), a));\n    }\n\n    // x3 = 2 * y1 * z1 * (w^2 - 8 * x1 * y1^2 * z1)\n    var x3 = mulFE(shlFE(subFE(sqrFE(w), mulFE(shlFE(x1, 3), y1sqz1)), 1), y1z1);\n    // y3 = 4 * y1^2 * z1 * (3 * w * x1 - 2 * y1^2 * z1) - w^3\n    var y3 = subFE(mulFE(shlFE(subFE(mulFE(mulFE(w, THREE), x1), shlFE(y1sqz1, 1)), 2), y1sqz1), mulFE(sqrFE(w), w));\n    // z3 = 8 * (y1 * z1)^3\n    var z3 = shlFE(mulFE(sqrFE(y1z1), y1z1), 3);\n\n    return newEC(b.curve, x3, y3, z3);\n}\n\n// Simple NAF (Non-Adjacent Form) multiplication algorithm\nfunction mulEC(a, k) {\n    if (isInfinity(a))\n        return a;\n    if (sig(k) === 0)\n        return getInfinity(a);\n\n    var e = k;\n    var h = mul(e, THREE);\n\n    var neg = negEC(a);\n    var R = a;\n\n    var i;\n    for (i = bitLength(h) - 2; i > 0; --i) {\n        R = twiceEC(R);\n\n        var hBit = testBit(h, i);\n        var eBit = testBit(e, i);\n\n        if (hBit !== eBit) {\n            R = addEC(R, hBit ? a : neg);\n        }\n    }\n\n    return R;\n}\n\nfunction mul2AndAddEC(a, k) {\n    var nbits = bitLength(k);\n    var R = a,\n            Q = getInfinity(a);\n\n    for (var i = 0; i < nbits - 1; i++) {\n        if (testBit(k, i) === 1)\n            Q = addEC(Q, R);\n\n        R = twiceEC(R);\n    }\n\n    if (testBit(k, nbits - 1) === 1)\n        Q = addEC(Q, R);\n\n    return Q;\n}\n\n// Compute a*j + x*k (simultaneous multiplication)\nfunction mulTwoEC(a, j, x, k) {\n    var i;\n    if (bitLength(j) > bitLength(k))\n        i = bitLength(j) - 1;\n    else\n        i = bitLength(k) - 1;\n\n    var R = getInfinity(a);\n    var both = addEC(a, x);\n    while (i >= 0) {\n        R = twiceEC(R);\n        if (testBit(j, i)) {\n            if (testBit(k, i)) {\n                R = addEC(R, both);\n            }\n            else {\n                R = addEC(R, a);\n            }\n        }\n        else {\n            if (testBit(k, i)) {\n                R = addEC(R, x);\n            }\n        }\n        --i;\n    }\n\n    return R;\n}\n\n// EC Curve\nfunction newCurve(q, a, b) {\n    var curve = {};\n    curve.q = q;\n    curve.r = new Barrett(q);\n    curve.a = newFE(curve, a);\n    curve.b = newFE(curve, b);\n    curve.infinity = newEC(curve);\n    return curve;\n} // </editor-fold>\n\n/*\n    * Converion tools (hex, binary)\n    *\n    */ // <editor-fold defaultstate=\"collapsed\">\n\nfunction atobi(d) {\n    var k = 8;\n    var a = new Uint8Array(d);\n    var r = nbi(a.length * 8 / DB);\n    r.t = 0;\n    r.s = 0;\n    var sh = 0;\n    for (var i = 0, n = a.length; i < n; i++) {\n        var x = a[i];\n        if (sh === 0)\n            r[r.t++] = x;\n        else if (sh + k > DB) {\n            r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh;\n            r[r.t++] = (x >> (DB - sh));\n        }\n        else\n            r[r.t - 1] |= x << sh;\n        sh += k;\n        if (sh >= DB)\n            sh -= DB;\n    }\n    return clamp(r);\n}\n\nfunction bitoa(s, bitLength) {\n    var k = 8;\n    var km = (1 << k) - 1, d, m = false, r = [], i = s.t;\n    var p = DB - (i * DB) % k;\n    if (i-- > 0) {\n        if (p < DB && (d = s[i] >> p) > 0) {\n            m = true;\n            r.push(d);\n        }\n        while (i >= 0) {\n            if (p < k) {\n                d = (s[i] & ((1 << p) - 1)) << (k - p);\n                d |= s[--i] >> (p += DB - k);\n            }\n            else {\n                d = (s[i] >> (p -= k)) & km;\n                if (p <= 0) {\n                    p += DB;\n                    --i;\n                }\n            }\n            if (d > 0)\n                m = true;\n            if (m)\n                r.push(d);\n        }\n    }\n    var r8 = new Uint8Array(bitLength ? bitLength / 8 : r.length);\n    if (m)\n        r8.set(r.reverse());\n    return r8.buffer;\n}\n\n\nfunction htobi(s) {\n    if (typeof s === 'number' || s instanceof Number)\n        return nbv(s);\n    s = s.replace(/[^\\-A-Fa-f0-9]/g, '');\n    if (!s)\n        s = '0';\n    var k = 4;\n    var r = nbi(s.length / 7);\n    var i = s.length, mi = false, sh = 0;\n    while (--i >= 0) {\n        var c = s.charAt(i);\n        if (c === '-') {\n            mi = true;\n            continue;\n        }\n        var x = parseInt(s.charAt(i), 16);\n        mi = false;\n        if (sh === 0)\n            r[r.t++] = x;\n        else if (sh + k > DB) {\n            r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh;\n            r[r.t++] = (x >> (DB - sh));\n        }\n        else\n            r[r.t - 1] |= x << sh;\n        sh += k;\n        if (sh >= DB)\n            sh -= DB;\n    }\n    if (mi)\n        subTo(ZERO, r, r);\n    return clamp(r);\n}\n\nfunction bitoh(x) {\n    if (x.s < 0)\n        return \"-\" + bitoh(negTo(x, nbi(x.t)));\n    var k = 4;\n    var km = (1 << k) - 1, d, m = false, r = \"\", i = x.t;\n    var p = DB - (i * DB) % k;\n    if (i-- > 0) {\n        if (p < DB && (d = x[i] >> p) > 0) {\n            m = true;\n            r = d.toString(16);\n        }\n        while (i >= 0) {\n            if (p < k) {\n                d = (x[i] & ((1 << p) - 1)) << (k - p);\n                d |= x[--i] >> (p += DB - k);\n            }\n            else {\n                d = (x[i] >> (p -= k)) & km;\n                if (p <= 0) {\n                    p += DB;\n                    --i;\n                }\n            }\n            if (d > 0)\n                m = true;\n            if (m)\n                r += d.toString(16);\n        }\n    }\n    return \"0x\" + (m ? r : \"0\");\n}\n\n// biginteger to big-endian integer bytearray\nfunction bitoi(s) {\n    var i = s.t, r = [];\n    r[0] = s.s;\n    var p = DB - (i * DB) % 8, d, k = 0;\n    if (i-- > 0) {\n        if (p < DB && (d = s[i] >> p) !== (s.s & DM) >> p)\n            r[k++] = d | (s.s << (DB - p));\n        while (i >= 0) {\n            if (p < 8) {\n                d = (s[i] & ((1 << p) - 1)) << (8 - p);\n                d |= s[--i] >> (p += DB - 8);\n            }\n            else {\n                d = (s[i] >> (p -= 8)) & 0xff;\n                if (p <= 0) {\n                    p += DB;\n                    --i;\n                }\n            }\n            if ((d & 0x80) !== 0)\n                d |= -256;\n            if (k === 0 && (s.s & 0x80) !== (d & 0x80))\n                ++k;\n            if (k > 0 || d !== s.s)\n                r[k++] = d;\n        }\n    }\n    return new Uint8Array(r).buffer;\n}\n\n// big-endian integer bytearray to biginteger\nfunction itobi(d) {\n    var k = 8, s = new Uint8Array(d),\n            r = nbi(s.length / 7);\n    r.t = 0;\n    r.s = 0;\n    var i = s.length, sh = 0;\n    while (--i >= 0) {\n        var x = s[i] & 0xff;\n        if (sh === 0)\n            r[r.t++] = x;\n        else if (sh + k > DB) {\n            r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh;\n            r[r.t++] = (x >> (DB - sh));\n        }\n        else\n            r[r.t - 1] |= x << sh;\n        sh += k;\n        if (sh >= DB)\n            sh -= DB;\n    }\n    if ((s[0] & 0x80) !== 0) {\n        r.s = -1;\n        if (sh > 0)\n            r[r.t - 1] |= ((1 << (DB - sh)) - 1) << sh;\n    }\n    return clamp(r);\n}\n\n\n// Swap bytes in buffer\nfunction swap(s) {\n    var src = new Uint8Array(s),\n            dst = new Uint8Array(src.length);\n    for (var i = 0, n = src.length; i < n; i++)\n        dst[n - i - 1] = src[i];\n    return dst.buffer;\n}\n\n// Calculate hash of data\nfunction hash(d) {\n    if (this.hash)\n        d = this.hash.digest(d);\n    // Swap hash for SignalCom\n    if (this.procreator === 'SC' ||\n            (this.procreator === 'VN' && this.hash.version === 2012))\n        d = swap(d);\n    return d;\n}\n\n// Check buffer\nfunction buffer(d) {\n    if (d instanceof CryptoOperationData)\n        return d;\n    else if (d && d?.buffer instanceof CryptoOperationData)\n        return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ?\n                d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer;\n    else\n        throw new DataError('CryptoOperationData or CryptoOperationDataView required');\n}\n\n// Check double buffer\nfunction to2(d) {\n    var b = buffer(d);\n    if (b.byteLength % 2 > 0)\n        throw new DataError('Buffer length must be even');\n    var n = b.byteLength / 2;\n    return [atobi(new Uint8Array(b, 0, n)), atobi(new Uint8Array(b, n, n))];\n}\n\nfunction from2(x, y, bitLength) {\n    var a = bitoa(x, bitLength),\n            b = bitoa(y, bitLength),\n            d = new Uint8Array(a.byteLength + b.byteLength);\n    d.set(new Uint8Array(a));\n    d.set(new Uint8Array(b), a.byteLength);\n    return d.buffer;\n}\n\nfunction getSeed(length) {\n    GostRandom = GostRandom || root.GostRandom;\n    var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto;\n    if (randomSource.getRandomValues) {\n        var d = new Uint8Array(Math.ceil(length / 8));\n        randomSource.getRandomValues(d);\n        return d;\n    } else\n        throw new NotSupportedError('Random generator not found');\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * The sign method returns sign data generated with the supplied privateKey.<br>\n *\n * @memberOf GostSign\n * @method sign\n * @instance\n * @param {(CryptoOperationData|TypedArray)} privateKey Private key\n * @param {(CryptoOperationData|TypedArray)} data Data\n * @returns {CryptoOperationData} Signature\n */\nfunction sign(privateKey, data) // <editor-fold defaultstate=\"collapsed\">\n{\n\n    // Stage 1\n    var b = buffer(data);\n    var alpha = atobi(hash.call(this, b));\n\n    var q = this.q;\n    var x = mod(atobi(buffer(privateKey)), q);\n\n    // Stage 2\n    var e = mod(alpha, q);\n    if (isZero(e))\n        e = ONE;\n\n    var s = ZERO;\n    while (isZero(s)) {\n        var r = ZERO;\n        while (isZero(r)) {\n\n            // Stage 3\n            var k = mod(atobi(this.ukm ||\n                    getSeed(this.bitLength)), q); // pseudo random 0 < k < q\n            // Stage 4\n            if (this.curve) {\n                // Gost R 34.10-2001 || Gost R 34.10-2012\n                var P = this.P;\n                var C = mulEC(P, k);\n                r = mod(getX(C), q);\n            } else {\n                // Gost R 34.10-94\n                var p = this.p, a = this.a;\n                r = mod(expMod(a, k, p), q);\n            }\n        }\n        // Stage 5\n        s = mod(add(mul(r, x), mul(k, e)), q);\n    }\n    // Stage 6\n    // console.log('s', bitoh(s));\n    // console.log('r', bitoh(r));\n    var zetta;\n    // Integer structure for SignalCom algorithm\n    if (this.procreator === 'SC') {\n        zetta = {\n            r: bitoh(r),\n            s: bitoh(s)\n        };\n    } else {\n        zetta = from2(r, s, this.bitLength);\n        // Swap bytes for CryptoPro algorithm\n        if (this.procreator === 'CP' || this.procreator === 'VN')\n            zetta = swap(zetta);\n    }\n    return zetta;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * The verify method returns signature verification for the supplied publicKey.<br>\n *\n * @memberOf GostSign\n * @method sign\n * @instance\n * @param {(CryptoOperationData|TypedArray)} publicKey Public key\n * @param {(CryptoOperationData|TypedArray)} signature Signature\n * @param {(CryptoOperationData|TypedArray)} data Data\n * @returns {boolean} Signature verified = true\n */\nfunction verify(publicKey, signature, data) // <editor-fold defaultstate=\"collapsed\">\n{\n\n    // Stage 1\n    var q = this.q;\n    var r, s;\n    // Ready int for SignalCom algorithm\n    if (this.procreator === 'SC') {\n        r = htobi(signature.r);\n        s = htobi(signature.s);\n    } else {\n        if (this.procreator === 'CP' || this.procreator === 'VN')\n            signature = swap(signature);\n        var zetta = to2(signature);\n        // Swap bytes for CryptoPro algorithm\n        s = zetta[1]; //  first 32 octets contain the big-endian representation of s\n        r = zetta[0]; //  and second 32 octets contain the big-endian representation of r\n    }\n    if (compare(r, q) >= 0 || compare(s, q) >= 0)\n        return false;\n    // Stage 2\n    var b = buffer(data);\n    var alpha = atobi(hash.call(this, b));\n    // Stage 3\n    var e = mod(alpha, q);\n    if (isZero(e) === 0)\n        e = ONE;\n    // Stage 4\n    var v = invMod(e, q);\n    // Stage 5\n    var z1 = mod(mul(s, v), q);\n    var z2 = sub(q, mod(mul(r, v), q));\n    // Stage 6\n    if (this.curve) {\n        // Gost R 34.10-2001 || Gost R 34.10-2012\n        var k2 = to2(publicKey),\n                curve = this.curve,\n                P = this.P,\n                x = newFE(curve, k2[0]), // first 32 octets contain the little-endian representation of x\n                y = newFE(curve, k2[1]), // and second 32 octets contain the little-endian representation of y.\n                Q = new newEC(curve, x, y); // This corresponds to the binary representation of (<y>256||<x>256)\n        var C = mulTwoEC(P, z1, Q, z2);\n        var R = mod(getX(C), q);\n    } else {\n        // Gost R 34.10-94\n        var p = this.p, a = this.a;\n        var y = atobi(publicKey);\n        var R = mod(mod(mul(expMod(a, z1, p), expMod(y, z2, p)), p), q);\n    }\n    // Stage 7\n    return (compare(R, r) === 0);\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * The generateKey method returns a new generated key pair using the specified\n * AlgorithmIdentifier.\n *\n * @memberOf GostSign\n * @method generateKey\n * @instance\n * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey\n */\nfunction generateKey() // <editor-fold defaultstate=\"collapsed\">\n{\n    var curve = this.curve;\n    if (curve) {\n\n        var Q = curve.infinity;\n        while (isInfinity(Q)) {\n\n            // Generate random private key\n            var d = ZERO;\n            if (this.ukm) {\n                d = atobi(this.ukm);\n            } else {\n                while (isZero(d))\n                    d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q\n            }\n\n            // Calculate public key\n            Q = mulEC(this.P, d);\n            var x = getX(Q), y = getY(Q);\n            // console.log('d', bitoh(d));\n            // console.log('x', bitoh(x));\n            // console.log('y', bitoh(y));\n        }\n\n        // Return result\n        return {\n            privateKey: bitoa(d, this.bitLength),\n            publicKey: from2(x, y, this.bitLength) // This corresponds to the binary representation of (<y>256||<x>256)\n        };\n\n    } else\n        throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported');\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10 mode MASK<br><br>\n *\n * The generateMaskKey method returns a new generated key mask using for wrapping.\n *\n * @memberOf GostSign\n * @method generateMaskKey\n * @instance\n * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey\n */\nfunction generateMaskKey() // <editor-fold defaultstate=\"collapsed\">\n{\n    var curve = this.curve;\n    if (curve) {\n        // Generate random private key\n        var d = ZERO;\n        while (isZero(d))\n            d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q\n\n        // Return result\n        return bitoa(d, this.bitLength);\n    } else\n        throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported');\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * Unwrap private key from private key and ukm (mask)\n *\n * @memberOf GostSign\n * @method unwrap\n * @instance\n * @param {(CryptoOperationData|TypedArray)} baseKey Unwrapping key\n * @param {(CryptoOperationData|TypedArray)} data Wrapped key\n * @returns {Object} CryptoOperationData unwrapped privateKey\n */\nfunction unwrapKey(baseKey, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    var curve = this.curve;\n    if (curve) {\n        var q = this.q;\n        var x = mod(atobi(buffer(data)), q);\n        var y = mod(atobi(buffer(baseKey)), q);\n        var z = this.procreator === 'VN' ? mod(mul(x, y), q) : mod(mul(x, invMod(y, q)), q);\n        return bitoa(z);\n    } else\n        throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported');\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * Wrap private key with private key and ukm (mask)\n *\n * @memberOf GostSign\n * @method unwrap\n * @instance\n * @param {(CryptoOperationData|TypedArray)} baseKey Wrapping key\n * @param {(CryptoOperationData|TypedArray)} data Key\n * @returns {Object} CryptoOperationData unwrapped privateKey\n */\nfunction wrapKey(baseKey, data) // <editor-fold defaultstate=\"collapsed\">\n{\n    var curve = this.curve;\n    if (curve) {\n        var q = this.q;\n        var x = mod(atobi(buffer(data)), q);\n        var y = mod(atobi(buffer(baseKey)), q);\n        var z = this.procreator === 'VN' ? mod(mul(x, invMod(y, q)), q) : mod(mul(x, y), q);\n        return bitoa(z);\n    } else\n        throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported');\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * @memberOf GostSign\n * @method derive\n * @instance\n * @private\n * @param {CryptoOperationData} baseKey Key for deriviation\n * @returns {CryptoOperationData}\n */\nfunction derive(baseKey) // <editor-fold defaultstate=\"collapsed\">\n{\n\n    var k, ukm = atobi(this.ukm);\n    var q = this.q;\n    var x = mod(atobi(buffer(baseKey)), q);\n\n    if (this.curve) {\n        // 1) Let K(x,y,UKM) = ((UKM*x)(mod q)) . (y.P) (512 bit), where\n        // x - sender’s private key (256 bit)\n        // x.P - sender’s public key (512 bit)\n        // y - recipient’s private key (256 bit)\n        // y.P - recipient’s public key (512 bit)\n        // UKM - non-zero integer, produced as in step 2 p. 6.1 [GOSTR341001]\n        // P - base point on the elliptic curve (two 256-bit coordinates)\n        // UKM*x - x multiplied by UKM as integers\n        // x.P - a multiple point\n        var K = mulEC(this.peer_Q, mod(mul(ukm, x), q));\n        k = from2(getX(K), getY(K), // This corresponds to the binary representation of (<y>256||<x>256)\n                this.bitLength);\n    } else {\n        // 1) Let K(x,y) = a^(x*y) (mod p), where\n        // x - sender’s private key, a^x - sender’s public key\n        // y - recipient’s private key, a^y - recipient’s public key\n        // a, p - parameters\n        var p = this.p, a = this.a;\n        k = bitoa(expMod(this.peer_y, x, p));\n    }\n    // 2) Calculate a 256-bit hash of K(x,y,UKM):\n    // KEK(x,y,UKM) = gostSign (K(x,y,UKM)\n    return hash.call(this, k);\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * The deriveBits method returns length bits on baseKey.\n *\n * @memberOf GostSign\n * @method deriveBits\n * @instance\n * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation\n * @param {number} length output bit-length\n * @returns {CryptoOperationData} result\n */\nfunction deriveBits(baseKey, length) // <editor-fold defaultstate=\"collapsed\">\n{\n    if (length < 8 || length > this.bitLength || length % 8 > 0)\n        throw new DataError('Length must be no more than ' + this.bitLength + ' bits and multiple of 8');\n    var n = length / 8,\n            b = derive.call(this, baseKey),\n            r = new Uint8Array(n);\n\n    r.set(new Uint8Array(b, 0, n));\n    return r.buffer;\n} // </editor-fold>\n\n/**\n * Algorithm name GOST R 34.10<br><br>\n *\n * The deriveKey method returns 256 bit Key encryption key on baseKey.\n *\n * This algorithm creates a key encryption key (KEK) using 64 bit UKM,\n * the sender’s private key, and the recipient’s public key (or the\n * reverse of the latter pair\n *\n * @memberOf GostSign\n * @method deriveKey\n * @instance\n * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation\n * @returns {CryptoOperationData} result\n */\nfunction deriveKey(baseKey) // <editor-fold defaultstate=\"collapsed\">\n{\n    var b = derive.call(this, baseKey),\n            r = new Uint8Array(32);\n\n    r.set(new Uint8Array(b, 0, 32));\n    return r.buffer;\n} // </editor-fold>\n\n\n/**\n * Gost R 34.10 universal object<br><br>\n *\n * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}<br><br>\n *\n * Normalized algorithm identifier common parameters:\n *\n *  <ul>\n *      <li><b>name</b> Algorithm name 'GOST R 34.10'</li>\n *      <li><b>version</b> Algorithm version\n *          <ul>\n *              <li><b>1994</b> - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash</li>\n *              <li><b>2001</b> - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash</li>\n *              <li><b>2012</b> - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode</li>\n *          </ul>\n *      </li>\n *      <li><b>length</b> Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm\n *          <ul>\n *              <li><b>GOST R 34.10-256</b> - 256 bits digest, default mode</li>\n *              <li><b>GOST R 34.10-512</b> - 512 bits digest only for GOST R 34.11-2012 hash</li>\n *          </ul>\n *      </li>\n *      <li><b>mode</b> Algorithm mode\n *          <ul>\n *              <li><b>SIGN</b> Digital signature mode (default)</li>\n *              <li><b>DH</b> Diffie-Hellman key generation and key agreement mode</li>\n *          </ul>\n *      </li>\n *      <li><b>sBox</b> Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001</li>\n *  </ul>\n *\n * Supported algorithms, modes and parameters:\n *\n *  <ul>\n *      <li>Sign/Verify mode (SIGN)</li>\n *      <li>DeriveKey/DeriveBits mode (DH)\n *          <ul>\n *              <li>{@link CryptoOperationData} <b>ukm</b> User key material. Default - random generated value</li>\n *              <li>{@link CryptoOperationData} <b>public</b> The peer's EC public key data</li>\n *          </ul>\n *      </li>\n *      <li>GenerateKey mode (SIGN and DH) version = 1994\n *          <ul>\n *              <li><b>namedParam</b> Paramset for key generation algorithm. If specified no additianal parameters required</li>\n *          </ul>\n *          Additional parameters, if namedParam not specified\n *          <ul>\n *              <li><b>modulusLength</b> Bit length of p (512 or 1024 bits). Default = 1024</li>\n *              <li><b>p</b> {@link CryptoOperationData} Modulus, prime number, 2^(t-1)<p<2^t</li>\n *              <li><b>q</b> {@link CryptoOperationData} Order of cyclic group, prime number, 2^254<q<2^256, q is a factor of p-1</li>\n *              <li><b>a</b> {@link CryptoOperationData} Generator, integer, 1<a<p-1, at that aq (mod p) = 1</li>\n *          </ul>\n *      </li>\n *      <li>GenerateKey mode (SIGN and DH) version = 2001 or 2012\n *          <ul>\n *              <li><b>namedCurve</b> Paramset for key generation algorithm. If specified no additianal parameters required</li>\n *          </ul>\n *          Additional EC parameters, if namedCurve not specified\n *          <ul>\n *              <li><b>p</b> {@link CryptoOperationData} Prime number - elliptic curve modulus</li>\n *              <li><b>a</b> {@link CryptoOperationData} Coefficients a of the elliptic curve E</li>\n *              <li><b>b</b> {@link CryptoOperationData} Coefficients b of the elliptic curve E</li>\n *              <li><b>q</b> {@link CryptoOperationData} Prime number - order of cyclic group</li>\n *              <li><b>x</b> {@link CryptoOperationData} Base point p x-coordinate</li>\n *              <li><b>y</b> {@link CryptoOperationData} Base point p y-coordinate</li>\n *          </ul>\n *      </li>\n *  </ul>\n *\n * @class GostSign\n * @param {AlgorithmIndentifier} algorithm\n */\nfunction GostSign(algorithm) // <editor-fold defaultstate=\"collapsed\">\n{\n    algorithm = algorithm || {};\n    this.name = (algorithm.name || 'GOST R 34.10') + '-' +\n            ((algorithm.version || 2012) % 100) + '-' + (algorithm.length || 256) +\n            (((algorithm.mode || 'SIGN') !== 'SIGN') ? '-' + algorithm.mode : '') +\n            (typeof algorithm.namedParam === 'string' ? '/' + algorithm.namedParam : '') +\n            (typeof algorithm.namedCurve === 'string' ? '/' + algorithm.namedCurve : '') +\n            (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : '');\n\n    var version = algorithm.version || 2012;\n\n    // Functions\n    switch (algorithm.mode || 'SIGN') {\n        case 'SIGN':\n            this.sign = sign;\n            this.verify = verify;\n            this.generateKey = generateKey;\n            break;\n        case 'DH':\n            this.deriveBits = deriveBits;\n            this.deriveKey = deriveKey;\n            this.generateKey = generateKey;\n            break;\n        case 'MASK':\n            this.wrapKey = wrapKey;\n            this.unwrapKey = unwrapKey;\n            this.generateKey = generateMaskKey;\n            break;\n    }\n\n    // Define parameters\n    if (version === 1994) {\n        // Named or parameters algorithm\n        var param = algorithm.param;\n        if (!param)\n            param = GostParams[this.namedParam = (algorithm.namedParam || 'S-A').toUpperCase()];\n        this.modulusLength = algorithm.modulusLength || param.modulusLength || 1024;\n        this.p = htobi(param.p);\n        this.q = htobi(param.q);\n        this.a = htobi(param.a);\n        // Public key for derive\n        if (algorithm['public'])\n            this.peer_y = atobi(algorithm['public']);\n    } else {\n        // Named or parameters algorithm\n        var param = algorithm.curve;\n        if (!param)\n            param = ECGostParams[this.namedCurve = (algorithm.namedCurve || 'S-256-A').toUpperCase()];\n        var curve = this.curve = newCurve(htobi(param.p), htobi(param.a), htobi(param.b));\n        this.P = newEC(curve,\n                newFE(curve, htobi(param.x)),\n                newFE(curve, htobi(param.y)));\n        this.q = htobi(param.q);\n        // Public key for derive\n        if (algorithm['public']) {\n            var k2 = to2(algorithm['public']);\n            this.peer_Q = new newEC(this.curve, // This corresponds to the binary representation of (<y>256||<x>256)\n                    newFE(this.curve, k2[0]), // first 32 octets contain the little-endian representation of x\n                    newFE(this.curve, k2[1])); // and second 32 octets contain the little-endian representation of y.\n        }\n    }\n\n    // Check bit length\n    var hashLen, keyLen;\n    if (this.curve) {\n        keyLen = algorithm.length || bitLength(this.q);\n        if (keyLen > 508 && keyLen <= 512)\n            keyLen = 512;\n        else if (keyLen > 254 && keyLen <= 256)\n            keyLen = 256;\n        else\n            throw new NotSupportedError('Support keys only 256 or 512 bits length');\n        hashLen = keyLen;\n    } else {\n        keyLen = algorithm.modulusLength || bitLength(this.p);\n        if (keyLen > 1016 && keyLen <= 1024)\n            keyLen = 1024;\n        else if (keyLen > 508 && keyLen <= 512)\n            keyLen = 512;\n        else\n            throw new NotSupportedError('Support keys only 512 or 1024 bits length');\n        hashLen = 256;\n    }\n    this.bitLength = hashLen;\n    this.keyLength = keyLen;\n\n    // Algorithm proceator for result conversion\n    this.procreator = algorithm.procreator;\n\n    // Hash function definition\n    var hash = algorithm.hash;\n    if (hash) {\n        if (typeof hash === 'string' || hash instanceof String)\n            hash = {name: hash};\n        if (algorithm.version === 1994 || algorithm.version === 2001) {\n            hash.version = 1994;\n            hash.length = 256;\n            hash.sBox = algorithm.sBox || hash.sBox;\n        } else {\n            hash.version = 2012;\n            hash.length = hashLen;\n        }\n        hash.procreator = hash.procreator || algorithm.procreator;\n\n        if (!GostDigest)\n            GostDigest = root.GostDigest;\n        if (!GostDigest)\n            throw new NotSupportedError('Object GostDigest not found');\n\n        this.hash = new GostDigest(hash);\n    }\n\n    // Pregenerated seed for key exchange algorithms\n    if (algorithm.ukm) // Now don't check size\n        this.ukm = algorithm.ukm;\n\n} // </editor-fold>\n\nexport default GostSign;\n"
  },
  {
    "path": "src/core/vendor/remove-exif.mjs",
    "content": "/* piexifjs\nThe MIT License (MIT)\nCopyright (c) 2014, 2015 hMatoba(https://github.com/hMatoba)\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n\nimport Utils from \"../Utils.mjs\";\n\n// Param jpeg should be a binaryArray\nexport function removeEXIF(jpeg) {\n  // Convert binaryArray to char string\n  jpeg = Utils.byteArrayToChars(jpeg);\n  if (jpeg.slice(0, 2) != \"\\xff\\xd8\") {\n    throw (\"Given data is not jpeg.\");\n  }\n\n  var segments = splitIntoSegments(jpeg);\n  if (segments[1].slice(0, 2) == \"\\xff\\xe1\" &&\n    segments[1].slice(4, 10) == \"Exif\\x00\\x00\") {\n    segments = [segments[0]].concat(segments.slice(2));\n  } else if (segments[2].slice(0, 2) == \"\\xff\\xe1\" &&\n    segments[2].slice(4, 10) == \"Exif\\x00\\x00\") {\n    segments = segments.slice(0, 2).concat(segments.slice(3));\n  } else {\n    throw (\"Exif not found.\");\n  }\n\n  var new_data = segments.join(\"\");\n\n  // Convert back to binaryArray\n  new_data = Utils.strToCharcode(new_data);\n\n  return new_data;\n};\n\nfunction splitIntoSegments(data) {\n  if (data.slice(0, 2) != \"\\xff\\xd8\") {\n    throw (\"Given data isn't JPEG.\");\n  }\n\n  var head = 2;\n  var segments = [\"\\xff\\xd8\"];\n  while (true) {\n    if (data.slice(head, head + 2) == \"\\xff\\xda\") {\n      segments.push(data.slice(head));\n      break;\n    } else {\n      var length = unpack(\">H\", data.slice(head + 2, head + 4))[0];\n      var endPoint = head + length + 2;\n      segments.push(data.slice(head, endPoint));\n      head = endPoint;\n    }\n\n    if (head >= data.length) {\n      throw (\"Wrong JPEG data.\");\n    }\n  }\n  return segments;\n}\n\nfunction unpack(mark, str) {\n  if (typeof(str) != \"string\") {\n    throw (\"'unpack' error. Got invalid type argument.\");\n  }\n  var l = 0;\n  for (var markPointer = 1; markPointer < mark.length; markPointer++) {\n    if (mark[markPointer].toLowerCase() == \"b\") {\n      l += 1;\n    } else if (mark[markPointer].toLowerCase() == \"h\") {\n      l += 2;\n    } else if (mark[markPointer].toLowerCase() == \"l\") {\n      l += 4;\n    } else {\n      throw (\"'unpack' error. Got invalid mark.\");\n    }\n  }\n\n  if (l != str.length) {\n    throw (\"'unpack' error. Mismatch between symbol and string length. \" + l + \":\" + str.length);\n  }\n\n  var littleEndian;\n  if (mark[0] == \"<\") {\n    littleEndian = true;\n  } else if (mark[0] == \">\") {\n    littleEndian = false;\n  } else {\n    throw (\"'unpack' error.\");\n  }\n  var unpacked = [];\n  var strPointer = 0;\n  var p = 1;\n  var val = null;\n  var c = null;\n  var length = null;\n  var sliced = \"\";\n\n  while (c = mark[p]) {\n    if (c.toLowerCase() == \"b\") {\n      length = 1;\n      sliced = str.slice(strPointer, strPointer + length);\n      val = sliced.charCodeAt(0);\n      if ((c == \"b\") && (val >= 0x80)) {\n        val -= 0x100;\n      }\n    } else if (c == \"H\") {\n      length = 2;\n      sliced = str.slice(strPointer, strPointer + length);\n      if (littleEndian) {\n        sliced = sliced.split(\"\").reverse().join(\"\");\n      }\n      val = sliced.charCodeAt(0) * 0x100 +\n        sliced.charCodeAt(1);\n    } else if (c.toLowerCase() == \"l\") {\n      length = 4;\n      sliced = str.slice(strPointer, strPointer + length);\n      if (littleEndian) {\n        sliced = sliced.split(\"\").reverse().join(\"\");\n      }\n      val = sliced.charCodeAt(0) * 0x1000000 +\n        sliced.charCodeAt(1) * 0x10000 +\n        sliced.charCodeAt(2) * 0x100 +\n        sliced.charCodeAt(3);\n      if ((c == \"l\") && (val >= 0x80000000)) {\n        val -= 0x100000000;\n      }\n    } else {\n      throw (\"'unpack' error. \" + c);\n    }\n\n    unpacked.push(val);\n    strPointer += length;\n    p += 1;\n  }\n\n  return unpacked;\n}\n"
  },
  {
    "path": "src/node/File.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport { detectFileType } from \"../core/lib/FileType.mjs\";\n\n\n/**\n * FileShim\n *\n * Create a class that behaves like the File object in the Browser so that\n * operations that use the File object still work.\n *\n * File doesn't write to disk, but it would be easy to do so with e.gfs.writeFile.\n */\nclass File {\n\n    /**\n     * Constructor\n     *\n     * https://w3c.github.io/FileAPI/#file-constructor\n     *\n     * @param {String|Array|ArrayBuffer|Buffer|[File]} bits - file content\n     * @param {String} name (optional) - file name\n     * @param {Object} stats (optional) - file stats e.g. lastModified\n     */\n    constructor(data, name=\"\", stats={}) {\n\n        if (!Array.isArray(data)) {\n            data = [data];\n        }\n\n        const buffers = data.map((d) => {\n            if (d instanceof File) {\n                return Buffer.from(d.data);\n            }\n\n            if (d instanceof ArrayBuffer) {\n                return Buffer.from(d);\n            }\n\n            return Buffer.from(d);\n        });\n        const totalLength = buffers.reduce((p, c) => p + c.length, 0);\n        this.data = Buffer.concat(buffers, totalLength);\n\n        this.name = name;\n        this.lastModified = stats.lastModified || Date.now();\n\n        const types = detectFileType(this.data);\n        if (types.length) {\n            this.type = types[0].mime;\n        } else {\n            this.type = \"application/unknown\";\n        }\n    }\n\n    /**\n     * size property\n     */\n    get size() {\n        return this.data.length;\n    }\n\n    /**\n     * Return lastModified as Date\n     */\n    get lastModifiedDate() {\n        return new Date(this.lastModified);\n    }\n\n}\n\nexport default File;\n"
  },
  {
    "path": "src/node/NodeDish.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport util from \"util\";\nimport Dish from \"../core/Dish.mjs\";\n\n/**\n * Subclass of Dish for use in the Node.js environment.\n *\n * Adds some helper functions and improves coercion for Node.js logging.\n */\nclass NodeDish extends Dish {\n\n    /**\n    * Create a Dish\n    * @param {any} inputOrDish - The dish input\n    * @param {String|Number} - The dish type, as enum or string\n    */\n    constructor(inputOrDish=null, type=null) {\n\n        // Allow `fs` file input:\n        // Any node fs Buffers transformed to array buffer\n        // Use Array.from as Uint8Array doesnt pass instanceof Array test\n        if (Buffer.isBuffer(inputOrDish)) {\n            inputOrDish = Array.from(inputOrDish);\n            type = Dish.BYTE_ARRAY;\n        }\n        super(inputOrDish, type);\n    }\n\n    /**\n     * Apply the inputted operation to the dish.\n     *\n     * @param {WrappedOperation} operation the operation to perform\n     * @param {*} args - any arguments for the operation\n     * @returns {Dish} a new dish with the result of the operation.\n     */\n    apply(operation, args=null) {\n        return operation(this, args);\n    }\n\n    /**\n     * alias for get\n     * @param args see get args\n     */\n    to(...args) {\n        return this.get(...args);\n    }\n\n    /**\n     * Avoid coercion to a String primitive.\n     */\n    toString() {\n        return this.presentAs(Dish.typeEnum(\"string\"));\n    }\n\n    /**\n     * What we want to log to the console.\n     */\n    [util.inspect.custom](depth, options) {\n        return this.presentAs(Dish.typeEnum(\"string\"));\n    }\n\n    /**\n     * Backwards compatibility for node v6\n     * Log only the value to the console in node.\n     */\n    inspect() {\n        return this.presentAs(Dish.typeEnum(\"string\"));\n    }\n\n    /**\n     * Avoid coercion to a Number primitive.\n     */\n    valueOf() {\n        return this.presentAs(Dish.typeEnum(\"number\"));\n    }\n\n}\n\nexport default NodeDish;\n"
  },
  {
    "path": "src/node/NodeRecipe.mjs",
    "content": "/**\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport {operations} from \"./index.mjs\";\nimport { sanitise } from \"./apiUtils.mjs\";\n\n/**\n * Similar to core/Recipe, Recipe controls a list of operations and\n * the NodeDish the operate on. However, this Recipe is for the node\n * environment.\n */\nclass NodeRecipe {\n\n    /**\n     * Recipe constructor\n     * @param recipeConfig\n     */\n    constructor(recipeConfig) {\n        this._parseConfig(recipeConfig);\n    }\n\n\n    /**\n     * Validate an ingredient & coerce to operation if necessary.\n     * @param {String | Function | Object} ing\n     * @returns {Function || Object} The operation, or an object with the\n     *  operation and its arguments\n     * @throws {TypeError} If it cannot find the operation in chef's list of operations.\n     */\n    _validateIngredient(ing) {\n        // CASE operation name given. Find operation and validate\n        if (typeof ing === \"string\") {\n            const op = operations.find((op) => {\n                return sanitise(op.opName) === sanitise(ing);\n            });\n            if (op) {\n                // Need to validate against case 2\n                return this._validateIngredient(op);\n            } else {\n                throw new TypeError(`Couldn't find an operation with name '${ing}'.`);\n            }\n        // CASE operation given. Check its a chef operation and check its not flowcontrol\n        } else if (typeof ing === \"function\") {\n            if (ing.flowControl) {\n                throw new TypeError(`flowControl operations like ${ing.opName} are not currently allowed in recipes for chef.bake in the Node API`);\n            }\n\n            if (operations.includes(ing)) {\n                return ing;\n            } else {\n                throw new TypeError(\"Inputted function not a Chef operation.\");\n            }\n        // CASE: op, maybe with configuration\n        } else if (ing.op) {\n            const sanitisedOp = this._validateIngredient(ing.op);\n            if (ing.args) {\n                return {op: sanitisedOp, args: ing.args};\n            }\n            return sanitisedOp;\n        } else {\n            throw new TypeError(\"Recipe can only contain function names or functions\");\n        }\n    }\n\n\n    /**\n     * Parse an opList from a recipeConfig and assign it to the recipe's opList.\n     * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig\n     */\n    _parseConfig(recipeConfig) {\n        if (!recipeConfig) {\n            this.opList = [];\n            return;\n        }\n\n        if (!Array.isArray(recipeConfig)) {\n            recipeConfig = [recipeConfig];\n        }\n\n        this.opList = recipeConfig.map((ing) => this._validateIngredient(ing));\n    }\n\n    /**\n     * Run the dish through each operation, one at a time.\n     * @param {NodeDish} dish\n     * @returns {NodeDish}\n     */\n    execute(dish) {\n        return this.opList.reduce((prev, curr) => {\n            // CASE where opList item is op and args\n            if (Object.prototype.hasOwnProperty.call(curr, \"op\") &&\n                Object.prototype.hasOwnProperty.call(curr, \"args\")) {\n                return curr.op(prev, curr.args);\n            }\n            // CASE opList item is just op.\n            return curr(prev);\n        }, dish);\n    }\n}\n\nexport default NodeRecipe;\n"
  },
  {
    "path": "src/node/api.mjs",
    "content": "/**\n * Wrap operations for consumption in Node.\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/* eslint no-console: [\"off\"] */\n\nimport NodeDish from \"./NodeDish.mjs\";\nimport NodeRecipe from \"./NodeRecipe.mjs\";\nimport OperationConfig from \"../core/config/OperationConfig.json\" assert {type: \"json\"};\nimport { sanitise, removeSubheadingsFromArray, sentenceToCamelCase } from \"./apiUtils.mjs\";\nimport ExcludedOperationError from \"../core/errors/ExcludedOperationError.mjs\";\n\n\n/**\n * transformArgs\n *\n * Take the default args array and update with any user-defined\n * operation arguments. Allows user to define arguments in object style,\n * with accommodating name matching. Using named args in the API is more\n * clear to the user.\n *\n * Argument name matching is case and space insensitive\n * @private\n * @param {Object[]} originalArgs - the operation-s args list\n * @param {Object} newArgs - any inputted args\n */\nfunction transformArgs(opArgsList, newArgs) {\n\n    if (newArgs && Array.isArray(newArgs)) {\n        return newArgs;\n    }\n\n    // Filter out arg values that are list subheadings - they are surrounded in [].\n    // See Strings op for example.\n    const opArgs = Object.assign([], opArgsList).map((a) => {\n        if (Array.isArray(a.value)) {\n            a.value = removeSubheadingsFromArray(a.value);\n        }\n        return a;\n    });\n\n    // Reconcile object style arg info to fit operation args shape.\n    if (newArgs) {\n        Object.keys(newArgs).map((key) => {\n            const index = opArgs.findIndex((arg) => {\n                return arg.name.toLowerCase().replace(/ /g, \"\") ===\n                    key.toLowerCase().replace(/ /g, \"\");\n            });\n\n            if (index > -1) {\n                const argument = opArgs[index];\n                if (argument.type === \"toggleString\") {\n                    if (typeof newArgs[key] === \"string\") {\n                        argument.string = newArgs[key];\n                    } else {\n                        argument.string = newArgs[key].string;\n                        argument.option = newArgs[key].option;\n                    }\n                } else if (argument.type === \"editableOption\") {\n                    // takes key: \"option\", key: {name, val: \"string\"}, key: {name, val: [...]}\n                    argument.value = typeof newArgs[key] === \"string\" ? newArgs[key]: newArgs[key].value;\n                } else {\n                    argument.value = newArgs[key];\n                }\n            }\n        });\n    }\n\n    // Sanitise args\n    return opArgs.map((arg) => {\n        if (arg.type === \"option\") {\n            // pick default option if not already chosen\n            return typeof arg.value === \"string\" ? arg.value : arg.value[arg.defaultIndex ?? 0];\n        }\n\n        if (arg.type === \"editableOption\") {\n            return typeof arg.value === \"string\" ? arg.value : arg.value[arg.defaultIndex ?? 0].value;\n        }\n\n        if (arg.type === \"toggleString\") {\n            // ensure string and option exist when user hasn't defined\n            arg.string = arg.string || \"\";\n            arg.option = arg.option || arg.toggleValues[0];\n            return arg;\n        }\n\n        return arg.value;\n    });\n}\n\n\n/**\n * Ensure an input is a SyncDish object.\n * @param input\n */\nfunction ensureIsDish(input) {\n    if (!input) {\n        return new NodeDish();\n    }\n\n    if (input instanceof NodeDish) {\n        return input;\n    } else {\n        return new NodeDish(input);\n    }\n}\n\n\n/**\n * prepareOp: transform args, make input the right type.\n * Also convert any Buffers to ArrayBuffers.\n * @param opInstance - instance of the operation\n * @param input - operation input\n * @param args - operation args\n */\nfunction prepareOp(opInstance, input, args) {\n    const dish = ensureIsDish(input);\n    // Transform object-style args to original args array\n    const transformedArgs = transformArgs(opInstance.args, args);\n    const transformedInput = dish.get(opInstance.inputType);\n    return {transformedInput, transformedArgs};\n}\n\n\n/**\n * createArgInfo\n *\n * Create an object of options for each argument in the given operation\n *\n * Argument names are converted to camel case for consistency.\n *\n * @param {Operation} op - the operation to extract args from\n * @returns {{}} - arrays of options for args.\n*/\nfunction createArgInfo(op) {\n    const result = {};\n    op.args.forEach((a) => {\n        if (a.type === \"option\" || a.type === \"editableOption\") {\n            result[sentenceToCamelCase(a.name)] = {\n                type: a.type,\n                options: removeSubheadingsFromArray(a.value)\n            };\n        } else if (a.type === \"toggleString\") {\n            result[sentenceToCamelCase(a.name)] = {\n                type: a.type,\n                value: a.value,\n                toggleValues: removeSubheadingsFromArray(a.toggleValues),\n            };\n        } else {\n            result[sentenceToCamelCase(a.name)] = {\n                type: a.type,\n                value: a.value,\n            };\n        }\n    });\n\n    return result;\n}\n\n\n/**\n * Wrap an operation to be consumed by node API.\n * Checks to see if run function is async or not.\n * new Operation().run() becomes operation()\n * Perform type conversion on input\n * @private\n * @param {Operation} Operation\n * @returns {Function} The operation's run function, wrapped in\n * some type conversion logic\n */\nexport function _wrap(OpClass) {\n\n    // Check to see if class's run function is async.\n    const opInstance = new OpClass();\n    const isAsync = opInstance.run.constructor.name === \"AsyncFunction\";\n    const isFlowControl = opInstance.flowControl;\n\n    let wrapped;\n\n    // If async, wrap must be async.\n    if (isAsync) {\n        /**\n         * Async wrapped operation run function\n         * @param {*} input\n         * @param {Object | String[]} args - either in Object or normal args array\n         * @returns {Promise<SyncDish>} operation's output, on a Dish.\n         * @throws {OperationError} if the operation throws one.\n         */\n        wrapped = async (input, args=null) => {\n            const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);\n\n            // SPECIAL CASE for Magic. Other flowControl operations will\n            // not work because the opList is not passed in.\n            if (isFlowControl) {\n                opInstance.ingValues = transformedArgs;\n\n                const state = {\n                    progress: 0,\n                    dish: ensureIsDish(transformedInput),\n                    opList: [opInstance],\n                };\n\n                const updatedState = await opInstance.run(state);\n\n                return new NodeDish({\n                    value: updatedState.dish.value,\n                    type: opInstance.outputType,\n                });\n            }\n\n            const result = await opInstance.run(transformedInput, transformedArgs);\n\n            return new NodeDish({\n                value: result,\n                type: opInstance.outputType,\n            });\n        };\n    } else {\n        /**\n         * wrapped operation run function\n         * @param {*} input\n         * @param {Object | String[]} args - either in Object or normal args array\n         * @returns {SyncDish} operation's output, on a Dish.\n         * @throws {OperationError} if the operation throws one.\n         */\n        wrapped = (input, args=null) => {\n            const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);\n            const result = opInstance.run(transformedInput, transformedArgs);\n            return new NodeDish({\n                value: result,\n                type: opInstance.outputType,\n            });\n        };\n    }\n\n    // used in chef.help\n    wrapped.opName = OpClass.name;\n    wrapped.args = createArgInfo(opInstance);\n    // Used in NodeRecipe to check for flowControl ops\n    wrapped.flowControl = isFlowControl;\n\n    return wrapped;\n}\n\n\n/**\n * help: Give information about operations matching the given search term,\n * or inputted operation.\n *\n * @param {String || wrapped operation} input - the name of the operation to get help for.\n * Case and whitespace are ignored in search.\n * @returns {Object[]} Config of matching operations.\n */\nexport function help(input) {\n    let searchTerm = false;\n    if (typeof input === \"string\") {\n        searchTerm = input;\n    } else if (typeof input === \"function\") {\n        searchTerm = input.opName;\n    }\n\n    if (!searchTerm) {\n        return null;\n    }\n\n    let exactMatchExists = false;\n\n    // Look for matches in operation name and description, listing name\n    // matches first.\n    const matches = Object.keys(OperationConfig)\n        // hydrate operation: swap op name for op config object (with name)\n        .map((m) => {\n            const hydrated = OperationConfig[m];\n            hydrated.name = m;\n\n            // flag up an exact name match. Only first exact match counts.\n            if (!exactMatchExists) {\n                exactMatchExists = sanitise(hydrated.name) === sanitise(searchTerm);\n            }\n            // Return hydrated along with what type of match it was\n            return {\n                hydrated,\n                nameExactMatch: sanitise(hydrated.name) === sanitise(searchTerm),\n                nameMatch: sanitise(hydrated.name).includes(sanitise(searchTerm)),\n                descMatch: sanitise(hydrated.description).includes(sanitise(searchTerm))\n            };\n        })\n        // Filter out non-matches. If exact match exists, filter out all others.\n        .filter((result) => {\n            if (exactMatchExists) {\n                return !!result.nameExactMatch;\n            }\n            return result.nameMatch || result.descMatch;\n        })\n        // sort results with name match first\n        .sort((a, b) => {\n            const aInt = a.nameMatch ? 1 : 0;\n            const bInt = b.nameMatch ? 1  : 0;\n            return bInt - aInt;\n        })\n        // extract just the hydrated config\n        .map(result => result.hydrated);\n\n    if (matches && matches.length) {\n        // console.log(`${matches.length} result${matches.length > 1 ? \"s\" : \"\"} found.`);\n        return matches;\n    }\n\n    // console.log(\"No results found.\");\n    return null;\n}\n\n\n/**\n * bake\n *\n * @param {*} input - some input for a recipe.\n * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig -\n * An operation, operation name, or an array of either.\n * @returns {NodeDish} of the result\n * @throws {TypeError} if invalid recipe given.\n */\nexport function bake(input, recipeConfig) {\n    const recipe =  new NodeRecipe(recipeConfig);\n    const dish = ensureIsDish(input);\n    return recipe.execute(dish);\n}\n\n\n/**\n * explainExcludedFunction\n *\n * Explain that the given operation is not included in the Node.js version.\n * @param {String} name - name of operation\n */\nexport function _explainExcludedFunction(name) {\n    /**\n     * Throw new error type with useful message.\n    */\n    const func = () => {\n        throw new ExcludedOperationError(`Sorry, the ${name} operation is not available in the Node.js version of CyberChef.`);\n    };\n    // Add opName prop so NodeRecipe can handle it, just like wrap does.\n    func.opName = name;\n    return func;\n}\n"
  },
  {
    "path": "src/node/apiUtils.mjs",
    "content": "/**\n * Utility functions for the node environment\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n\n/**\n * someName => Somename\n *\n * @param {String} str = string to be altered\n * @returns {String}\n */\nconst capitalise = function capitalise(str) {\n    // Don't edit names that start with 2+ caps\n    if (/^[A-Z0-9]{2,}/g.test(str)) {\n        return str;\n    }\n    // reserved. Don't change for now.\n    if (str === \"Return\") {\n        return str;\n    }\n\n    return `${str.charAt(0).toUpperCase()}${str.substr(1).toLowerCase()}`;\n};\n\n\n/**\n * SomeName => someName\n * @param {String} name - string to be altered\n * @returns {String} decapitalised\n */\nexport function decapitalise(str) {\n    // Don't decapitalise str that start with 2+ caps\n    if (/^[A-Z0-9]{2,}/g.test(str)) {\n        return str;\n    }\n    // reserved. Don't change for now.\n    if (str === \"Return\") {\n        return str;\n    }\n\n    return `${str.charAt(0).toLowerCase()}${str.substr(1)}`;\n}\n\n\n/**\n * Remove strings surrounded with [] from the given array.\n*/\nexport function removeSubheadingsFromArray(array) {\n    if (Array.isArray(array)) {\n        return array.filter((i) => {\n            if (typeof i === \"string\") {\n                return !i.match(/^\\[[\\s\\S]*\\]$/);\n            }\n            return true;\n        });\n    }\n}\n\n\n/**\n * Remove spaces, make lower case.\n * @param str\n */\nexport function sanitise(str) {\n    return str.replace(/[/\\s.-]/g, \"\").toLowerCase();\n}\n\n\n/**\n * something like this => somethingLikeThis\n * ABC a sentence => ABCASentence\n*/\nexport function sentenceToCamelCase(str) {\n    return str.split(\" \")\n        .map((s, index) => {\n            if (index === 0) {\n                return decapitalise(s);\n            }\n            return capitalise(s);\n        })\n        .reduce((prev, curr) => `${prev}${curr}`, \"\");\n}\n"
  },
  {
    "path": "src/node/config/excludedOperations.mjs",
    "content": "/**\n * Operations to exclude from the Node API\n *\n * @author d98762656 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nexport default  [\n    // This functionality can be done more easily using JavaScript\n    \"Fork\",\n    \"Merge\",\n    \"Jump\",\n    \"ConditionalJump\",\n    \"Label\",\n    \"Comment\",\n\n    // esprima doesn't work in .mjs\n    \"JavaScriptBeautify\",\n    \"JavaScriptMinify\",\n    \"JavaScriptParser\",\n\n    // Irrelevant in Node console\n    \"SyntaxHighlighter\",\n];\n"
  },
  {
    "path": "src/node/config/scripts/generateNodeIndex.mjs",
    "content": "/**\n * This script generates the exports functionality for the node API.\n *\n * it exports chef as default, but all the wrapped operations as\n * other top level exports.\n *\n * @author d98762656 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n/* eslint no-console: 0 */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport * as operations from \"../../../core/operations/index.mjs\";\nimport { decapitalise } from \"../../apiUtils.mjs\";\nimport excludedOperations from \"../excludedOperations.mjs\";\n\nconst includedOperations = Object.keys(operations).filter((op => excludedOperations.indexOf(op) === -1));\n\nconst dir = path.join(`${process.cwd()}/src/node`);\nif (!fs.existsSync(dir)) {\n    console.log(\"\\nCWD: \" + process.cwd());\n    console.log(\"Error: generateNodeIndex.mjs should be run from the project root\");\n    console.log(\"Example> node --experimental-modules src/node/config/scripts/generateNodeIndex.mjs\");\n    process.exit(1);\n}\n\nlet code = `/**\n* THIS FILE IS AUTOMATICALLY GENERATED BY src/node/config/scripts/generateNodeIndex.mjs\n*\n* @author d98762625 [d98762625@gmail.com]\n* @copyright Crown Copyright 2019\n* @license Apache-2.0\n*/\n\n/* eslint camelcase: 0 */\n\n\nimport NodeDish from \"./NodeDish.mjs\";\nimport { _wrap, help, bake, _explainExcludedFunction } from \"./api.mjs\";\nimport File from \"./File.mjs\";\nimport { OperationError, DishError, ExcludedOperationError } from \"../core/errors/index.mjs\";\nimport {\n    // import as core_ to avoid name clashes after wrap.\n`;\n\nincludedOperations.forEach((op) => {\n    // prepend with core_ to avoid name collision later.\n    code += `    ${op} as core_${op},\\n`;\n});\n\ncode +=`\n} from \"../core/operations/index.mjs\";\n\nglobal.File = File;\n\n/**\n * generateChef\n *\n * Creates decapitalised, wrapped ops in chef object for default export.\n */\nfunction generateChef() {\n    return {\n`;\n\nincludedOperations.forEach((op) => {\n    code += `        \"${decapitalise(op)}\": _wrap(core_${op}),\\n`;\n});\n\nexcludedOperations.forEach((op) => {\n    code += `        \"${decapitalise(op)}\": _explainExcludedFunction(\"${op}\"),\\n`;\n});\n\ncode += `    };\n}\n\nconst chef = generateChef();\n// Add some additional features to chef object.\nchef.help = help;\nchef.Dish = NodeDish;\n\n// Define consts here so we can add to top-level export - wont allow\n// export of chef property.\n`;\n\nObject.keys(operations).forEach((op) => {\n    code += `const ${decapitalise(op)} = chef.${decapitalise(op)};\\n`;\n});\n\ncode +=`\n\n// Define array of all operations to create register for bake.\nconst operations = [\\n`;\n\nObject.keys(operations).forEach((op) => {\n    code += `    ${decapitalise(op)},\\n`;\n});\n\ncode += `];\n\nchef.bake = bake;\nexport default chef;\n\n// Operations as top level exports.\nexport {\n    operations,\n`;\n\nObject.keys(operations).forEach((op) => {\n    code += `    ${decapitalise(op)},\\n`;\n});\n\ncode += \"    NodeDish as Dish,\\n\";\ncode += \"    bake,\\n\";\ncode += \"    help,\\n\";\ncode += \"    OperationError,\\n\";\ncode += \"    ExcludedOperationError,\\n\";\ncode += \"    DishError,\\n\";\ncode += \"};\\n\";\n\n\nfs.writeFileSync(\n    path.join(dir, \"./index.mjs\"),\n    code\n);\n"
  },
  {
    "path": "src/node/repl.mjs",
    "content": "/**\n * Create a REPL server for chef\n *\n *\n * @author d98762656 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport chef from \"./index.mjs\";\nimport repl from \"repl\";\n\n\n/* eslint no-console: [\"off\"] */\n\nconsole.log(`\n   ______      __              ________         ____\n  / ____/_  __/ /_  ___  _____/ ____/ /_  ___  / __/\n / /   / / / / __ \\\\/ _ \\\\/ ___/ /   / __ \\\\/ _ \\\\/ /_  \n/ /___/ /_/ / /_/ /  __/ /  / /___/ / / /  __/ __/  \n\\\\____/\\\\__, /_.___/\\\\___/_/   \\\\____/_/ /_/\\\\___/_/     \n     /____/                                         \n     \n`);\nconst replServer = repl.start({\n    prompt: \"chef > \",\n});\n\nglobal.File = chef.File;\n\nObject.keys(chef).forEach((key) => {\n    if (key !== \"operations\") {\n        replServer.context[key] = chef[key];\n    }\n});\n\n"
  },
  {
    "path": "src/node/wrapper.js",
    "content": "/**\n * Export the main ESM module as CommonJS\n *\n *\n * @author d98762656 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nmodule.exports = (async () => await import(\"./index.mjs\"))();\nmodule.exports.File = (async () => await import(\"./File.mjs\"))();\n"
  },
  {
    "path": "src/web/App.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils, { debounce } from \"../core/Utils.mjs\";\nimport {fromBase64} from \"../core/lib/Base64.mjs\";\nimport Manager from \"./Manager.mjs\";\nimport HTMLCategory from \"./HTMLCategory.mjs\";\nimport HTMLOperation from \"./HTMLOperation.mjs\";\nimport Split from \"split.js\";\nimport moment from \"moment-timezone\";\nimport cptable from \"codepage\";\n\n\n/**\n * HTML view for CyberChef responsible for building the web page and dealing with all user\n * interactions.\n */\nclass App {\n\n    /**\n     * App constructor.\n     *\n     * @param {CatConf[]} categories - The list of categories and operations to be populated.\n     * @param {Object.<string, OpConf>} operations - The list of operation configuration objects.\n     * @param {String[]} defaultFavourites - A list of default favourite operations.\n     * @param {Object} options - Default setting for app options.\n     */\n    constructor(categories, operations, defaultFavourites, defaultOptions) {\n        this.categories    = categories;\n        this.operations    = operations;\n        this.dfavourites   = defaultFavourites;\n        this.doptions      = defaultOptions;\n        this.options       = Object.assign({}, defaultOptions);\n\n        this.manager       = new Manager(this);\n\n        this.baking        = false;\n        this.autoBake_     = false;\n        this.progress      = 0;\n        this.ingId         = 0;\n\n        this.appLoaded     = false;\n        this.workerLoaded  = false;\n        this.waitersLoaded = false;\n\n        this.snackbars     = [];\n    }\n\n\n    /**\n     * This function sets up the stage and creates listeners for all events.\n     *\n     * @fires Manager#appstart\n     */\n    setup() {\n        document.dispatchEvent(this.manager.appstart);\n\n        this.initialiseSplitter();\n        this.loadLocalStorage();\n        this.manager.options.applyPreferredColorScheme();\n        this.populateOperationsList();\n        this.manager.setup();\n        this.manager.output.saveBombe();\n        this.adjustComponentSizes();\n        this.setCompileMessage();\n        this.uriParams = this.getURIParams();\n\n        log.debug(\"App loaded\");\n        this.appLoaded = true;\n        this.loaded();\n    }\n\n\n    /**\n     * Fires once all setup activities have completed.\n     *\n     * @fires Manager#apploaded\n     */\n    loaded() {\n        // Check that both the app and the worker have loaded successfully, and that\n        // we haven't already loaded before attempting to remove the loading screen.\n        if (!this.workerLoaded || !this.appLoaded || !this.waitersLoaded ||\n            !document.getElementById(\"loader-wrapper\")) return;\n\n        // Load state from URI\n        this.loadURIParams(this.uriParams);\n\n        // Trigger CSS animations to remove preloader\n        document.body.classList.add(\"loaded\");\n\n        // Wait for animations to complete then remove the preloader and loaded style\n        // so that the animations for existing elements don't play again.\n        setTimeout(function() {\n            document.getElementById(\"loader-wrapper\").remove();\n            document.body.classList.remove(\"loaded\");\n\n            // Bake initial input\n            this.manager.input.bakeAll();\n        }.bind(this), 1000);\n\n        // Clear the loading message interval\n        clearInterval(window.loadingMsgsInt);\n\n        // Remove the loading error handler\n        window.removeEventListener(\"error\", window.loadingErrorHandler);\n\n        document.dispatchEvent(this.manager.apploaded);\n\n        this.manager.input.calcMaxTabs();\n        this.manager.output.calcMaxTabs();\n    }\n\n\n    /**\n     * An error handler for displaying the error to the user.\n     *\n     * @param {Error} err\n     * @param {boolean} [logToConsole=false]\n     */\n    handleError(err, logToConsole) {\n        if (logToConsole) log.error(err);\n        const msg = err.displayStr || err.toString();\n        this.alert(Utils.escapeHtml(msg), this.options.errorTimeout, !this.options.showErrors);\n    }\n\n\n    /**\n     * Asks the ChefWorker to bake the current input using the current recipe.\n     *\n     * @param {boolean} [step] - Set to true if we should only execute one operation instead of the\n     *   whole recipe.\n     */\n    bake(step=false) {\n        if (this.baking) return;\n\n        // Reset attemptHighlight flag\n        this.options.attemptHighlight = true;\n\n        // Remove all current indicators\n        this.manager.recipe.updateBreakpointIndicator(false);\n\n        this.manager.worker.bake(\n            this.getRecipeConfig(), // The configuration of the recipe\n            this.options,           // Options set by the user\n            this.progress,          // The current position in the recipe\n            step                    // Whether or not to take one step or execute the whole recipe\n        );\n    }\n\n\n    /**\n     * Runs Auto Bake if it is set.\n     */\n    autoBake() {\n        if (this.baking) {\n            this.manager.worker.cancelBakeForAutoBake();\n            this.baking = false;\n        }\n\n        if (this.autoBake_) {\n            log.debug(\"Auto-baking\");\n            this.manager.worker.bakeInputs({\n                nums: [this.manager.tabs.getActiveTab(\"input\")],\n                step: false\n            });\n        } else {\n            this.manager.controls.showStaleIndicator();\n        }\n    }\n\n\n    /**\n     * Executes the next step of the recipe.\n     */\n    step() {\n        if (this.baking) return;\n\n        // Reset status using cancelBake\n        this.manager.worker.cancelBake(true, false);\n\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        if (activeTab === -1) return;\n\n        let progress = 0;\n        if (this.manager.output.outputs[activeTab].progress !== false) {\n            log.error(this.manager.output.outputs[activeTab]);\n            progress = this.manager.output.outputs[activeTab].progress;\n        }\n\n        this.manager.input.inputWorker.postMessage({\n            action: \"step\",\n            data: {\n                activeTab: activeTab,\n                progress: progress + 1\n            }\n        });\n    }\n\n\n    /**\n     * Runs a silent bake, forcing the browser to load and cache all the relevant JavaScript code needed\n     * to do a real bake.\n     *\n     * The output will not be modified (hence \"silent\" bake). This will only actually execute the recipe\n     * if auto-bake is enabled, otherwise it will just wake up the ChefWorker with an empty recipe.\n     */\n    silentBake() {\n        let recipeConfig = [];\n\n        if (this.autoBake_) {\n            // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled\n            // for a good reason.\n            recipeConfig = this.getRecipeConfig();\n        }\n\n        this.manager.worker.silentBake(recipeConfig);\n    }\n\n\n    /**\n     * Sets the user's input data.\n     *\n     * @param {string} input - The string to set the input to\n     */\n    setInput(input) {\n        // Get the currently active tab.\n        // If there isn't one, assume there are no inputs so use inputNum of 1\n        let inputNum = this.manager.tabs.getActiveTab(\"input\");\n        if (inputNum === -1) inputNum = 1;\n        this.manager.input.updateInputValue(inputNum, input);\n\n        this.manager.input.inputWorker.postMessage({\n            action: \"setInput\",\n            data: {\n                inputNum: inputNum,\n                silent: true\n            }\n        });\n    }\n\n\n    /**\n     * Populates the operations accordion list with the categories and operations specified in the\n     * view constructor.\n     *\n     * @fires Manager#oplistcreate\n     */\n    populateOperationsList() {\n        // Move edit button away before we overwrite it\n        document.body.appendChild(document.getElementById(\"edit-favourites\"));\n\n        let html = \"\";\n        let i;\n\n        for (i = 0; i < this.categories.length; i++) {\n            const catConf = this.categories[i],\n                selected = i === 0,\n                cat = new HTMLCategory(catConf.name, selected);\n\n            for (let j = 0; j < catConf.ops.length; j++) {\n                const opName = catConf.ops[j];\n                if (!(opName in this.operations)) {\n                    log.warn(`${opName} could not be found.`);\n                    continue;\n                }\n\n                const op = new HTMLOperation(opName, this.operations[opName], this, this.manager);\n                cat.addOperation(op);\n            }\n\n            html += cat.toHtml();\n        }\n\n        document.getElementById(\"categories\").innerHTML = html;\n\n        const opLists = document.querySelectorAll(\"#categories .op-list\");\n\n        for (i = 0; i < opLists.length; i++) {\n            opLists[i].dispatchEvent(this.manager.oplistcreate);\n        }\n\n        // Add edit button to first category (Favourites)\n        const favCat = document.querySelector(\"#categories a\");\n        favCat.appendChild(document.getElementById(\"edit-favourites\"));\n        favCat.setAttribute(\"data-help-title\", \"Favourite operations\");\n        favCat.setAttribute(\"data-help\", `<p>This category displays your favourite operations.</p>\n        <ul>\n            <li><b>To add:</b> drag an operation over the Favourites category</li>\n            <li><b>To reorder:</b> Click on the 'Edit favourites' button and drag operations up and down in the list provided</li>\n            <li><b>To remove:</b> Click on the 'Edit favourites' button and hit the delete button next to the operation you want to remove</li>\n        </ul>`);\n    }\n\n\n    /**\n     * Sets up the adjustable splitter to allow the user to resize areas of the page.\n     *\n     * @param {boolean} [minimise=false] - Set this flag if attempting to minimise frames to 0 width\n     */\n    initialiseSplitter(minimise=false) {\n        if (this.columnSplitter) this.columnSplitter.destroy();\n        if (this.ioSplitter) this.ioSplitter.destroy();\n\n        this.columnSplitter = Split([\"#operations\", \"#recipe\", \"#IO\"], {\n            sizes: [20, 30, 50],\n            minSize: minimise ? [0, 0, 0] : [240, 310, 450],\n            gutterSize: 4,\n            expandToMin: true,\n            onDrag: debounce(function() {\n                this.adjustComponentSizes();\n            }, 50, \"dragSplitter\", this, [])\n        });\n\n        this.ioSplitter = Split([\"#input\", \"#output\"], {\n            direction: \"vertical\",\n            gutterSize: 4,\n            minSize: minimise ? [0, 0] : [100, 100]\n        });\n\n        this.adjustComponentSizes();\n    }\n\n\n    /**\n     * Loads the information previously saved to the HTML5 local storage object so that user options\n     * and favourites can be restored.\n     */\n    loadLocalStorage() {\n        // Load options\n        let lOptions;\n        if (this.isLocalStorageAvailable() && localStorage.options !== undefined) {\n            lOptions = JSON.parse(localStorage.options);\n        }\n        this.manager.options.load(lOptions);\n\n        // Load favourites\n        this.loadFavourites();\n    }\n\n\n    /**\n     * Loads the user's favourite operations from the HTML5 local storage object and populates the\n     * Favourites category with them.\n     * If the user currently has no saved favourites, the defaults from the view constructor are used.\n     */\n    loadFavourites() {\n        let favourites;\n\n        if (this.isLocalStorageAvailable()) {\n            favourites = localStorage?.favourites?.length > 2 ?\n                JSON.parse(localStorage.favourites) :\n                this.dfavourites;\n            favourites = this.validFavourites(favourites);\n            this.saveFavourites(favourites);\n        } else {\n            favourites = this.dfavourites;\n        }\n\n        const favCat = this.categories.filter(function(c) {\n            return c.name === \"Favourites\";\n        })[0];\n\n        if (favCat) {\n            favCat.ops = favourites;\n        } else {\n            this.categories.unshift({\n                name: \"Favourites\",\n                ops: favourites\n            });\n        }\n    }\n\n\n    /**\n     * Filters the list of favourite operations that the user had stored and removes any that are no\n     * longer available. The user is notified if this is the case.\n\n     * @param {string[]} favourites - A list of the user's favourite operations\n     * @returns {string[]} A list of the valid favourites\n     */\n    validFavourites(favourites) {\n        const validFavs = [];\n        for (let i = 0; i < favourites.length; i++) {\n            if (favourites[i] in this.operations) {\n                validFavs.push(favourites[i]);\n            } else {\n                this.alert(`The operation \"${Utils.escapeHtml(favourites[i])}\" is no longer available. ` +\n                    \"It has been removed from your favourites.\");\n            }\n        }\n        return validFavs;\n    }\n\n\n    /**\n     * Saves a list of favourite operations to the HTML5 local storage object.\n     *\n     * @param {string[]} favourites - A list of the user's favourite operations\n     */\n    saveFavourites(favourites) {\n        if (!this.isLocalStorageAvailable()) {\n            this.alert(\n                \"Your security settings do not allow access to local storage so your favourites cannot be saved.\",\n                5000\n            );\n            return false;\n        }\n\n        localStorage.setItem(\"favourites\", JSON.stringify(this.validFavourites(favourites)));\n    }\n\n\n    /**\n     * Resets favourite operations back to the default as specified in the view constructor and\n     * refreshes the operation list.\n     */\n    resetFavourites() {\n        this.saveFavourites(this.dfavourites);\n        this.loadFavourites();\n        this.populateOperationsList();\n        this.manager.recipe.initialiseOperationDragNDrop();\n    }\n\n\n    /**\n     * Adds an operation to the user's favourites.\n     *\n     * @param {string} name - The name of the operation\n     */\n    addFavourite(name) {\n        const favourites = JSON.parse(localStorage.favourites);\n\n        if (favourites.indexOf(name) >= 0) {\n            this.alert(`'${name}' is already in your favourites`, 3000);\n            return;\n        }\n\n        favourites.push(name);\n        this.saveFavourites(favourites);\n        this.loadFavourites();\n        this.populateOperationsList();\n        this.manager.recipe.initialiseOperationDragNDrop();\n    }\n\n    /**\n     * Gets the URI params from the window and parses them to extract the actual values.\n     *\n     * @returns {object}\n     */\n    getURIParams() {\n        // Load query string or hash from URI (depending on which is populated)\n        // We prefer getting the hash by splitting the href rather than referencing\n        // location.hash as some browsers (Firefox) automatically URL decode it,\n        // which cause issues.\n        const params = window.location.search ||\n            window.location.href.split(\"#\")[1] ||\n            window.location.hash;\n        const parsedParams = Utils.parseURIParams(params);\n        return parsedParams;\n    }\n\n    /**\n     * Searches the URI parameters for recipe and input parameters.\n     * If recipe is present, replaces the current recipe with the recipe provided in the URI.\n     * If input is present, decodes and sets the input to the one provided in the URI.\n     * If character encodings are present, sets them appropriately.\n     * If theme is present, uses the theme.\n     *\n     * @param {Object} params\n     * @fires Manager#statechange\n     */\n    loadURIParams(params=this.getURIParams()) {\n        this.uriParams = params;\n\n        // Read in recipe from URI params\n        if (this.uriParams.recipe) {\n            try {\n                const recipeConfig = Utils.parseRecipeConfig(this.uriParams.recipe);\n                this.setRecipeConfig(recipeConfig);\n            } catch (err) {}\n        } else if (this.uriParams.op) {\n            // If there's no recipe, look for single operations\n            this.manager.recipe.clearRecipe();\n\n            // Search for nearest match and add it\n            const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false);\n            if (matchedOps.length) {\n                this.manager.recipe.addOperation(matchedOps[0].name);\n            }\n\n            // Populate search with the string\n            const search = document.getElementById(\"search\");\n\n            search.value = this.uriParams.op;\n            search.dispatchEvent(new Event(\"search\"));\n        }\n\n        // Input Character Encoding\n        // Must be set before the input is loaded\n        if (this.uriParams.ienc) {\n            this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10), true, true);\n        }\n\n        // Output Character Encoding\n        if (this.uriParams.oenc) {\n            this.manager.output.chrEncChange(parseInt(this.uriParams.oenc, 10), true);\n        }\n\n        // Input EOL sequence\n        if (this.uriParams.ieol) {\n            this.manager.input.eolChange(this.uriParams.ieol, true);\n        }\n\n        // Output EOL sequence\n        if (this.uriParams.oeol) {\n            this.manager.output.eolChange(this.uriParams.oeol, true);\n        }\n\n        // Read in input data from URI params\n        if (this.uriParams.input) {\n            try {\n                let inputVal;\n                const inputChrEnc = this.manager.input.getChrEnc();\n                const inputData = fromBase64(this.uriParams.input, null, \"byteArray\");\n                if (inputChrEnc > 0) {\n                    inputVal = cptable.utils.decode(inputChrEnc, inputData);\n                } else {\n                    inputVal = Utils.byteArrayToChars(inputData);\n                }\n                this.setInput(inputVal);\n            } catch (err) {}\n        }\n\n        // Read in theme from URI params\n        if (this.uriParams.theme) {\n            this.manager.options.changeTheme(Utils.escapeHtml(this.uriParams.theme));\n        } else {\n            this.manager.options.applyPreferredColorScheme();\n        }\n\n        window.dispatchEvent(this.manager.statechange);\n    }\n\n\n    /**\n     * Returns the next ingredient ID and increments it for next time.\n     *\n     * @returns {number}\n     */\n    nextIngId() {\n        return this.ingId++;\n    }\n\n\n    /**\n     * Gets the current recipe configuration.\n     *\n     * @returns {Object[]}\n     */\n    getRecipeConfig() {\n        return this.manager.recipe.getConfig();\n    }\n\n\n    /**\n     * Given a recipe configuration, sets the recipe to that configuration.\n     *\n     * @fires Manager#statechange\n     * @param {Object[]} recipeConfig - The recipe configuration\n     */\n    setRecipeConfig(recipeConfig) {\n        document.getElementById(\"rec-list\").innerHTML = null;\n\n        for (let i = 0; i < recipeConfig.length; i++) {\n            const item = this.manager.recipe.addOperation(recipeConfig[i].op);\n\n            // Populate arguments\n            log.debug(`Populating arguments for ${recipeConfig[i].op}`);\n            const args = item.querySelectorAll(\".arg\");\n            for (let j = 0; j < args.length; j++) {\n                if (recipeConfig[i].args[j] === undefined) continue;\n                if (args[j].getAttribute(\"type\") === \"checkbox\") {\n                    // checkbox\n                    args[j].checked = recipeConfig[i].args[j];\n                } else if (args[j].classList.contains(\"toggle-string\")) {\n                    // toggleString\n                    args[j].value = recipeConfig[i].args[j].string;\n                    args[j].parentNode.parentNode.querySelector(\"button\").innerHTML =\n                        Utils.escapeHtml(recipeConfig[i].args[j].option);\n                } else {\n                    // all others\n                    args[j].value = recipeConfig[i].args[j];\n                }\n            }\n\n            // Set disabled and breakpoint\n            if (recipeConfig[i].disabled) {\n                item.querySelector(\".disable-icon\").click();\n            }\n            if (recipeConfig[i].breakpoint) {\n                item.querySelector(\".breakpoint\").click();\n            }\n\n            this.manager.recipe.triggerArgEvents(item);\n\n            this.progress = 0;\n        }\n    }\n\n\n    /**\n     * Resets the splitter positions to default.\n     */\n    resetLayout() {\n        this.columnSplitter.setSizes([20, 30, 50]);\n        this.ioSplitter.setSizes([50, 50]);\n        this.adjustComponentSizes();\n    }\n\n    /**\n     * Adjust components to fit their containers.\n     */\n    adjustComponentSizes() {\n        this.manager.recipe.adjustWidth();\n        this.manager.input.calcMaxTabs();\n        this.manager.output.calcMaxTabs();\n        this.manager.controls.calcControlsHeight();\n    }\n\n\n    /**\n     * Sets the compile message.\n     */\n    setCompileMessage() {\n        // Display time since last build and compile message\n        const now = new Date(),\n            msSinceCompile = now.getTime() - window.compileTime,\n            timeSinceCompile = moment.duration(msSinceCompile, \"milliseconds\").humanize();\n\n        // Calculate previous version to compare to\n        const prev = PKG_VERSION.split(\".\").map(n => {\n            return parseInt(n, 10);\n        });\n        if (prev[2] > 0) prev[2]--;\n        else if (prev[1] > 0) prev[1]--;\n        else prev[0]--;\n\n        // const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(\".\")}...v${PKG_VERSION}`;\n\n        let compileInfo = `<a href='https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md'>Last build: ${timeSinceCompile.substring(0, 1).toUpperCase() + timeSinceCompile.substring(1)} ago</a>`;\n\n        if (window.compileMessage !== \"\") {\n            compileInfo += \" - \" + window.compileMessage;\n        }\n\n        const notice = document.getElementById(\"notice\");\n        notice.innerHTML = compileInfo;\n        notice.setAttribute(\"title\", Utils.stripHtmlTags(window.compileMessage));\n        notice.setAttribute(\"data-help-title\", \"Last build\");\n        notice.setAttribute(\"data-help\", \"This live version of CyberChef is updated whenever new commits are added to the master branch of the CyberChef repository. It represents the latest, most up-to-date build of CyberChef.\");\n    }\n\n\n    /**\n     * Determines whether the browser supports Local Storage and if it is accessible.\n     *\n     * @returns {boolean}\n     */\n    isLocalStorageAvailable() {\n        try {\n            if (!localStorage) return false;\n            return true;\n        } catch (err) {\n            // Access to LocalStorage is denied\n            return false;\n        }\n    }\n\n\n    /**\n     * Pops up a message to the user and writes it to the console log.\n     *\n     * @param {string} str - The message to display (HTML supported)\n     * @param {number} [timeout=0] - The number of milliseconds before the alert closes automatically\n     *     0 for never (until the user closes it)\n     * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the\n     *     console\n     *\n     * @example\n     * // Pops up a box with the message \"Error: Something has gone wrong!\" that will need to be\n     * // dismissed by the user.\n     * this.alert(\"Error: Something has gone wrong!\", 0);\n     *\n     * // Pops up a box with the message \"Happy Christmas!\" that will disappear after 5 seconds.\n     * this.alert(\"Happy Christmas!\", 5000);\n     */\n    alert(str, timeout=0, silent=false) {\n        const time = new Date();\n\n        log.info(\"[\" + time.toLocaleString() + \"] \" + str);\n        if (silent) return;\n\n        this.snackbars.push($.snackbar({\n            content: str,\n            timeout: timeout,\n            htmlAllowed: true,\n            onClose: () => {\n                this.snackbars.shift().remove();\n            }\n        }));\n    }\n\n\n    /**\n     * Pops up a box asking the user a question and sending the answer to a specified callback function.\n     *\n     * @param {string} title - The title of the box\n     * @param {string} body - The question (HTML supported)\n     * @param {string} accept - The text of the accept button\n     * @param {string} reject - The text of the reject button\n     * @param {function} callback - A function accepting one boolean argument which handles the\n     *   response e.g. function(answer) {...}\n     * @param {Object} [scope=this] - The object to bind to the callback function\n     *\n     * @example\n     * // Pops up a box asking if the user would like a cookie. Prints the answer to the console.\n     * this.confirm(\"Question\", \"Would you like a cookie?\", \"Yes\", \"No\", function(answer) {console.log(answer);});\n     */\n    confirm(title, body, accept, reject, callback, scope) {\n        scope = scope || this;\n        document.getElementById(\"confirm-title\").innerHTML = title;\n        document.getElementById(\"confirm-body\").innerHTML = body;\n        document.getElementById(\"confirm-yes\").innerText = accept;\n        document.getElementById(\"confirm-no\").innerText = reject;\n        document.getElementById(\"confirm-modal\").style.display = \"block\";\n\n        this.confirmClosed = false;\n        $(\"#confirm-modal\").modal()\n            .one(\"show.bs.modal\", function(e) {\n                this.confirmClosed = false;\n            }.bind(this))\n            .one(\"click\", \"#confirm-yes\", function() {\n                this.confirmClosed = true;\n                callback.bind(scope)(true);\n                $(\"#confirm-modal\").modal(\"hide\");\n            }.bind(this))\n            .one(\"click\", \"#confirm-no\", function() {\n                this.confirmClosed = true;\n                callback.bind(scope)(false);\n            }.bind(this))\n            .one(\"hide.bs.modal\", function(e) {\n                if (!this.confirmClosed) {\n                    callback.bind(scope)(undefined);\n                }\n                this.confirmClosed = true;\n            }.bind(this));\n    }\n\n\n    /**\n     * Handler for CyerChef statechange events.\n     * Fires whenever the input or recipe changes in any way.\n     *\n     * @listens Manager#statechange\n     * @param {event} e\n     */\n    stateChange(e) {\n        debounce(function() {\n            this.progress = 0;\n            this.autoBake();\n            this.updateURL(true, null, true);\n        }, 20, \"stateChange\", this, [])();\n    }\n\n\n    /**\n     * Update the page title and URL to contain the new recipe\n     *\n     * @param {boolean} includeInput\n     * @param {string} [input=null]\n     * @param {boolean} [changeUrl=true]\n     */\n    updateURL(includeInput, input=null, changeUrl=true) {\n        // Set title\n        const recipeConfig = this.getRecipeConfig();\n        let title = \"CyberChef\";\n        if (recipeConfig.length === 1) {\n            title = `${recipeConfig[0].op} - ${title}`;\n        } else if (recipeConfig.length > 1) {\n            // See how long the full recipe is\n            const ops = recipeConfig.map(op => op.op).join(\", \");\n            if (ops.length < 45) {\n                title = `${ops} - ${title}`;\n            } else {\n                // If it's too long, just use the first one and say how many more there are\n                title = `${recipeConfig[0].op}, ${recipeConfig.length - 1} more - ${title}`;\n            }\n        }\n        document.title = title;\n\n        // Update the current history state (not creating a new one)\n        if (this.options.updateUrl && changeUrl) {\n            this.lastStateUrl = this.manager.controls.generateStateUrl(true, includeInput, input, recipeConfig);\n            window.history.replaceState({}, title, this.lastStateUrl);\n        }\n    }\n\n\n    /**\n     * Handler for the history popstate event.\n     * Reloads parameters from the URL.\n     *\n     * @param {event} e\n     */\n    popState(e) {\n        this.loadURIParams();\n    }\n\n}\n\nexport default App;\n"
  },
  {
    "path": "src/web/HTMLCategory.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n/**\n * Object to handle the creation of operation categories.\n */\nclass HTMLCategory {\n\n    /**\n     * HTMLCategory constructor.\n     *\n     * @param {string} name - The name of the category.\n     * @param {boolean} selected - Whether this category is pre-selected or not.\n     */\n    constructor(name, selected) {\n        this.name = name;\n        this.selected = selected;\n        this.opList = [];\n    }\n\n\n    /**\n     * Adds an operation to this category.\n     *\n     * @param {HTMLOperation} operation - The operation to add.\n     */\n    addOperation(operation) {\n        this.opList.push(operation);\n    }\n\n\n    /**\n     * Renders the category and all operations within it in HTML.\n     *\n     * @returns {string}\n     */\n    toHtml() {\n        const catName = \"cat\" + this.name.replace(/[\\s/\\-:_]/g, \"\");\n        let html = `<div class=\"panel category\">\n        <a class=\"category-title\" data-toggle=\"collapse\" data-target=\"#${catName}\">\n            ${this.name}\n            <span class=\"op-count hidden\">\n                ${this.opList.length}\n            </span>\n        </a>\n        <div id=\"${catName}\" class=\"panel-collapse collapse ${(this.selected ? \" show\" : \"\")}\" data-parent=\"#categories\">\n            <ul class=\"op-list\">`;\n\n        for (let i = 0; i < this.opList.length; i++) {\n            html += this.opList[i].toStubHtml();\n        }\n\n        html += \"</ul></div></div>\";\n        return html;\n    }\n\n}\n\nexport default HTMLCategory;\n"
  },
  {
    "path": "src/web/HTMLIngredient.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils from \"../core/Utils.mjs\";\n\n/**\n * Object to handle the creation of operation ingredients.\n */\nclass HTMLIngredient {\n\n    /**\n     * HTMLIngredient constructor.\n     *\n     * @param {Object} config - The configuration object for this ingredient.\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(config, app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.name = config.name;\n        this.type = config.type;\n        this.value = config.value;\n        this.disabled = config.disabled || false;\n        this.hint = config.hint || false;\n        this.rows = config.rows || false;\n        this.target = config.target;\n        this.defaultIndex = config.defaultIndex || 0;\n        this.maxLength = config.maxLength || null;\n        this.toggleValues = config.toggleValues;\n        this.ingId = this.app.nextIngId();\n        this.id = \"ing-\" + this.ingId;\n        this.tabIndex = this.ingId + 2; // Input = 1, Search = 2\n        this.min = (typeof config.min === \"number\") ? config.min : \"\";\n        this.max = (typeof config.max === \"number\") ? config.max : \"\";\n        this.step = config.step || 1;\n    }\n\n\n    /**\n     * Renders the ingredient in HTML.\n     *\n     * @returns {string}\n     */\n    toHtml() {\n        let html = \"\",\n            i, m, eventFn;\n        const hintHtml = this.hint ? `data-toggle=\"tooltip\" title=\"${this.hint}\"` : \"\";\n\n        switch (this.type) {\n            case \"string\":\n            case \"binaryString\":\n            case \"byteArray\":\n                html += `<div class=\"form-group ing-wide\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating\">${this.name}</label>\n                    <input type=\"text\"\n                        class=\"form-control arg\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        value=\"${this.value}\"\n                        ${this.disabled ? \"disabled\" : \"\"}\n                        ${this.maxLength ? `maxlength=\"${this.maxLength}\"` : \"\"}>\n                </div>`;\n                break;\n            case \"shortString\":\n            case \"binaryShortString\":\n                html += `<div class=\"form-group ing-short\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating inline\">${this.name}</label>\n                    <input type=\"text\"\n                        class=\"form-control arg inline\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        value=\"${this.value}\"\n                        ${this.disabled ? \"disabled\" : \"\"}\n                        ${this.maxLength ? `maxlength=\"${this.maxLength}\"` : \"\"}>\n                </div>`;\n                break;\n            case \"toggleString\":\n                html += `<div class=\"form-group input-group ing-wide\" data-help-title=\"Multi-type ingredients\" data-help=\"Selecting a data type from the dropdown will change how the ingredient is interpreted by the operation.\" ${hintHtml}>\n                    <div class=\"toggle-string\">\n                        <label for=\"${this.id}\" class=\"bmd-label-floating toggle-string\">${this.name}</label>\n                        <input type=\"text\"\n                            class=\"form-control arg toggle-string\"\n                            id=\"${this.id}\"\n                            tabindex=\"${this.tabIndex}\"\n                            arg-name=\"${this.name}\"\n                            value=\"${this.value}\"\n                            ${this.disabled ? \"disabled\" : \"\"}\n                            ${this.maxLength ? `maxlength=\"${this.maxLength}\"` : \"\"}>\n                    </div>\n                    <div class=\"input-group-append\">\n                        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">${this.toggleValues[0]}</button>\n                        <div class=\"dropdown-menu toggle-dropdown\">`;\n                for (i = 0; i < this.toggleValues.length; i++) {\n                    html += `<a class=\"dropdown-item\" href=\"#\">${this.toggleValues[i]}</a>`;\n                }\n                html += `</div>\n                    </div>\n\n                </div>`;\n                break;\n            case \"number\":\n                html += `<div class=\"form-group inline ing-medium\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating inline\">${this.name}</label>\n                    <input type=\"number\"\n                        class=\"form-control arg inline\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        value=\"${this.value}\"\n                        min=\"${this.min}\"\n                        max=\"${this.max}\"\n                        step=\"${this.step}\"\n                        ${this.disabled ? \"disabled\" : \"\"}>\n                </div>`;\n                break;\n            case \"boolean\":\n                html += `<div class=\"form-group inline boolean-arg ing-flexible\" ${hintHtml}>\n                    <div class=\"checkbox\">\n                        <label>\n                            <input type=\"checkbox\"\n                                class=\"arg\"\n                                id=\"${this.id}\"\n                                tabindex=\"${this.tabIndex}\"\n                                arg-name=\"${this.name}\"\n                                ${this.value ? \" checked\" : \"\"}\n                                ${this.disabled ? \" disabled\" : \"\"}\n                                value=\"${this.name}\"> ${this.name}\n                        </label>\n                    </div>\n                </div>`;\n                break;\n            case \"option\":\n                html += `<div class=\"form-group ing-medium\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating inline\">${this.name}</label>\n                    <select\n                        class=\"form-control arg inline\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        ${this.disabled ? \"disabled\" : \"\"}>`;\n                for (i = 0; i < this.value.length; i++) {\n                    if ((m = this.value[i].match(/\\[([a-z0-9 -()^]+)\\]/i))) {\n                        html += `<optgroup label=\"${m[1]}\">`;\n                    } else if (this.value[i].match(/\\[\\/([a-z0-9 -()^]+)\\]/i)) {\n                        html += \"</optgroup>\";\n                    } else {\n                        html += `<option ${this.defaultIndex === i ? \"selected\" : \"\"}>${this.value[i]}</option>`;\n                    }\n                }\n                html += `</select>\n                </div>`;\n                break;\n            case \"populateOption\":\n            case \"populateMultiOption\":\n                html += `<div class=\"form-group ing-medium\" data-help-title=\"Population dropdowns\" data-help=\"Selecting a value from this dropdown will populate some of the other ingredients for this operation with pre-canned values.\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating\">${this.name}</label>\n                    <select\n                        class=\"form-control arg no-state-change populate-option\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        ${this.disabled ? \"disabled\" : \"\"}>`;\n                for (i = 0; i < this.value.length; i++) {\n                    if ((m = this.value[i].name.match(/\\[([a-z0-9 -()^]+)\\]/i))) {\n                        html += `<optgroup label=\"${m[1]}\">`;\n                    } else if (this.value[i].name.match(/\\[\\/([a-z0-9 -()^]+)\\]/i)) {\n                        html += \"</optgroup>\";\n                    } else {\n                        const val = this.type === \"populateMultiOption\" ?\n                            JSON.stringify(this.value[i].value) :\n                            this.value[i].value;\n                        html += `<option populate-value='${Utils.escapeHtml(val)}'>${this.value[i].name}</option>`;\n                    }\n                }\n                html += `</select>\n                </div>`;\n\n                eventFn = this.type === \"populateMultiOption\" ?\n                    this.populateMultiOptionChange :\n                    this.populateOptionChange;\n                this.manager.addDynamicListener(\"#\" + this.id, \"change\", eventFn, this);\n                break;\n            case \"editableOption\":\n                html += `<div class=\"form-group input-group ing-wide\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating\">${this.name}</label>\n                    <input type=\"text\"\n                        class=\"form-control arg\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        value=\"${this.value[this.defaultIndex].value}\"\n                        ${this.disabled ? \"disabled\" : \"\"}>\n                    <div class=\"input-group-append\">\n                        <button type=\"button\"\n                            class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\"\n                            data-toggle=\"dropdown\"\n                            data-boundary=\"scrollParent\"\n                            aria-haspopup=\"true\"\n                            aria-expanded=\"false\">\n                            <span class=\"sr-only\">Toggle Dropdown</span>\n                        </button>\n                        <div class=\"dropdown-menu editable-option-menu\">`;\n                for (i = 0; i < this.value.length; i++) {\n                    html += `<a class=\"dropdown-item\" href=\"#\" value=\"${this.value[i].value}\">${this.value[i].name}</a>`;\n                }\n                html += `</div>\n                    </div>\n                </div>`;\n\n                this.manager.addDynamicListener(\".editable-option-menu a\", \"click\", this.editableOptionClick, this);\n                break;\n            case \"editableOptionShort\":\n                html += `<div class=\"form-group input-group ing-short\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating inline\">${this.name}</label>\n                    <input type=\"text\"\n                        class=\"form-control arg inline\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        value=\"${this.value[this.defaultIndex].value}\"\n                        ${this.disabled ? \"disabled\" : \"\"}>\n                    <div class=\"input-group-append inline\">\n                        <button type=\"button\"\n                            class=\"btn btn-secondary dropdown-toggle dropdown-toggle-split\"\n                            data-toggle=\"dropdown\"\n                            data-boundary=\"scrollParent\"\n                            aria-haspopup=\"true\"\n                            aria-expanded=\"false\">\n                            <span class=\"sr-only\">Toggle Dropdown</span>\n                        </button>\n                        <div class=\"dropdown-menu editable-option-menu\">`;\n                for (i = 0; i < this.value.length; i++) {\n                    html += `<a class=\"dropdown-item\" href=\"#\" value=\"${this.value[i].value}\">${this.value[i].name}</a>`;\n                }\n                html += `</div>\n                    </div>\n                </div>`;\n\n                this.manager.addDynamicListener(\".editable-option-menu a\", \"click\", this.editableOptionClick, this);\n                break;\n            case \"text\":\n                html += `<div class=\"form-group ing-very-wide\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating\">${this.name}</label>\n                    <textarea\n                        class=\"form-control arg\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        rows=\"${this.rows ? this.rows : 3}\"\n                        ${this.disabled ? \"disabled\" : \"\"}>${this.value}</textarea>\n                </div>`;\n                break;\n            case \"argSelector\":\n                html += `<div class=\"form-group inline ing-medium\" data-help-title=\"Ingredient selector\" data-help=\"Selecting options in this dropdown will configure which operation ingredients are visible.\" ${hintHtml}>\n                    <label for=\"${this.id}\" class=\"bmd-label-floating inline\">${this.name}</label>\n                    <select\n                        class=\"form-control arg inline arg-selector\"\n                        id=\"${this.id}\"\n                        tabindex=\"${this.tabIndex}\"\n                        arg-name=\"${this.name}\"\n                        ${this.disabled ? \"disabled\" : \"\"}>`;\n                for (i = 0; i < this.value.length; i++) {\n                    html += `<option ${this.defaultIndex === i ? \"selected\" : \"\"}\n                        turnon=\"${JSON.stringify(this.value[i].on || [])}\"\n                        turnoff=\"${JSON.stringify(this.value[i].off || [])}\">\n                            ${this.value[i].name}\n                        </option>`;\n                }\n                html += `</select>\n                </div>`;\n\n                this.manager.addDynamicListener(\".arg-selector\", \"change\", this.argSelectorChange, this);\n                break;\n            case \"label\":\n                html += `<div class=\"form-group ing-flexible\" ${hintHtml}>\n                    <label>${this.name}</label>\n                    <input type=\"hidden\"\n                        class=\"form-control arg\"\n                        id=\"${this.id}\"\n                        arg-name=\"${this.name}\"\n                        value=\"\">\n                </div>`;\n                break;\n            default:\n                break;\n        }\n\n        return html;\n    }\n\n\n    /**\n     * Handler for populate option changes.\n     * Populates the relevant argument with the specified value.\n     *\n     * @param {event} e\n     */\n    populateOptionChange(e) {\n        e.preventDefault();\n        e.stopPropagation();\n\n        const el = e.target;\n        const op = el.parentNode.parentNode;\n        const target = op.querySelectorAll(\".arg\")[this.target];\n\n        const popVal = el.childNodes[el.selectedIndex].getAttribute(\"populate-value\");\n        if (popVal !== \"\") target.value = popVal;\n\n        const evt = new Event(\"change\");\n        target.dispatchEvent(evt);\n\n        this.manager.recipe.ingChange();\n    }\n\n\n    /**\n     * Handler for populate multi option changes.\n     * Populates the relevant arguments with the specified values.\n     *\n     * @param {event} e\n     */\n    populateMultiOptionChange(e) {\n        e.preventDefault();\n        e.stopPropagation();\n\n        const el = e.target;\n        const op = el.parentNode.parentNode;\n        const args = op.querySelectorAll(\".arg\");\n        const targets = this.target.map(i => args[i]);\n        const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute(\"populate-value\"));\n        const evt = new Event(\"change\");\n\n        for (let i = 0; i < targets.length; i++) {\n            targets[i].value = vals[i];\n        }\n\n        // Fire change event after all targets have been assigned\n        this.manager.recipe.ingChange();\n\n        // Send change event for each target once all have been assigned, to update the label placement.\n        for (const target of targets) {\n            target.dispatchEvent(evt);\n        }\n    }\n\n\n    /**\n     * Handler for editable option clicks.\n     * Populates the input box with the selected value.\n     *\n     * @param {event} e\n     */\n    editableOptionClick(e) {\n        e.preventDefault();\n        e.stopPropagation();\n\n        const link = e.target,\n            input = link.parentNode.parentNode.parentNode.querySelector(\"input\");\n\n        input.value = link.getAttribute(\"value\");\n        const evt = new Event(\"change\");\n        input.dispatchEvent(evt);\n\n        this.manager.recipe.ingChange();\n    }\n\n\n    /**\n     * Handler for argument selector changes.\n     * Shows or hides the relevant arguments for this operation.\n     *\n     * @param {event} e\n     */\n    argSelectorChange(e) {\n        e.preventDefault();\n        e.stopPropagation();\n\n        const option = e.target.options[e.target.selectedIndex];\n        const op = e.target.closest(\".operation\");\n        const args = op.querySelectorAll(\".ingredients .form-group\");\n        const turnon = JSON.parse(option.getAttribute(\"turnon\"));\n        const turnoff = JSON.parse(option.getAttribute(\"turnoff\"));\n\n        args.forEach((arg, i) => {\n            if (turnon.includes(i)) {\n                arg.classList.remove(\"d-none\");\n            }\n            if (turnoff.includes(i)) {\n                arg.classList.add(\"d-none\");\n            }\n        });\n    }\n\n}\n\nexport default HTMLIngredient;\n"
  },
  {
    "path": "src/web/HTMLOperation.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport HTMLIngredient from \"./HTMLIngredient.mjs\";\nimport Utils from \"../core/Utils.mjs\";\nimport url from \"url\";\n\n\n/**\n * Object to handle the creation of operations.\n */\nclass HTMLOperation {\n\n    /**\n     * HTMLOperation constructor.\n     *\n     * @param {string} name - The name of the operation.\n     * @param {Object} config - The configuration object for this operation.\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(name, config, app, manager) {\n        this.app         = app;\n        this.manager     = manager;\n\n        this.name        = name;\n        this.description = config.description;\n        this.infoURL     = config.infoURL;\n        this.manualBake  = config.manualBake || false;\n        this.config      = config;\n        this.ingList     = [];\n\n        for (let i = 0; i < config.args.length; i++) {\n            const ing = new HTMLIngredient(config.args[i], this.app, this.manager);\n            this.ingList.push(ing);\n        }\n    }\n\n\n    /**\n     * Renders the operation in HTML as a stub operation with no ingredients.\n     *\n     * @returns {string}\n     */\n    toStubHtml(removeIcon) {\n        let html = \"<li class='operation'\";\n\n        if (this.description) {\n            const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : \"\";\n\n            html += ` data-container='body' data-toggle='popover' data-placement='right'\n                data-content=\"${this.description}${infoLink}\" data-html='true' data-trigger='hover'\n                data-boundary='viewport'`;\n        }\n\n        html += \">\" + this.name;\n\n        if (removeIcon) {\n            html += \"<i class='material-icons remove-icon op-icon'>delete</i>\";\n        }\n\n        html += \"</li>\";\n\n        return html;\n    }\n\n\n    /**\n     * Renders the operation in HTML as a full operation with ingredients.\n     *\n     * @returns {string}\n     */\n    toFullHtml() {\n        let html = `<div class=\"op-title\">${Utils.escapeHtml(this.name)}</div>\n        <div class=\"ingredients\">`;\n\n        for (let i = 0; i < this.ingList.length; i++) {\n            html += this.ingList[i].toHtml();\n        }\n\n        html += `</div>\n        <div class=\"recip-icons\">\n            <i class=\"material-icons breakpoint\" title=\"Set breakpoint\" break=\"false\" data-help-title=\"Setting breakpoints\" data-help=\"Setting a breakpoint on an operation will cause execution of the Recipe to pause when it reaches that operation.\">pause</i>\n            <i class=\"material-icons disable-icon\" title=\"Disable operation\" disabled=\"false\" data-help-title=\"Disabling operations\" data-help=\"Disabling an operation will prevent it from being executed when the Recipe is baked. Execution will skip over the disabled operation and continue with subsequent operations.\">not_interested</i>\n            <i class=\"material-icons hide-args-icon\" title=\"Hide operation's arguments\" hide-args=\"false\" data-help-title=\"Hide operation's arguments\" data-help=\"Hiding an operation's argument will save space in the Recipe window. Execution will still take place with the selected argument options.\">keyboard_arrow_up</i>\n        </div>\n        <div class=\"clearfix\">&nbsp;</div>`;\n\n        return html;\n    }\n\n\n    /**\n     * Highlights searched strings in the name and description of the operation.\n     *\n     * @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]]\n     * @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]]\n     */\n    highlightSearchStrings(nameIdxs, descIdxs) {\n        if (nameIdxs.length && typeof nameIdxs[0][0] === \"number\") {\n            let opName = \"\",\n                pos = 0;\n\n            nameIdxs.forEach(idxs => {\n                const [start, length] = idxs;\n                if (typeof start !== \"number\") return;\n                opName += this.name.slice(pos, start) + \"<b>\" +\n                    this.name.slice(start, start + length) + \"</b>\";\n                pos = start + length;\n            });\n            opName += this.name.slice(pos, this.name.length);\n            this.name = opName;\n        }\n\n        if (this.description && descIdxs.length && descIdxs[0][0] >= 0) {\n            // Find HTML tag offsets\n            const re = /<[^>]+>/g;\n            let match;\n            while ((match = re.exec(this.description))) {\n                // If the search string occurs within an HTML tag, return without highlighting it.\n                const inHTMLTag = descIdxs.reduce((acc, idxs) => {\n                    const start = idxs[0];\n                    return start >= match.index && start <= (match.index + match[0].length);\n                }, false);\n\n                if (inHTMLTag) return;\n            }\n\n            let desc = \"\",\n                pos = 0;\n\n            descIdxs.forEach(idxs => {\n                const [start, length] = idxs;\n                desc += this.description.slice(pos, start) + \"<b><u>\" +\n                    this.description.slice(start, start + length) + \"</u></b>\";\n                pos = start + length;\n            });\n            desc += this.description.slice(pos, this.description.length);\n            this.description = desc;\n        }\n    }\n\n}\n\n\n/**\n * Given a URL for a Wikipedia (or other wiki) page, this function returns a link to that page.\n *\n * @param {string} urlStr\n * @returns {string}\n */\nfunction titleFromWikiLink(urlStr) {\n    const urlObj = url.parse(urlStr);\n    let wikiName = \"\",\n        pageTitle = \"\";\n\n    switch (urlObj.host) {\n        case \"forensics.wiki\":\n            wikiName = \"Forensics Wiki\";\n            pageTitle = Utils.toTitleCase(urlObj.path.replace(/\\//g, \"\").replace(/_/g, \" \"));\n            break;\n        case \"wikipedia.org\":\n            wikiName = \"Wikipedia\";\n            pageTitle = urlObj.pathname.substr(6).replace(/_/g, \" \"); // Chop off '/wiki/'\n            break;\n        default:\n            // Not a wiki link, return full URL\n            return `<a href='${urlStr}' target='_blank'>More Information<i class='material-icons inline-icon'>open_in_new</i></a>`;\n    }\n\n    return `<a href='${urlObj.href}' target='_blank'>${pageTitle}<i class='material-icons inline-icon'>open_in_new</i></a> on ${wikiName}`;\n}\n\nexport default HTMLOperation;\n"
  },
  {
    "path": "src/web/Manager.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport WorkerWaiter from \"./waiters/WorkerWaiter.mjs\";\nimport WindowWaiter from \"./waiters/WindowWaiter.mjs\";\nimport ControlsWaiter from \"./waiters/ControlsWaiter.mjs\";\nimport RecipeWaiter from \"./waiters/RecipeWaiter.mjs\";\nimport OperationsWaiter from \"./waiters/OperationsWaiter.mjs\";\nimport InputWaiter from \"./waiters/InputWaiter.mjs\";\nimport OutputWaiter from \"./waiters/OutputWaiter.mjs\";\nimport OptionsWaiter from \"./waiters/OptionsWaiter.mjs\";\nimport HighlighterWaiter from \"./waiters/HighlighterWaiter.mjs\";\nimport SeasonalWaiter from \"./waiters/SeasonalWaiter.mjs\";\nimport BindingsWaiter from \"./waiters/BindingsWaiter.mjs\";\nimport BackgroundWorkerWaiter from \"./waiters/BackgroundWorkerWaiter.mjs\";\nimport TabWaiter from \"./waiters/TabWaiter.mjs\";\nimport TimingWaiter from \"./waiters/TimingWaiter.mjs\";\n\n\n/**\n * This object controls the Waiters responsible for handling events from all areas of the app.\n */\nclass Manager {\n\n    /**\n     * Manager constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     */\n    constructor(app) {\n        this.app = app;\n\n        // Define custom events\n        /**\n         * @event Manager#appstart\n         */\n        this.appstart = new CustomEvent(\"appstart\", {bubbles: true});\n        /**\n         * @event Manager#apploaded\n         */\n        this.apploaded = new CustomEvent(\"apploaded\", {bubbles: true});\n        /**\n         * @event Manager#operationadd\n         */\n        this.operationadd = new CustomEvent(\"operationadd\", {bubbles: true});\n        /**\n         * @event Manager#operationremove\n         */\n        this.operationremove = new CustomEvent(\"operationremove\", {bubbles: true});\n        /**\n         * @event Manager#oplistcreate\n         */\n        this.oplistcreate = new CustomEvent(\"oplistcreate\", {bubbles: true});\n        /**\n         * @event Manager#statechange\n         */\n        this.statechange = new CustomEvent(\"statechange\", {bubbles: true});\n\n        // Define Waiter objects to handle various areas\n        this.timing      = new TimingWaiter(this.app, this);\n        this.worker      = new WorkerWaiter(this.app, this);\n        this.window      = new WindowWaiter(this.app);\n        this.controls    = new ControlsWaiter(this.app, this);\n        this.recipe      = new RecipeWaiter(this.app, this);\n        this.ops         = new OperationsWaiter(this.app, this);\n        this.tabs        = new TabWaiter(this.app, this);\n        this.input       = new InputWaiter(this.app, this);\n        this.output      = new OutputWaiter(this.app, this);\n        this.options     = new OptionsWaiter(this.app, this);\n        this.highlighter = new HighlighterWaiter(this.app, this);\n        this.seasonal    = new SeasonalWaiter(this.app, this);\n        this.bindings    = new BindingsWaiter(this.app, this);\n        this.background  = new BackgroundWorkerWaiter(this.app, this);\n\n        // Object to store dynamic handlers to fire on elements that may not exist yet\n        this.dynamicHandlers = {};\n\n        this.initialiseEventListeners();\n    }\n\n\n    /**\n     * Sets up the various components and listeners.\n     */\n    setup() {\n        this.input.setupInputWorker();\n        this.input.addInput(true);\n        this.worker.setupChefWorker();\n        this.recipe.initialiseOperationDragNDrop();\n        this.controls.initComponents();\n        this.controls.autoBakeChange();\n        this.bindings.updateKeybList();\n        this.background.registerChefWorker();\n        this.seasonal.load();\n\n        this.confirmWaitersLoaded();\n    }\n\n    /**\n     * Confirms that all Waiters have loaded correctly.\n     */\n    confirmWaitersLoaded() {\n        if (this.tabs.getActiveTab(\"input\") >= 0 &&\n            this.tabs.getActiveTab(\"output\") >= 0) {\n            log.debug(\"Waiters loaded\");\n            this.app.waitersLoaded = true;\n            this.app.loaded();\n        } else {\n            // Not loaded yet, try again soon\n            setTimeout(this.confirmWaitersLoaded.bind(this), 10);\n        }\n    }\n\n\n    /**\n     * Main function to handle the creation of the event listeners.\n     */\n    initialiseEventListeners() {\n        // Global\n        window.addEventListener(\"resize\", this.window.windowResize.bind(this.window));\n        window.addEventListener(\"blur\", this.window.windowBlur.bind(this.window));\n        window.addEventListener(\"focus\", this.window.windowFocus.bind(this.window));\n        window.addEventListener(\"statechange\", this.app.stateChange.bind(this.app));\n        window.addEventListener(\"popstate\", this.app.popState.bind(this.app));\n        window.addEventListener(\"message\", this.input.handlePostMessage.bind(this.input));\n\n        // Controls\n        document.getElementById(\"bake\").addEventListener(\"click\", this.controls.bakeClick.bind(this.controls));\n        document.getElementById(\"auto-bake\").addEventListener(\"change\", this.controls.autoBakeChange.bind(this.controls));\n        document.getElementById(\"step\").addEventListener(\"click\", this.controls.stepClick.bind(this.controls));\n        document.getElementById(\"clr-recipe\").addEventListener(\"click\", this.controls.clearRecipeClick.bind(this.controls));\n        document.getElementById(\"save\").addEventListener(\"click\", this.controls.saveClick.bind(this.controls));\n        document.getElementById(\"save-button\").addEventListener(\"click\", this.controls.saveButtonClick.bind(this.controls));\n        document.getElementById(\"save-link-recipe-checkbox\").addEventListener(\"change\", this.controls.slrCheckChange.bind(this.controls));\n        document.getElementById(\"save-link-input-checkbox\").addEventListener(\"change\", this.controls.sliCheckChange.bind(this.controls));\n        document.getElementById(\"load\").addEventListener(\"click\", this.controls.loadClick.bind(this.controls));\n        document.getElementById(\"load-delete-button\").addEventListener(\"click\", this.controls.loadDeleteClick.bind(this.controls));\n        document.getElementById(\"load-name\").addEventListener(\"change\", this.controls.loadNameChange.bind(this.controls));\n        document.getElementById(\"load-button\").addEventListener(\"click\", this.controls.loadButtonClick.bind(this.controls));\n        document.getElementById(\"hide-icon\").addEventListener(\"click\", this.controls.hideRecipeArgsClick.bind(this.recipe));\n        document.getElementById(\"support\").addEventListener(\"click\", this.controls.supportButtonClick.bind(this.controls));\n        this.addMultiEventListeners(\"#save-texts textarea\", \"keyup paste\", this.controls.saveTextChange, this.controls);\n\n        // Operations\n        this.addMultiEventListener(\"#search\", \"keyup paste search\", this.ops.searchOperations, this.ops);\n        this.addDynamicListener(\".op-list li.operation\", \"dblclick\", this.ops.operationDblclick, this.ops);\n        document.getElementById(\"edit-favourites\").addEventListener(\"click\", this.ops.editFavouritesClick.bind(this.ops));\n        document.getElementById(\"save-favourites\").addEventListener(\"click\", this.ops.saveFavouritesClick.bind(this.ops));\n        document.getElementById(\"reset-favourites\").addEventListener(\"click\", this.ops.resetFavouritesClick.bind(this.ops));\n        this.addDynamicListener(\".op-list\", \"oplistcreate\", this.ops.opListCreate, this.ops);\n        this.addDynamicListener(\"li.operation\", \"operationadd\", this.recipe.opAdd, this.recipe);\n\n        // Recipe\n        this.addDynamicListener(\".arg:not(select)\", \"input\", this.recipe.ingChange, this.recipe);\n        this.addDynamicListener(\".arg[type=checkbox], .arg[type=radio], select.arg\", \"change\", this.recipe.ingChange, this.recipe);\n        this.addDynamicListener(\".hide-args-icon\", \"click\", this.recipe.hideArgsClick, this.recipe);\n        this.addDynamicListener(\".disable-icon\", \"click\", this.recipe.disableClick, this.recipe);\n        this.addDynamicListener(\".breakpoint\", \"click\", this.recipe.breakpointClick, this.recipe);\n        this.addDynamicListener(\"#rec-list li.operation\", \"dblclick\", this.recipe.operationDblclick, this.recipe);\n        this.addDynamicListener(\"#rec-list li.operation > div\", \"dblclick\", this.recipe.operationChildDblclick, this.recipe);\n        this.addDynamicListener(\"#rec-list .dropdown-menu.toggle-dropdown a\", \"click\", this.recipe.dropdownToggleClick, this.recipe);\n        this.addDynamicListener(\"#rec-list\", \"operationremove\", this.recipe.opRemove.bind(this.recipe));\n        this.addDynamicListener(\"textarea.arg\", \"dragover\", this.recipe.textArgDragover, this.recipe);\n        this.addDynamicListener(\"textarea.arg\", \"dragleave\", this.recipe.textArgDragLeave, this.recipe);\n        this.addDynamicListener(\"textarea.arg\", \"drop\", this.recipe.textArgDrop, this.recipe);\n\n        // Input\n        document.getElementById(\"reset-layout\").addEventListener(\"click\", this.app.resetLayout.bind(this.app));\n        this.addListeners(\"#clr-io,#btn-close-all-tabs\", \"click\", this.input.clearAllIoClick, this.input);\n        this.addListeners(\"#open-file,#open-folder\", \"change\", this.input.inputOpen, this.input);\n        document.getElementById(\"btn-open-file\").addEventListener(\"click\", this.input.inputOpenClick.bind(this.input));\n        document.getElementById(\"btn-open-folder\").addEventListener(\"click\", this.input.folderOpenClick.bind(this.input));\n        this.addListeners(\"#input-wrapper\", \"dragover\", this.input.inputDragover, this.input);\n        this.addListeners(\"#input-wrapper\", \"dragleave\", this.input.inputDragleave, this.input);\n        this.addListeners(\"#input-wrapper\", \"drop\", this.input.inputDrop, this.input);\n        document.getElementById(\"btn-new-tab\").addEventListener(\"click\", this.input.addInputClick.bind(this.input));\n        document.getElementById(\"btn-previous-input-tab\").addEventListener(\"mousedown\", this.input.previousTabClick.bind(this.input));\n        document.getElementById(\"btn-next-input-tab\").addEventListener(\"mousedown\", this.input.nextTabClick.bind(this.input));\n        this.addListeners(\"#btn-next-input-tab,#btn-previous-input-tab\", \"mouseup\", this.input.tabMouseUp, this.input);\n        this.addListeners(\"#btn-next-input-tab,#btn-previous-input-tab\", \"mouseout\", this.input.tabMouseUp, this.input);\n        document.getElementById(\"btn-go-to-input-tab\").addEventListener(\"click\", this.input.goToTab.bind(this.input));\n        document.getElementById(\"btn-find-input-tab\").addEventListener(\"click\", this.input.findTab.bind(this.input));\n        this.addDynamicListener(\"#input-tabs li .input-tab-content\", \"click\", this.input.changeTabClick, this.input);\n        document.getElementById(\"input-show-pending\").addEventListener(\"change\", this.input.filterTabSearch.bind(this.input));\n        document.getElementById(\"input-show-loading\").addEventListener(\"change\", this.input.filterTabSearch.bind(this.input));\n        document.getElementById(\"input-show-loaded\").addEventListener(\"change\", this.input.filterTabSearch.bind(this.input));\n        this.addListeners(\"#input-filter-content,#input-filter-filename\", \"click\", this.input.filterOptionClick, this.input);\n        document.getElementById(\"input-filter\").addEventListener(\"change\", this.input.filterTabSearch.bind(this.input));\n        document.getElementById(\"input-filter\").addEventListener(\"keyup\", this.input.filterTabSearch.bind(this.input));\n        document.getElementById(\"input-num-results\").addEventListener(\"change\", this.input.filterTabSearch.bind(this.input));\n        document.getElementById(\"input-num-results\").addEventListener(\"keyup\", this.input.filterTabSearch.bind(this.input));\n        document.getElementById(\"input-filter-refresh\").addEventListener(\"click\", this.input.filterTabSearch.bind(this.input));\n        this.addDynamicListener(\".input-filter-result\", \"click\", this.input.filterItemClick, this.input);\n\n\n        // Output\n        document.getElementById(\"save-to-file\").addEventListener(\"click\", this.output.saveClick.bind(this.output));\n        document.getElementById(\"save-all-to-file\").addEventListener(\"click\", this.output.saveAllClick.bind(this.output));\n        document.getElementById(\"copy-output\").addEventListener(\"click\", this.output.copyClick.bind(this.output));\n        document.getElementById(\"switch\").addEventListener(\"click\", this.output.switchClick.bind(this.output));\n        document.getElementById(\"maximise-output\").addEventListener(\"click\", this.output.maximiseOutputClick.bind(this.output));\n        document.getElementById(\"magic\").addEventListener(\"click\", this.output.magicClick.bind(this.output));\n        this.addDynamicListener(\".extract-file,.extract-file i\", \"click\", this.output.extractFileClick, this.output);\n        this.addDynamicListener(\"#output-tabs-wrapper #output-tabs li .output-tab-content\", \"click\", this.output.changeTabClick, this.output);\n        document.getElementById(\"btn-previous-output-tab\").addEventListener(\"mousedown\", this.output.previousTabClick.bind(this.output));\n        document.getElementById(\"btn-next-output-tab\").addEventListener(\"mousedown\", this.output.nextTabClick.bind(this.output));\n        this.addListeners(\"#btn-next-output-tab,#btn-previous-output-tab\", \"mouseup\", this.output.tabMouseUp, this.output);\n        this.addListeners(\"#btn-next-output-tab,#btn-previous-output-tab\", \"mouseout\", this.output.tabMouseUp, this.output);\n        document.getElementById(\"btn-go-to-output-tab\").addEventListener(\"click\", this.output.goToTab.bind(this.output));\n        document.getElementById(\"btn-find-output-tab\").addEventListener(\"click\", this.output.findTab.bind(this.output));\n        document.getElementById(\"output-show-pending\").addEventListener(\"change\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-show-baking\").addEventListener(\"change\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-show-baked\").addEventListener(\"change\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-show-stale\").addEventListener(\"change\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-show-errored\").addEventListener(\"change\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-content-filter\").addEventListener(\"change\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-content-filter\").addEventListener(\"keyup\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-num-results\").addEventListener(\"change\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-num-results\").addEventListener(\"keyup\", this.output.filterTabSearch.bind(this.output));\n        document.getElementById(\"output-filter-refresh\").addEventListener(\"click\", this.output.filterTabSearch.bind(this.output));\n        this.addDynamicListener(\".output-filter-result\", \"click\", this.output.filterItemClick, this.output);\n\n\n        // Options\n        document.getElementById(\"options\").addEventListener(\"click\", this.options.optionsClick.bind(this.options));\n        document.getElementById(\"reset-options\").addEventListener(\"click\", this.options.resetOptionsClick.bind(this.options));\n        this.addDynamicListener(\".option-item input[type=checkbox]\", \"change\", this.options.switchChange, this.options);\n        this.addDynamicListener(\".option-item input[type=checkbox]#wordWrap\", \"change\", this.options.setWordWrap, this.options);\n        this.addDynamicListener(\".option-item input[type=checkbox]#useMetaKey\", \"change\", this.bindings.updateKeybList, this.bindings);\n        this.addDynamicListener(\".option-item input[type=checkbox]#showCatCount\", \"change\", this.ops.setCatCount, this.ops);\n        this.addDynamicListener(\".option-item input[type=number]\", \"keyup\", this.options.numberChange, this.options);\n        this.addDynamicListener(\".option-item input[type=number]\", \"change\", this.options.numberChange, this.options);\n        this.addDynamicListener(\".option-item select\", \"change\", this.options.selectChange, this.options);\n        document.getElementById(\"theme\").addEventListener(\"change\", this.options.themeChange.bind(this.options));\n        document.getElementById(\"logLevel\").addEventListener(\"change\", this.options.logLevelChange.bind(this.options));\n\n        // Misc\n        window.addEventListener(\"keydown\", this.bindings.parseInput.bind(this.bindings));\n    }\n\n\n    /**\n     * Adds an event listener to each element in the specified group.\n     *\n     * @param {string} selector - A selector string for the element group to add the event to, see\n     *   this.getAll()\n     * @param {string} eventType - The event to listen for\n     * @param {function} callback - The function to execute when the event is triggered\n     * @param {Object} [scope=this] - The object to bind to the callback function\n     *\n     * @example\n     * // Calls the clickable function whenever any element with the .clickable class is clicked\n     * this.addListeners(\".clickable\", \"click\", this.clickable, this);\n     */\n    addListeners(selector, eventType, callback, scope) {\n        scope = scope || this;\n        [].forEach.call(document.querySelectorAll(selector), function(el) {\n            el.addEventListener(eventType, callback.bind(scope));\n        });\n    }\n\n\n    /**\n     * Adds multiple event listeners to the specified element.\n     *\n     * @param {string} selector - A selector string for the element to add the events to\n     * @param {string} eventTypes - A space-separated string of all the event types to listen for\n     * @param {function} callback - The function to execute when the events are triggered\n     * @param {Object} [scope=this] - The object to bind to the callback function\n     *\n     * @example\n     * // Calls the search function whenever the keyup, paste or search events are triggered on the\n     * // search element\n     * this.addMultiEventListener(\"search\", \"keyup paste search\", this.search, this);\n     */\n    addMultiEventListener(selector, eventTypes, callback, scope) {\n        const evs = eventTypes.split(\" \");\n        for (let i = 0; i < evs.length; i++) {\n            document.querySelector(selector).addEventListener(evs[i], callback.bind(scope));\n        }\n    }\n\n\n    /**\n     * Adds multiple event listeners to each element in the specified group.\n     *\n     * @param {string} selector - A selector string for the element group to add the events to\n     * @param {string} eventTypes - A space-separated string of all the event types to listen for\n     * @param {function} callback - The function to execute when the events are triggered\n     * @param {Object} [scope=this] - The object to bind to the callback function\n     *\n     * @example\n     * // Calls the save function whenever the keyup or paste events are triggered on any element\n     * // with the .saveable class\n     * this.addMultiEventListener(\".saveable\", \"keyup paste\", this.save, this);\n     */\n    addMultiEventListeners(selector, eventTypes, callback, scope) {\n        const evs = eventTypes.split(\" \");\n        for (let i = 0; i < evs.length; i++) {\n            this.addListeners(selector, evs[i], callback, scope);\n        }\n    }\n\n\n    /**\n     * Adds an event listener to the global document object which will listen on dynamic elements which\n     * may not exist in the DOM yet.\n     *\n     * @param {string} selector - A selector string for the element(s) to add the event to\n     * @param {string} eventType - The event(s) to listen for\n     * @param {function} callback - The function to execute when the event(s) is/are triggered\n     * @param {Object} [scope=this] - The object to bind to the callback function\n     *\n     * @example\n     * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this\n     * // listener is created\n     * this.addDynamicListener(\"button\", \"click\", alert, this);\n     */\n    addDynamicListener(selector, eventType, callback, scope) {\n        const eventConfig = {\n            selector: selector,\n            callback: callback.bind(scope || this)\n        };\n\n        if (Object.prototype.hasOwnProperty.call(this.dynamicHandlers, eventType)) {\n            // Listener already exists, add new handler to the appropriate list\n            this.dynamicHandlers[eventType].push(eventConfig);\n        } else {\n            this.dynamicHandlers[eventType] = [eventConfig];\n            // Set up listener for this new type\n            document.addEventListener(eventType, this.dynamicListenerHandler.bind(this));\n        }\n    }\n\n\n    /**\n     * Handler for dynamic events. This function is called for any dynamic event and decides which\n     * callback(s) to execute based on the type and selector.\n     *\n     * @param {Event} e - The event to be handled\n     */\n    dynamicListenerHandler(e) {\n        const { type, target } = e;\n        const handlers = this.dynamicHandlers[type];\n        const matches = target.matches ||\n                target.webkitMatchesSelector ||\n                target.mozMatchesSelector ||\n                target.msMatchesSelector ||\n                target.oMatchesSelector;\n\n        for (let i = 0; i < handlers.length; i++) {\n            if (matches && matches.call(target, handlers[i].selector)) {\n                handlers[i].callback(e);\n            }\n        }\n    }\n}\n\nexport default Manager;\n"
  },
  {
    "path": "src/web/html/index.html",
    "content": "<!-- htmlmin:ignore --><!--\n    CyberChef - The Cyber Swiss Army Knife\n\n    @copyright Crown Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %>\n    @license Apache-2.0\n\n      Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %> Crown Copyright\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n-->\n<!-- htmlmin:ignore -->\n<!DOCTYPE html>\n<html lang=\"en\" class=\"classic\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <title>CyberChef</title>\n\n        <meta name=\"copyright\" content=\"Crown Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %>\" />\n        <meta name=\"description\" content=\"The Cyber Swiss Army Knife - a web app for encryption, encoding, compression and data analysis\" />\n        <meta name=\"keywords\" content=\"base64, hex, decode, encode, encrypt, decrypt, compress, decompress, regex, regular expressions, hash, crypt, hexadecimal, user agent, url, certificate, x.509, parser, JSON, gzip,  md5, sha1, aes, des, blowfish, xor\" />\n\n        <link rel=\"icon\" type=\"image/ico\" href=\"<%- require('../static/images/favicon.ico') %>\" />\n\n        <script type=\"application/javascript\">\n            \"use strict\";\n\n            // Load theme before the preloader is shown\n            try {\n                document.querySelector(\":root\").className = (JSON.parse(localStorage.getItem(\"options\")) || {}).theme;\n            } catch (err) {\n                // LocalStorage access is denied by security settings\n            }\n\n            // Define loading messages\n            var loadingMsgs = [\n                \"Proving P = NP...\",\n                \"Computing 6 x 9...\",\n                \"Mining bitcoin...\",\n                \"Dividing by 0...\",\n                \"Initialising Skynet...\",\n                \"[REDACTED]\",\n                \"Downloading more RAM...\",\n                \"Ordering 1s and 0s...\",\n                \"Navigating neural network...\",\n                \"Importing machine learning...\",\n                \"Issuing Alice and Bob one-time pads...\",\n                \"Mining bitcoin cash...\",\n                \"Generating key material by trying to escape vim...\",\n                \"for i in range(additional): Pylon()\",\n                \"(creating unresolved tension...\",\n                \"Symlinking emacs and vim to ed...\",\n                \"Training branch predictor...\",\n                \"Timing cache hits...\",\n                \"Speculatively executing recipes...\",\n                \"Adding LLM hallucinations...\",\n                \"Decompressing malware...\"\n            ];\n\n            // Shuffle array using Durstenfeld algorithm\n            for (let i = loadingMsgs.length - 1; i > 0; --i) {\n                var j = Math.floor(Math.random() * (i + 1));\n                var temp = loadingMsgs[i];\n                loadingMsgs[i] = loadingMsgs[j];\n                loadingMsgs[j] = temp;\n            }\n\n            // Show next loading message and move it to the end of the array\n            function changeLoadingMsg() {\n                var msg = loadingMsgs.shift();\n                loadingMsgs.push(msg);\n                try {\n                    var el = document.getElementById(\"preloader-msg\");\n                    if (!el.classList.contains(\"loading\"))\n                        el.classList.add(\"loading\"); // Causes CSS transition on first message\n                    el.innerHTML = msg;\n                } catch (err) {\n                    // This error was likely caused by the DOM not being ready yet,\n                    // so we wait another second and then try again.\n                    setTimeout(changeLoadingMsg, 1000);\n                }\n            }\n\n            changeLoadingMsg();\n            window.loadingMsgsInt = setInterval(changeLoadingMsg, (Math.random() * 2000) + 1500);\n\n            // If any errors are thrown during loading, handle them here\n            function loadingErrorHandler(e) {\n                function escapeHtml(str) {\n                    var HTML_CHARS = {\n                        \"&\": \"&amp;\",\n                        \"<\": \"&lt;\",\n                        \">\": \"&gt;\",\n                        '\"': \"&quot;\",\n                        \"'\": \"&#x27;\", // &apos; not recommended because it's not in the HTML spec\n                        \"/\": \"&#x2F;\", // forward slash is included as it helps end an HTML entity\n                        \"`\": \"&#x60;\"\n                    };\n\n                    return str.replace(/[&<>\"'/`]/g, function (match) {\n                        return HTML_CHARS[match];\n                    });\n                }\n\n                var msg = e.message +\n                    (e.filename ? \"\\nFilename: \" + e.filename : \"\") +\n                    (e.lineno ? \"\\nLine: \" + e.lineno : \"\") +\n                    (e.colno ? \"\\nColumn: \" + e.colno : \"\") +\n                    (e.error ? \"\\nError: \" + e.error : \"\") +\n                    \"\\nUser-Agent: \" + navigator.userAgent +\n                    \"\\nCyberChef version: <%= htmlWebpackPlugin.options.version %>\";\n\n                clearInterval(window.loadingMsgsInt);\n                document.getElementById(\"preloader\").remove();\n                document.getElementById(\"preloader-msg\").remove();\n                document.getElementById(\"preloader-error\").innerHTML =\n                    \"CyberChef encountered an error while loading.<br><br>\" +\n                    \"The following browser versions are supported:\" +\n                    \"<ul><li>Google Chrome 50+</li><li>Mozilla Firefox 38+</li></ul>\" +\n                    \"Your user agent is:<br>\" + escapeHtml(navigator.userAgent) + \"<br><br>\" +\n                    \"If your browser is supported, please <a href='https://github.com/gchq/CyberChef/issues/new/choose'>\" +\n                    \"raise an issue</a> including the following details:<br><br>\" +\n                    \"<pre>\" + escapeHtml(msg) + \"</pre>\";\n            };\n            window.addEventListener(\"error\", loadingErrorHandler);\n        </script>\n    </head>\n    <body>\n        <!-- Preloader overlay -->\n        <div id=\"loader-wrapper\">\n            <div id=\"preloader\" class=\"loader\"></div>\n            <div id=\"preloader-msg\" class=\"loading-msg\"></div>\n            <div id=\"preloader-error\" class=\"loading-error\"></div>\n        </div>\n        <!-- End preloader overlay -->\n        <button type=\"button\" aria-label=\"Edit Favourites\" class=\"btn btn-warning bmd-btn-icon\" id=\"edit-favourites\" data-toggle=\"tooltip\" title=\"Edit favourites\">\n            <i class=\"material-icons\" aria-hidden=\"true\">star</i>\n        </button>\n        <div id=\"content-wrapper\">\n            <div id=\"banner\" class=\"row\">\n                <div class=\"col\" style=\"text-align: left; padding-left: 10px;\">\n                    <a href=\"#\" data-toggle=\"modal\" data-target=\"#download-modal\" data-help-title=\"Downloading CyberChef\" data-help=\"<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>\">Download CyberChef <i class=\"material-icons\">file_download</i></a>\n                </div>\n                <div class=\"col-md-6\" id=\"notice-wrapper\">\n                    <span id=\"notice\">\n                        <script type=\"text/javascript\">\n                            // Must be text/javascript rather than application/javascript otherwise IE won't recognise it...\n                            if (navigator.userAgent && navigator.userAgent.match(/Trident/)) {\n                                document.getElementById(\"notice\").innerHTML += \"Internet Explorer is not supported, please use Firefox or Chrome instead\";\n                                alert(\"Internet Explorer is not supported, please use Firefox or Chrome instead\");\n                            }\n                        </script>\n                        <noscript>JavaScript is not enabled. Good luck.</noscript>\n                    </span>\n                </div>\n                <div class=\"col\" style=\"text-align: right; padding-right: 0;\">\n                    <a href=\"#\" id=\"options\" data-help-title=\"Options and Settings\" data-help=\"Configurable options to change how CyberChef behaves. These settings are stored in your browser's local storage, meaning they will persist between sessions that use the same browser profile.\">Options <i class=\"material-icons\">settings</i></a>\n                    <a href=\"#\" id=\"support\" data-toggle=\"modal\" data-target=\"#support-modal\" data-help-title=\"About / Support\" data-help=\"This pane provides information about the CyberChef web app, how to use some of the features, and how to raise bug reports.\">About / Support <i class=\"material-icons\">help</i></a>\n                </div>\n            </div>\n            <div id=\"workspace-wrapper\">\n                <div id=\"operations\" class=\"split split-horizontal no-select\">\n                    <div class=\"title no-select\" data-help-title=\"Operations list\" data-help=\"<p>The Operations list contains all the operations in CyberChef arranged into categories. Some operations may be present in multiple categories. You can search for operations using the search box.</p><p>To use an operation, either double click it, or drag it into the Recipe pane. You will then be able to configure its arguments (or 'Ingredients' in CyberChef terminology).</p>\">\n                        Operations\n                        <span class=\"op-count\"></span>\n                    </div>\n                    <input id=\"search\" type=\"search\" class=\"form-control\" placeholder=\"Search...\" autocomplete=\"off\" tabindex=\"2\" data-help-title=\"Searching for operations\" data-help=\"<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>\">\n                    <ul id=\"search-results\" class=\"op-list\"></ul>\n                    <div id=\"categories\" class=\"panel-group no-select\"></div>\n                </div>\n\n                <div id=\"recipe\" class=\"split split-horizontal no-select\" data-help-title=\"Recipe pane\" data-help=\"<p>The Recipe pane is where your chosen Operations are configured. If you are a programmer, think of these as functions. If you are not a programmer, these are like steps in a cake recipe. The Input data will be processed based on the Operations in your Recipe.</p><ul><li>To reorder, simply drag and drop the Operations into the order your require</li><li>To remove an operation, either double click it, or drag it outside of the Recipe pane</li></ul><p>The arguments (or 'Ingredients' in CyberChef terminology) can be configured to change how an Operation processes the data.</p>\">\n                    <div class=\"title no-select\">\n                        Recipe\n                        <span class=\"pane-controls hide-on-maximised-output\">\n                            <button type=\"button\" aria-label=\"Hide arguments\" class=\"btn btn-primary bmd-btn-icon\" id=\"hide-icon\" data-toggle=\"tooltip\" title=\"Hide arguments\" hide-args=\"false\" data-help-title=\"Hiding every Operation's argument view in a Recipe\" data-help=\"Clicking 'Hide arguments' will hide all the argument views for every Operation in the Recipe, to save space when you have too many Operation in your Recipe\">\n                                <i class=\"material-icons\">keyboard_arrow_up</i>\n                            </button>\n                            <button type=\"button\" aria-label=\"Save recipe\" class=\"btn btn-primary bmd-btn-icon\" id=\"save\" data-toggle=\"tooltip\" title=\"Save recipe\" data-help-title=\"Saving a recipe\" data-help=\"<p>Recipes can be represented in a few different formats and saved for use at a later date. You can either copy the Recipe configuration and save it somewhere offline for later use, or use your browser's local storage.</p><ul><li><b>Deep link:</b> The easiest way to share a CyberChef Recipe is to copy the deep link, either from the address bar (which is updated as the Recipe or Input changes), or from the 'Save recipe' pane. When you visit this link, the Recipe and Input will be populated from where you left off.</li><li><b>Chef format:</b> This custom format is designed to be compact and easily readable. It is the format used in CyberChef's URL, so it largely uses characters that do not have to be escaped in URL encoding, making it a little easier to understand what a CyberChef URL contains.</li><li><b>Clean JSON:</b> This JSON format uses whitespace and indentation in a way that makes the Recipe easy to read.</li><li><b>Compact JSON:</b> This is the most compact way that the Recipe can be represented in JSON.</li><li><b>Local storage:</b> Alternatively, you can enter a name into the 'Recipe name' field and save to your browser's local storage. The Recipe will then be available to load from the 'Load Recipe' pane as long as you are using the same browser profile. Be aware that if your browser profile is cleaned, you may lose this data.</li></ul>\">\n                                <i class=\"material-icons\" aria-hidden=\"true\">save</i>\n                            </button>\n                            <button type=\"button\" aria-label=\"Load recipe\" class=\"btn btn-primary bmd-btn-icon\" id=\"load\" data-toggle=\"tooltip\" title=\"Load recipe\" data-help-title=\"Loading a recipe\" data-help=\"<p>Saved recipes can be loaded using one of the following methods:</p><ul><li>If you have a CyberChef deep link, simply visit that link and the Recipe and Input will be populated automatically.</li><li>If you have a Recipe string in any of the accepted formats, paste it into the 'Load recipe' pane textbox and click 'Load'.</li><li>If you have saved a Recipe to your browser's local storage, it should be available in the dropdown menu in the 'Load recipe' pane. If it is not there, you may not be using the same browser profile, or your profile may have been cleared.</li></ul>\">\n                                <i class=\"material-icons\" aria-hidden=\"true\">folder</i>\n                            </button>\n                            <button type=\"button\" aria-label=\"Clear recipe\" class=\"btn btn-primary bmd-btn-icon\" id=\"clr-recipe\" data-toggle=\"tooltip\" title=\"Clear recipe\" data-help-title=\"Clearing a recipe\" data-help=\"Clicking the 'Clear recipe' button will remove all operations from the Recipe. It will not clear the Input, but it will trigger a Bake if Auto-bake is turned on, which will change the value of the Output.\">\n                                <i class=\"material-icons\" aria-hidden=\"true\">delete</i>\n                            </button>\n                        </span>\n                    </div>\n                    <ul id=\"rec-list\" class=\"list-area no-select\"></ul>\n\n                    <div id=\"controls\" class=\"no-select hide-on-maximised-output\">\n                        <div id=\"controls-content\">\n                            <button type=\"button\" class=\"mx-2 btn btn-lg btn-secondary\" id=\"step\" data-toggle=\"tooltip\" title=\"Step through the recipe\" data-help-title=\"Stepping through the Recipe\" data-help=\"<p>The Step button allows you to execute one operation at a time, rather than running the whole Recipe from beginning to end.</p><p>Step allows you to inspect the data at each stage of the Recipe and understand what is being passed to the next operation.</p>\">\n                                Step\n                            </button>\n\n                            <button type=\"button\" class=\"mx-2 btn btn-lg btn-success btn-raised btn-block\" id=\"bake\" data-help-title=\"Baking\" data-help=\"<p>Baking causes CyberChef to run the Recipe against your data. This involves three steps:</p><ol><li>The data in the Input is encoded into bytes using the character encoding selected in the Input status bar.</li><li>The data is run through each of the operations in the Recipe in turn with the output of one operation being fed into the next operation as its input.</li><li>The outcome of the final operation in the Recipe is decoded into Output text using the character encoding selected in the Output status bar.</li></ol><p>If there are multiple Inputs, the Bake button causes every Input to be baked simultaneously.</p>\">\n                                <img aria-hidden=\"true\" src=\"<%- require('../static/images/cook_male-32x32.png') %>\" alt=\"Chef Icon\"/>\n                                <span>Bake!</span>\n                            </button>\n\n                            <div class=\"form-group\" style=\"display: contents;\">\n                                <div class=\"mx-1 checkbox\" data-help-title=\"Auto-bake\" data-help=\"<p>When Auto-bake is turned on, CyberChef will bake the Input using the Recipe whenever anything in the Input or Recipe changes.</p>This includes:<ul><li>Adding or removing operations</li><li>Modifying operation arguments</li><li>Editing the Input</li><li>Changing the Input character encoding</li></ul><p>If there are multiple inputs, only the currently active tab will be baked when Auto-bake triggers. You can bake all inputs manually using the Bake button.</p>\">\n                                    <label id=\"auto-bake-label\">\n                                        <input type=\"checkbox\" checked=\"checked\" id=\"auto-bake\">\n                                        <br>Auto Bake\n                                    </label>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n\n                <div class=\"split split-horizontal\" id=\"IO\">\n                    <div id=\"input\" class=\"split no-select\" data-help-title=\"Input pane\" data-help=\"<p>Input data can be entered by typing it in, pasting it in, dragging it in, or using the 'Load file' or 'Load folder' buttons.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p>\">\n                        <div class=\"title no-select\">\n                            <label for=\"input-text\">Input</label>\n                            <span class=\"pane-controls\">\n                                <div class=\"io-info\" id=\"input-files-info\"></div>\n                                <button type=\"button\" aria-label=\"Add new input tab\" class=\"btn btn-primary bmd-btn-icon\" id=\"btn-new-tab\" data-toggle=\"tooltip\" title=\"Add a new input tab\" data-help-title=\"Tabs\" data-help=\"<p>New tabs can be created to support multiple Inputs. These tabs have their own associated character encodings and EOL separators, as defined in their status bars.</p><p>The deep link in the URL bar only contains information about the currently active tab.</p>\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">add</i>\n                                </button>\n                                <button type=\"button\" aria-label=\"Open folder as input\" class=\"btn btn-primary bmd-btn-icon\" id=\"btn-open-folder\" data-toggle=\"tooltip\" title=\"Open folder as input\" data-help-title=\"Opening a folder\" data-help=\"<p>You can open a whole folder into CyberChef, which will result in each file being loaded into a separate Input tab.</p><p>CyberChef can handle lots of Input files, but be aware that performance may suffer, especially if the files are large in size.</p><p>Folders can also be loaded by dragging them over the Input pane and dropping them.</p>\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">folder_open</i>\n                                    <input type=\"file\" id=\"open-folder\" style=\"display: none\" multiple directory webkitdirectory>\n                                </button>\n                                <button type=\"button\" aria-label=\"Open file as input\" class=\"btn btn-primary bmd-btn-icon\" id=\"btn-open-file\" data-toggle=\"tooltip\" title=\"Open file as input\" data-help-title=\"Opening a file\" data-help=\"<p>Files can be loaded into CyberChef individually or in groups, either using the 'Open file as input' button, or by dragging and dropping them over the Input pane.</p><p>CyberChef can handle reasonably large files (at least 500MB, depending on hardware), but performance may be impacted and some Operations will run very slowly over large Inputs.</p>\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">input</i>\n                                    <input type=\"file\" id=\"open-file\" style=\"display: none\" multiple>\n                                </button>\n                                <button type=\"button\" aria-label=\"Clear input and output\" class=\"btn btn-primary bmd-btn-icon\" id=\"clr-io\" data-toggle=\"tooltip\" title=\"Clear input and output\" data-help-title=\"Clearing the Input and Output\" data-help=\"Clicking the 'Clear input and output' button will remove all Inputs and Outputs. It will not clear the Recipe.\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">delete</i>\n                                </button>\n                                <button type=\"button\" aria-label=\"Reset pane layout\" class=\"btn btn-primary bmd-btn-icon\" id=\"reset-layout\" data-toggle=\"tooltip\" title=\"Reset pane layout\" data-help-title=\"Resetting the pane layout\" data-help=\"CyberChef's panes can be resized to suit your area of focus. This button will reset the pane sizes to their default configuration.\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">view_compact</i>\n                                </button>\n                            </span>\n                        </div>\n\n                        <div id=\"input-wrapper\" class=\"no-select\">\n                            <div id=\"input-tabs-wrapper\" style=\"display: none;\" class=\"no-select\" data-help-proxy=\"#btn-new-tab\">\n                                <span id=\"btn-previous-input-tab\" class=\"input-tab-buttons\">\n                                    &lt;\n                                </span>\n                                <span id=\"btn-input-tab-dropdown\" class=\"input-tab-buttons\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n                                    ···\n                                </span>\n                                <div class=\"dropdown-menu\" aria-labelledby=\"btn-input-tab-dropdown\">\n                                    <a id=\"btn-go-to-input-tab\" class=\"dropdown-item\">\n                                        Go to tab\n                                    </a>\n                                    <a id=\"btn-find-input-tab\" class=\"dropdown-item\">\n                                        Find tab\n                                    </a>\n                                    <a id=\"btn-close-all-tabs\" class=\"dropdown-item\">\n                                        Close all tabs\n                                    </a>\n                                </div>\n                                <span id=\"btn-next-input-tab\" class=\"input-tab-buttons\">\n                                    &gt;\n                                </span>\n                                <ul id=\"input-tabs\">\n                                </ul>\n                            </div>\n                            <div id=\"input-text\"></div>\n                        </div>\n                    </div>\n\n                    <div id=\"output\" class=\"split\" data-help-title=\"Output pane\" data-help=\"<p>This pane displays the results of the Recipe after it has processed your Input.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p><p>When copying these characters from the Output, the original byte value will be copied into your clipboard, rather than the control character picture itself.</p>\">\n                        <div class=\"title no-select\">\n                            <label for=\"output-text\">Output</label>\n                            <span class=\"pane-controls\">\n                                <div class=\"io-info\" id=\"bake-info\"></div>\n                                <button type=\"button\" class=\"btn btn-primary bmd-btn-icon\" id=\"save-all-to-file\" data-toggle=\"tooltip\" title=\"Save all outputs to a zip file\" style=\"display: none\" data-help-title=\"Saving all outputs to a zip file\" data-help=\"<p>When operating with multiple tabbed Inputs and Outputs, you can use this button to save off all the Outputs at once in a ZIP file.</p><p>Use the 'Bake' button to bake all Inputs at once.</p><p>You will be given the choice to specify the file extension for the Outputs, or you can let CyberChef attempt to detect the filetype of each one. If an Output's type is not clear, CyberChef will use the '.dat' extension.</p>\">\n                                        <i class=\"material-icons\">archive</i>\n                                    </button>\n                                <button type=\"button\" aria-label=\"save\" class=\"btn btn-primary bmd-btn-icon\" id=\"save-to-file\" data-toggle=\"tooltip\" title=\"Save output to file\" data-help-title=\"Saving output to a file\" data-help=\"The currently active Output can be saved to a file. You will be asked to specify a filename. CyberChef will attempt to guess the correct file extension based on the data. If a file type cannot be detected, the extension defaults to '.dat' but can be changed manually.\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">save</i>\n                                </button>\n                                <button type=\"button\" aria-label=\"copy content\" class=\"btn btn-primary bmd-btn-icon\" id=\"copy-output\" data-toggle=\"tooltip\" title=\"Copy raw output to the clipboard\" data-help-title=\"Copying raw output to the clipboard\" data-help=\"<p>Data can be copied from the Output in the normal way by selecting text and copying it. This button provides a quick way of copying the entire output to the clipboard without having to select it. It directly copies the raw data rather than selecting text in the Output editor. Each method will have the same result, but the button may be more efficient for large Outputs as it does not require any DOM interaction.</p>\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">content_copy</i>\n                                </button>\n                                <button type=\"button\" aria-label=\"replace input with output\" class=\"btn btn-primary bmd-btn-icon\" id=\"switch\" data-toggle=\"tooltip\" title=\"Replace input with output\" data-help-title=\"Replacing input with output\" data-help=\"<p>This button moves the currently active Output data into the currently active Input tab, overwriting whatever data was already there.</p><p>The Input character encoding and EOL sequence will be changed to match the current Output values, so that the data is interpreted correctly.</p>\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">open_in_browser</i>\n                                </button>\n                                <button type=\"button\" aria-label=\"maximise output pane\" class=\"btn btn-primary bmd-btn-icon\" id=\"maximise-output\" data-toggle=\"tooltip\" title=\"Maximise output pane\" data-help-title=\"Maximising the Output pane\" data-help=\"This button allows you to view the Output pane at maximum size, hiding the Operations, Recipe and Input panes. You can restore the pane to its normal size by clicking the same button again.\">\n                                    <i class=\"material-icons\" aria-hidden=\"true\">fullscreen</i>\n                                </button>\n                            </span>\n\n                            <button type=\"button\" class=\"btn btn-primary bmd-btn-icon hidden\" id=\"magic\" data-toggle=\"tooltip\" title=\"Magic!\" data-html=\"true\" data-help-title=\"CyberChef Magic!\" data-help=\"<p>One of CyberChef's best features is its ability to automatically detect which Operations might make more sense of your data. The Magic button appears when CyberChef has a suggested Operation for you based on the data in the Output.</p><p>Clicking on the button will add the suggested Operation(s) to your Recipe.</p><p>This background Magic detection will inspect your Output up to three levels deep and attempt to unwrap it using a range of techniques. For more control, use the 'Magic' operation, which allows you to configure greater depth and filter based on various parameters.</p><p>Further information about CyberChef Magic can be found <a href='https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic'>here</a>.</p>\">\n                                <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\">\n                                    <path d=\"M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z\" />\n                                </svg>\n                            </button>\n                            <span id=\"stale-indicator\" class=\"hidden\" data-toggle=\"tooltip\" title=\"The output is stale. The input or recipe has changed since this output was generated. Bake again to get the new value.\" data-help-title=\"Staleness indicator\" data-help=\"The staleness indicator is displayed when the Recipe or Input has changed but the Output has not yet been updated to reflect this. It is most commonly displayed when Auto-bake is turned off and indicates that you need to Bake in order to see an accurate Output.\">\n                                <i class=\"material-icons\">access_time</i>\n                            </span>\n                        </div>\n\n                        <div id=\"output-wrapper\" class=\"no-select\">\n                            <div id=\"output-tabs-wrapper\" style=\"display: none\" class=\"no-select\">\n                                <span id=\"btn-previous-output-tab\" class=\"output-tab-buttons\">\n                                    &lt;\n                                </span>\n                                <span id=\"btn-output-tab-dropdown\" class=\"output-tab-buttons\" data-toggle=\"dropdown\"  aria-haspopup=\"true\" aria-expanded=\"false\">\n                                    ···\n                                </span>\n                                <div class=\"dropdown-menu\" aria-labelledby=\"btn-input-tab-dropdown\">\n                                    <a id=\"btn-go-to-output-tab\" class=\"dropdown-item\">\n                                        Go to tab\n                                    </a>\n                                    <a id=\"btn-find-output-tab\" class=\"dropdown-item\">\n                                        Find tab\n                                    </a>\n                                </div>\n                                <span id=\"btn-next-output-tab\" class=\"output-tab-buttons\">\n                                    &gt;\n                                </span>\n                                <ul id=\"output-tabs\">\n                                </ul>\n                            </div>\n                            <div id=\"output-text\"></div>\n                            <div id=\"output-loader\">\n                                <div id=\"output-loader-animation\">\n                                    <object id=\"bombe\" data=\"<%- require('../static/images/bombe.svg') %>\" width=\"100%\" height=\"100%\" data-help-title=\"Loading animation\" data-help=\"This loading animation shows an accurate representation of how rotors moved on The Bombe, an electro-mechanical device built at Bletchley Park in 1939 by Alan Turing with refinements by Gordon Welchman in 1940. The Bombe was used by the Government Code and Cipher School (the precursor to GCHQ) to discover daily settings of Enigma machines used by the German military in World War 2.<br><br>More information can be found on <a href='https://wikipedia.org/wiki/Bombe'>Wikipedia</a>.\"></object>\n                                </div>\n                                <div class=\"loading-msg\"></div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"save-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\" data-help-proxy=\"#save\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">Save recipe</h5>\n                    </div>\n                    <div class=\"modal-body\">\n                        <div class=\"form-group\">\n                            <ul class=\"nav nav-tabs\" role=\"tablist\">\n                                <li class=\"nav-item\">\n                                    <a class=\"nav-link active\" href=\"#chef-format\" role=\"tab\" data-toggle=\"tab\">Chef format</a>\n                                </li>\n                                <li class=\"nav-item\">\n                                    <a class=\"nav-link\" href=\"#clean-json\" role=\"tab\" data-toggle=\"tab\">Clean JSON</a>\n                                </li>\n                                <li class=\"nav-item\">\n                                    <a class=\"nav-link\" href=\"#compact-json\" role=\"tab\" data-toggle=\"tab\">Compact JSON</a>\n                                </li>\n                            </ul>\n                            <div class=\"tab-content\" id=\"save-texts\">\n                                <div role=\"tabpanel\" class=\"tab-pane active\" id=\"chef-format\">\n                                    <textarea class=\"form-control\" id=\"save-text-chef\" rows=\"5\"></textarea>\n                                </div>\n                                <div role=\"tabpanel\" class=\"tab-pane\" id=\"clean-json\">\n                                    <textarea class=\"form-control\" id=\"save-text-clean\" rows=\"5\"></textarea>\n                                </div>\n                                <div role=\"tabpanel\" class=\"tab-pane\" id=\"compact-json\">\n                                    <textarea class=\"form-control\" id=\"save-text-compact\" rows=\"5\"></textarea>\n                                </div>\n                            </div>\n                        </div>\n                        <div class=\"form-group\">\n                            <label for=\"save-name\" class=\"bmd-label-floating\">Recipe name</label>\n                            <input type=\"text\" class=\"form-control\" id=\"save-name\">\n                            <span class=\"bmd-help\">Save your recipe to local storage using this name, or copy it to load later</span>\n                        </div>\n                    </div>\n                    <div class=\"modal-footer\" id=\"save-footer\">\n                        <button type=\"button\" class=\"btn btn-primary\" id=\"save-button\" data-dismiss=\"modal\">Save</button>\n                        <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Done</button>\n                    </div>\n                    <div class=\"modal-body\">\n                        <div class=\"form-group\" id=\"save-link-group\">\n                            <h6 style=\"display: inline\">Deep link</h6>\n                            <div class=\"save-link-options\">\n                                <label class=\"checkbox-inline\">\n                                    <input type=\"checkbox\" id=\"save-link-recipe-checkbox\" checked> Include recipe\n                                </label>\n                                <label class=\"checkbox-inline\">\n                                    <input type=\"checkbox\" id=\"save-link-input-checkbox\" checked> Include input\n                                </label>\n                            </div>\n                            <br><br>\n                            <a id=\"save-link\" style=\"word-wrap: break-word;\"></a>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"load-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\" data-help-proxy=\"#load\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">Load recipe</h5>\n                    </div>\n                    <div class=\"modal-body\">\n                        <div class=\"form-group\">\n                            <label for=\"load-name\" class=\"bmd-label-floating\">Recipe name</label>\n                            <select class=\"form-control\" id=\"load-name\"></select>\n                            <span class=\"bmd-help\">Load your recipe from local storage by selecting its name from the drop-down</span>\n                        </div>\n                        <div class=\"form-group\">\n                            <label for=\"load-text\" class=\"bmd-label-floating\">Recipe</label>\n                            <textarea class=\"form-control\" id=\"load-text\" rows=\"5\"></textarea>\n                            <span class=\"bmd-help\">Load your recipe by pasting it into this box</span>\n                        </div>\n                    </div>\n                    <div class=\"modal-footer\">\n                        <button type=\"button\" class=\"btn btn-primary\" id=\"load-button\" data-dismiss=\"modal\">Load</button>\n                        <button type=\"button\" class=\"btn btn-danger\" id=\"load-delete-button\">Delete</button>\n                        <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Cancel</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"options-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\" data-help-proxy=\"#options\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">Options</h5>\n                    </div>\n                    <div class=\"modal-body\" id=\"options-body\">\n                        <p style=\"font-weight: bold\">Please note that these options will persist between sessions.</p>\n\n                        <div class=\"form-group option-item\">\n                            <label for=\"theme\" class=\"bmd-label-floating\"> Theme (only supported in modern browsers)</label>\n                            <select class=\"form-control\" option=\"theme\" id=\"theme\">\n                                <option value=\"classic\">Classic</option>\n                                <option value=\"dark\">Dark</option>\n                                <option value=\"geocities\">GeoCities</option>\n                                <option value=\"solarizedDark\">Solarized Dark</option>\n                                <option value=\"solarizedLight\">Solarized Light</option>\n                            </select>\n                        </div>\n\n                        <div class=\"form-group option-item\">\n                            <label for=\"logLevel\" class=\"bmd-label-floating\">Console logging level</label>\n                            <select class=\"form-control\" option=\"logLevel\" id=\"logLevel\">\n                                <option value=\"silent\">Silent</option>\n                                <option value=\"error\">Error</option>\n                                <option value=\"warn\">Warn</option>\n                                <option value=\"info\">Info</option>\n                                <option value=\"debug\">Debug</option>\n                                <option value=\"trace\">Trace</option>\n                            </select>\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"updateUrl\">\n                                <input type=\"checkbox\" option=\"updateUrl\" id=\"updateUrl\" checked>\n                                Update the URL when the input or recipe changes\n                            </label>\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"showHighlighter\">\n                                <input type=\"checkbox\" option=\"showHighlighter\" id=\"showHighlighter\" checked>\n                                Highlight selected bytes in output and input (when possible)\n                            </label>\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"wordWrap\">\n                                <input type=\"checkbox\" option=\"wordWrap\" id=\"wordWrap\" checked>\n                                Word wrap the input and output\n                            </label>\n                        </div>\n\n                        <div class=\"checkbox option-item mb-0\">\n                            <label for=\"showErrors\">\n                                <input type=\"checkbox\" option=\"showErrors\" id=\"showErrors\" checked>\n                                Show errors from operations (recommended)\n                            </label>\n                        </div>\n\n                        <div class=\"form-group option-item\">\n                            <label for=\"errorTimeout\" class=\"bmd-label-floating\">Operation error timeout in ms (0 for never)</label>\n                            <input type=\"number\" class=\"form-control\" option=\"errorTimeout\" id=\"errorTimeout\">\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"useMetaKey\">\n                                <input type=\"checkbox\" option=\"useMetaKey\" id=\"useMetaKey\">\n                                Use meta key for keybindings (Windows ⊞/Command ⌘)\n                            </label>\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"autoMagic\">\n                                <input type=\"checkbox\" option=\"autoMagic\" id=\"autoMagic\">\n                                Attempt to detect encoded data automagically\n                            </label>\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"imagePreview\">\n                                <input type=\"checkbox\" option=\"imagePreview\" id=\"imagePreview\">\n                                Render a preview of the input if it's detected to be an image\n                            </label>\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"syncTabs\">\n                                <input type=\"checkbox\" option=\"syncTabs\" id=\"syncTabs\">\n                                Keep the current tab in sync between the input and output\n                            </label>\n                        </div>\n\n                        <div class=\"checkbox option-item\">\n                            <label for=\"showCatCount\">\n                                <input type=\"checkbox\" option=\"showCatCount\" id=\"showCatCount\">\n                                Show the number of operations in each category\n                            </label>\n                        </div>\n                    </div>\n                    <div class=\"modal-footer\">\n                        <button type=\"button\" class=\"btn btn-secondary\" id=\"reset-options\">Reset options to default</button>\n                        <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Close</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"favourites-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\" data-help-proxy=\"a[data-target='#catFavourites']\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">Edit Favourites</h5>\n                    </div>\n                    <div class=\"modal-body\" id=\"favourites-body\">\n                        <ul>\n                            <li><span style=\"font-weight: bold\">To add:</span> drag the operation over the favourites category and drop it</li>\n                            <li><span style=\"font-weight: bold\">To reorder:</span> drag up and down in the list below</li>\n                            <li><span style=\"font-weight: bold\">To remove:</span> hit the delete button or drag out of the list below</li>\n                        </ul>\n                        <br>\n                        <ul id=\"edit-favourites-list\" class=\"op-list\"></ul>\n                        <div class=\"option-item\">\n                        </div>\n                    </div>\n                    <div class=\"modal-footer\">\n                        <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\" id=\"reset-favourites\">Reset favourites to default</button>\n                        <button type=\"button\" class=\"btn btn-success\" data-dismiss=\"modal\" id=\"save-favourites\">Save</button>\n                        <button type=\"button\" class=\"btn btn-danger\" data-dismiss=\"modal\">Cancel</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"support-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\" data-help-proxy=\"#support\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">CyberChef - The Cyber Swiss Army Knife</h5>\n                    </div>\n                    <div class=\"modal-body\">\n                        <img aria-hidden=\"true\" class=\"about-img-left\" src=\"<%- require('../static/images/cyberchef-128x128.png') %>\" alt=\"CyberChef Logo\"/>\n                        <p class=\"subtext\">\n                            Version <%= htmlWebpackPlugin.options.version %><br>\n                            Compile time: <%= htmlWebpackPlugin.options.compileTime %>\n                        </p>\n                        <p>&copy; Crown Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %>.</p>\n                        <p>Released under the Apache Licence, Version 2.0.</p>\n                        <p><a href=\"https://gitter.im/gchq/CyberChef\">\n                            <img src=\"<%- require('../static/images/gitter-badge.svg') %>\">\n                        </a></p>\n\n                        <ul class=\"nav nav-tabs\" role=\"tablist\">\n                            <li class=\"nav-item\" role=\"presentation\">\n                                <a class=\"nav-link active\" href=\"#faqs\" aria-controls=\"profile\" role=\"tab\" data-toggle=\"tab\">\n                                    FAQs\n                                </a>\n                            </li>\n                            <li class=\"nav-item\" role=\"presentation\">\n                                <a class=\"nav-link\" href=\"#report-bug\" aria-controls=\"messages\" role=\"tab\" data-toggle=\"tab\">\n                                    Report a bug\n                                </a>\n                            </li>\n                            <li class=\"nav-item\" role=\"presentation\">\n                                <a class=\"nav-link\" href=\"#about\" aria-controls=\"messages\" role=\"tab\" data-toggle=\"tab\">\n                                    About\n                                </a>\n                            </li>\n                            <li class=\"nav-item\" role=\"presentation\">\n                                <a class=\"nav-link\" href=\"#keybindings\" aria-controls=\"messages\" role=\"tab\" data-toggle=\"tab\">\n                                    Keybindings\n                                </a>\n                            </li>\n                        </ul>\n                        <div class=\"tab-content\">\n                            <div role=\"tabpanel\" class=\"tab-pane active\" id=\"faqs\" data-help-title=\"FAQ pane\" data-help=\"The Frequently Asked Questions pane provides answers to some of the most common queries people have about CyberChef.\">\n                                <br>\n                                <a class=\"btn btn-primary\" data-toggle=\"collapse\" data-target=\"#faq-contextual-help\">\n                                    How does X feature work?\n                                </a>\n                                <div class=\"collapse\" id=\"faq-contextual-help\">\n                                    <p>CyberChef has a contextual help feature. Just hover your cursor over a feature that you want to learn more about and press <code>F1</code> on your keyboard to get some information about it. Give it a try by hovering over this text and pressing <code>F1</code> now!</code></p>\n                                </div>\n                                <br>\n\n                                <a class=\"btn btn-primary\" data-toggle=\"collapse\" data-target=\"#faq-examples\">\n                                    What sort of things can I do with CyberChef?\n                                </a>\n                                <div class=\"collapse\" id=\"faq-examples\">\n                                    <p>There are <span class=\"num-ops\">hundreds of</span> operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>\n                                    <ul>\n                                        <li><a href=\"#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1\">Decode a Base64-encoded string</a></li>\n                                        <li><a href=\"#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA\">Convert a date and time to a different time zone</a></li>\n                                        <li><a href=\"#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy\">Parse a Teredo IPv6 address</a></li>\n                                        <li><a href=\"#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw\">Convert data from a hexdump, then decompress</a></li>\n                                        <li><a href=\"#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ\">Decrypt and disassemble shellcode</a></li>\n                                        <li><a href=\"#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA\">Display multiple timestamps as full dates</a></li>\n                                        <li><a href=\"#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA\">Carry out different operations on data of different types</a></li>\n                                        <li><a href=\"#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ\">Use parts of the input as arguments to operations</a></li>\n                                    </ul>\n                                </div>\n                                <br>\n\n                                <a class=\"btn btn-primary\" data-toggle=\"collapse\" data-target=\"#faq-load-files\">\n                                    Can I load input directly from files?\n                                </a>\n                                <div class=\"collapse\" id=\"faq-load-files\">\n                                    <p>Yes! Just drag your file over the input box and drop it.</p>\n                                    <p>CyberChef can handle files up to around 2GB (depending on your browser), however some of the operations may take a very long time to run over this much data.</p>\n                                    <p>If the output is larger than a certain threshold (default <a href=\"#recipe=Multiply('Line%20feed')Convert_data_units('Bytes%20(B)','Mebibytes%20(MiB)')&input=MTAyNAoxMDI0\">1MiB</a>), it will be presented to you as a file available for download. Slices of the file can be viewed in the output if you need to inspect them.</p>\n                                </div>\n                                <br>\n\n                                <a class=\"btn btn-primary\" data-toggle=\"collapse\" data-target=\"#faq-fork\">\n                                    How do I run operation X over multiple inputs at once?\n                                </a>\n                                <div class=\"collapse\" id=\"faq-fork\">\n                                    <p>Maybe you have 10 timestamps that you want to parse or 16 encoded strings that all have the same key.</p>\n                                    <p>The 'Fork' operation (found in the 'Flow control' category) splits up the input line by line and runs all subsequent operations on each line separately. Each output is then displayed on a separate line. These delimiters can be changed, so if your inputs are separated by commas, you can change the split delimiter to a comma instead.</p>\n                                    <p><a href=\"#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA\">Click here</a> for an example.</p>\n                                </div>\n                                <br>\n\n                                <a class=\"btn btn-primary\" data-toggle=\"collapse\" data-target=\"#faq-magic\">\n                                    How does the 'Magic' operation work?\n                                </a>\n                                <div class=\"collapse\" id=\"faq-magic\">\n                                    <p>The 'Magic' operation uses a number of methods to detect encoded data and the operations which can be used to make sense of it. A technical description of these methods can be found <a href=\"https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic\">here</a>.</p>\n                                </div>\n                            </div>\n                            <div role=\"tabpanel\" class=\"tab-pane\" id=\"report-bug\">\n                                <br>\n                                <p>If you find a bug in CyberChef, please raise an issue in our GitHub repository explaining it in as much detail as possible. Copy and include the following information if relevant.</p>\n                                <br>\n                                <pre id=\"report-bug-info\"></pre>\n                                <br>\n                                <a class=\"btn btn-primary\" href=\"https://github.com/gchq/CyberChef/issues/new/choose\" role=\"button\">Raise issue on GitHub</a>\n                            </div>\n                            <div role=\"tabpanel\" class=\"tab-pane\" id=\"about\" style=\"padding: 20px;\">\n                                <h5><strong>What</strong></h5>\n                                <p>A simple, intuitive web app for analysing and decoding data without having to deal with complex tools or programming languages. CyberChef encourages both technical and non-technical people to explore data formats, encryption and compression.</p><br>\n\n                                <h5><strong>Why</strong></h5>\n                                <p>Digital data comes in all shapes, sizes and formats in the modern world – CyberChef helps to make sense of this data all on one easy-to-use platform.</p><br>\n\n\n                                <h5><strong>How</strong></h5>\n                                <p>The interface is designed with simplicity at its heart. Complex techniques are now as trivial as drag-and-drop. Simple functions can be combined to build up a \"recipe\", potentially resulting in complex analysis, which can be shared with other users and used with their input.</p>\n                                <p>For those comfortable writing code, CyberChef is a quick and efficient way to prototype solutions to a problem which can then be scripted once proven to work.</p><br>\n\n\n                                <h5><strong>Who</strong></h5>\n                                <p>It is expected that CyberChef will be useful for cybersecurity and antivirus companies. It should also appeal to the academic world and any individuals or companies involved in the analysis of digital data, be that software developers, analysts, mathematicians or casual puzzle solvers.</p><br>\n\n\n                                <h5><strong>Aim</strong></h5>\n                                <p>It is hoped that by releasing CyberChef through <a href=\"https://github.com/gchq/CyberChef\">GitHub</a>, contributions can be added which can be rolled out into future versions of the tool.</p><br>\n\n\n                                <br>\n                                <p>There are <span class=\"num-ops\">hundreds of</span> useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>\n                                <p>It’s the Cyber Swiss Army Knife.</p>\n                            </div>\n                            <div role=\"tabpanel\" class=\"tab-pane\" id=\"keybindings\" style=\"padding: 20px;\">\n                                <table class=\"table table-condensed table-bordered table-hover\" id=\"keybList\"></table>\n                            </div>\n                        </div>\n                    </div>\n                    <div class=\"modal-footer\">\n                        <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Close</button>\n                    </div>\n                    <a href=\"https://github.com/gchq/CyberChef\">\n                        <img aria-hidden=\"true\" style=\"position: absolute; top: 0; right: 0; border: 0;\" src=\"<%- require('../static/images/fork_me.png') %>\" alt=\"Fork me on GitHub\">\n                    </a>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"confirm-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\" id=\"confirm-title\"></h5>\n                    </div>\n                    <div class=\"modal-body\" id=\"confirm-body\"></div>\n                    <div class=\"modal-footer\">\n                        <button type=\"button\" class=\"btn btn-success\" id=\"confirm-yes\">\n                            Yes\n                        </button>\n                        <button type=\"button\" class=\"btn btn-danger\" id=\"confirm-no\" data-dismiss=\"modal\">\n                            No\n                        </button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"input-tab-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">Find Input Tab</h5>\n                    </div>\n                    <div class=\"modal-body\" id=\"input-tab-body\">\n                        <h6>Load Status</h6>\n                        <div id=\"input-find-options\">\n                            <ul id=\"input-find-options-checkboxes\">\n                                <li class=\"checkbox input-find-option\">\n                                    <label for=\"input-show-pending\">\n                                        <input type=\"checkbox\" id=\"input-show-pending\" checked=\"\">\n                                        Pending\n                                    </label>\n                                </li>\n                                <li class=\"checkbox input-find-option\">\n                                    <label for=\"input-show-loading\">\n                                        <input type=\"checkbox\" id=\"input-show-loading\" checked=\"\">\n                                        Loading\n                                    </label>\n                                </li>\n                                <li class=\"checkbox input-find-option\">\n                                    <label for=\"input-show-loaded\">\n                                        <input type=\"checkbox\" id=\"input-show-loaded\" checked=\"\">\n                                        Loaded\n                                    </label>\n                                </li>\n                            </ul>\n                        </div>\n                        <div class=\"form-group input-group\">\n                            <div class=\"toggle-string\">\n                                <label for=\"input-filter\" class=\"bmd-label-floating toggle-string\">Filter (regex)</label>\n                                <input type=\"text\" class=\"form-control toggle-string\" id=\"input-filter\">\n                            </div>\n                            <div class=\"input-group-append\">\n                                <button class=\"btn btn-secondary dropdown-toggle\" id=\"input-filter-button\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">CONTENT</button>\n                                <div class=\"dropdown-menu toggle-dropdown\">\n                                    <a class=\"dropdown-item\" id=\"input-filter-content\">Content</a>\n                                    <a class=\"dropdown-item\" id=\"input-filter-filename\">Filename</a>\n                                </div>\n                            </div>\n                        </div>\n                        <div class=\"form-group input-find-option\" id=\"input-num-results-container\">\n                            <label for=\"input-num-results\" class=\"bmd-label-floating\">Number of results</label>\n                            <input type=\"number\" class=\"form-control\" id=\"input-num-results\" value=\"20\" min=\"1\">\n                        </div>\n                        <div style=\"clear:both\"></div>\n                        <h6>Results</h6>\n                        <ul id=\"input-search-results\"></ul>\n                    </div>\n                    <div class=\"modal-footer\">\n                            <button type=\"button\" class=\"btn btn-primary\" id=\"input-filter-refresh\">Refresh</button>\n                            <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Close</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"output-tab-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">Find Output Tab</h5>\n                    </div>\n                    <div class=\"modal-body\" id=\"output-tab-body\">\n                        <h6>Bake Status</h6>\n                        <div id=\"output-find-options\">\n                            <ul id=\"output-find-options-checkboxes\">\n                                <li class=\"checkbox output-find-option\">\n                                    <label for=\"output-show-pending\">\n                                        <input type=\"checkbox\" id=\"output-show-pending\" checked=\"\">\n                                        Pending\n                                    </label>\n                                </li>\n                                <li class=\"checkbox output-find-option\">\n                                    <label for=\"output-show-baking\">\n                                        <input type=\"checkbox\" id=\"output-show-baking\" checked=\"\">\n                                        Baking\n                                    </label>\n                                </li>\n                                <li class=\"checkbox output-find-option\">\n                                    <label for=\"output-show-baked\">\n                                        <input type=\"checkbox\" id=\"output-show-baked\" checked=\"\">\n                                        Baked\n                                    </label>\n                                </li>\n                                <li class=\"checkbox output-find-option\">\n                                    <label for=\"output-show-stale\">\n                                        <input type=\"checkbox\" id=\"output-show-stale\" checked=\"\">\n                                        Stale\n                                    </label>\n                                </li>\n                                <li class=\"checkbox output-find-option\">\n                                    <label for=\"output-show-errored\">\n                                        <input type=\"checkbox\" id=\"output-show-errored\" checked=\"\">\n                                        Errored\n                                    </label>\n                                </li>\n                            </ul>\n                            <div class=\"form-group output-find-option\">\n                                <label for=\"output-content-filter\" class=\"bmd-label-floating\">Content filter (regex)</label>\n                                <input type=\"text\" class=\"form-control\" id=\"output-content-filter\">\n                            </div>\n                            <div class=\"form-group output-find-option\" id=\"output-num-results-container\">\n                                <label for=\"output-num-results\" class=\"bmd-label-floating\">Number of results</label>\n                                <input type=\"number\" class=\"form-control\" id=\"output-num-results\" value=\"20\">\n                            </div>\n                        </div>\n                        <br>\n                        <h6>Results</h6>\n                        <ul id=\"output-search-results\"></ul>\n                    </div>\n                    <div class=\"modal-footer\">\n                            <button type=\"button\" class=\"btn btn-primary\" id=\"output-filter-refresh\">Refresh</button>\n                            <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Close</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"modal fade\" id=\"download-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\" data-help-proxy=\"a[data-target='#download-modal']\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">Download CyberChef</h5>\n                    </div>\n                    <div class=\"modal-body\">\n                        <p>\n                            CyberChef runs entirely within your browser with no server-side component, meaning that your Input data and Recipe configuration are not sent anywhere, whether you use the live, official version of CyberChef or a downloaded, standalone version (assuming it is unmodified).\n                        </p>\n                        <p>\n                            There are three operations that make calls to external services, those being the 'Show on map' operation which downloads map tiles from wikimedia.org, the 'DNS over HTTPS' operation which resolves DNS requests using either Google or Cloudflare services, and the 'HTTP request' operation that calls out to the configured URL you enter. You can confirm what network requests are made using your browser's developer console (F12) and viewing the Network tab.\n                        </p>\n                        <p>\n                            If you would like to download your own standalone copy of CyberChef to run in a segregated network or where there is limited or no Internet connectivity, you can get a ZIP file containing the whole web app below. This can be run locally or hosted on a web server with no configuration required.\n                        </p>\n                        <p>\n                            Be aware that the standalone version will never update itself, meaning it will not receive bug fixes or new features until you re-download newer versions manually.\n                        </p>\n\n                        <h6>CyberChef v<%= htmlWebpackPlugin.options.version %></h6>\n                        <ul>\n                            <li>Build time: <%= htmlWebpackPlugin.options.compileTime %></li>\n                            <li>The changelog for this version can be viewed <a href=\"https://github.com/gchq/CyberChef/blob/v<%= htmlWebpackPlugin.options.version %>/CHANGELOG.md\">here</a></li>\n                            <li>&copy; Crown Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %></li>\n                            <li>Released under the Apache Licence, Version 2.0</li>\n                            <li>SHA256 hash: DOWNLOAD_HASH_PLACEHOLDER</li>\n                        </ul>\n                        <a href=\"CyberChef_v<%= htmlWebpackPlugin.options.version %>.zip\" download class=\"btn btn-outline-primary\">Download ZIP file</a>\n                    </div>\n                    <div class=\"modal-footer\">\n                        <button type=\"button\" class=\"btn btn-primary\" data-dismiss=\"modal\">Ok</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <!-- The Help modal should be last to ensure it has the highest z-index -->\n        <div class=\"modal fade\" id=\"help-modal\" tabindex=\"-1\" role=\"dialog\">\n            <div class=\"modal-dialog modal-lg\" role=\"document\">\n                <div class=\"modal-content\">\n                    <div class=\"modal-header\">\n                        <h5 class=\"modal-title\">\n                            <i class=\"material-icons modal-icon\">info_outline</i>\n                            <span id=\"help-title\"></span>\n                        </h5>\n                    </div>\n                    <div class=\"modal-body\">\n\n                    </div>\n                    <div class=\"modal-footer\">\n                        <button type=\"button\" class=\"btn btn-primary\" id=\"help-ok\" data-dismiss=\"modal\">Ok</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n    </body>\n</html>\n"
  },
  {
    "path": "src/web/index.js",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n// Styles\nimport \"./stylesheets/index.js\";\n\n// Libs\nimport \"arrive\";\nimport \"snackbarjs\";\nimport \"bootstrap-material-design/js/index\";\nimport \"bootstrap-colorpicker\";\nimport moment from \"moment-timezone\";\nimport * as CanvasComponents from \"../core/lib/CanvasComponents.mjs\";\n\n// CyberChef\nimport App from \"./App.mjs\";\nimport Categories from \"../core/config/Categories.json\" assert {type: \"json\"};\nimport OperationConfig from \"../core/config/OperationConfig.json\" assert {type: \"json\"};\n\n\n/**\n * Main function used to build the CyberChef web app.\n */\nfunction main() {\n    const defaultFavourites = [\n        \"To Base64\",\n        \"From Base64\",\n        \"To Hex\",\n        \"From Hex\",\n        \"To Hexdump\",\n        \"From Hexdump\",\n        \"URL Decode\",\n        \"Regular expression\",\n        \"Entropy\",\n        \"Fork\",\n        \"Magic\"\n    ];\n\n    const defaultOptions = {\n        updateUrl:           true,\n        showHighlighter:     true,\n        wordWrap:            true,\n        showErrors:          true,\n        errorTimeout:        4000,\n        attemptHighlight:    true,\n        theme:               \"classic\",\n        useMetaKey:          false,\n        logLevel:            \"info\",\n        autoMagic:           true,\n        imagePreview:        true,\n        syncTabs:            true,\n        showCatCount:        false,\n    };\n\n    document.removeEventListener(\"DOMContentLoaded\", main, false);\n    window.app = new App(Categories, OperationConfig, defaultFavourites, defaultOptions);\n    window.app.setup();\n}\n\nwindow.compileTime = moment.tz(COMPILE_TIME, \"DD/MM/YYYY HH:mm:ss z\", \"UTC\").valueOf();\nwindow.compileMessage = COMPILE_MSG;\n\n// Make libs available to operation outputs\nwindow.CanvasComponents = CanvasComponents;\n\ndocument.addEventListener(\"DOMContentLoaded\", main, false);\n\n"
  },
  {
    "path": "src/web/static/fonts/bmfonts/Roboto72White.fnt",
    "content": "<?xml version=\"1.0\"?>\n<font>\n  <info face=\"Roboto\" size=\"72\" bold=\"0\" italic=\"0\" charset=\"\" unicode=\"0\" stretchH=\"100\" smooth=\"1\" aa=\"1\" padding=\"1,1,1,1\" spacing=\"-2,-2\" outline=\"0\" />\n  <common lineHeight=\"85\" base=\"67\" scaleW=\"512\" scaleH=\"512\" pages=\"1\" packed=\"0\" alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\" />\n  <pages>\n    <page id=\"0\" file=\"Roboto72White.png\" />  </pages>\n  <chars count=\"98\">\n    <char id=\"0\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"66\" xadvance=\"0\" page=\"0\" chnl=\"0\" />\n    <char id=\"10\" x=\"0\" y=\"0\" width=\"70\" height=\"99\" xoffset=\"2\" yoffset=\"-11\" xadvance=\"74\" page=\"0\" chnl=\"0\" />\n    <char id=\"32\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"66\" xadvance=\"18\" page=\"0\" chnl=\"0\" />\n    <char id=\"33\" x=\"493\" y=\"99\" width=\"10\" height=\"55\" xoffset=\"5\" yoffset=\"14\" xadvance=\"19\" page=\"0\" chnl=\"0\" />\n    <char id=\"34\" x=\"446\" y=\"319\" width=\"16\" height=\"19\" xoffset=\"4\" yoffset=\"12\" xadvance=\"23\" page=\"0\" chnl=\"0\" />\n    <char id=\"35\" x=\"204\" y=\"265\" width=\"41\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"36\" x=\"269\" y=\"0\" width=\"35\" height=\"69\" xoffset=\"3\" yoffset=\"6\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"37\" x=\"31\" y=\"155\" width=\"48\" height=\"56\" xoffset=\"3\" yoffset=\"13\" xadvance=\"53\" page=\"0\" chnl=\"0\" />\n    <char id=\"38\" x=\"79\" y=\"155\" width=\"43\" height=\"56\" xoffset=\"3\" yoffset=\"13\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"39\" x=\"503\" y=\"99\" width=\"7\" height=\"19\" xoffset=\"3\" yoffset=\"12\" xadvance=\"13\" page=\"0\" chnl=\"0\" />\n    <char id=\"40\" x=\"70\" y=\"0\" width=\"21\" height=\"78\" xoffset=\"4\" yoffset=\"7\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"41\" x=\"91\" y=\"0\" width=\"22\" height=\"78\" xoffset=\"-1\" yoffset=\"7\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"42\" x=\"342\" y=\"319\" width=\"32\" height=\"32\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"31\" page=\"0\" chnl=\"0\" />\n    <char id=\"43\" x=\"242\" y=\"319\" width=\"37\" height=\"40\" xoffset=\"2\" yoffset=\"23\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"44\" x=\"433\" y=\"319\" width=\"13\" height=\"21\" xoffset=\"-1\" yoffset=\"58\" xadvance=\"14\" page=\"0\" chnl=\"0\" />\n    <char id=\"45\" x=\"27\" y=\"360\" width=\"19\" height=\"8\" xoffset=\"0\" yoffset=\"41\" xadvance=\"19\" page=\"0\" chnl=\"0\" />\n    <char id=\"46\" x=\"17\" y=\"360\" width=\"10\" height=\"11\" xoffset=\"4\" yoffset=\"58\" xadvance=\"19\" page=\"0\" chnl=\"0\" />\n    <char id=\"47\" x=\"355\" y=\"0\" width=\"30\" height=\"58\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"30\" page=\"0\" chnl=\"0\" />\n    <char id=\"48\" x=\"449\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"3\" yoffset=\"13\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"49\" x=\"474\" y=\"211\" width=\"22\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"50\" x=\"195\" y=\"155\" width=\"37\" height=\"55\" xoffset=\"2\" yoffset=\"13\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"51\" x=\"379\" y=\"99\" width=\"35\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"52\" x=\"128\" y=\"265\" width=\"39\" height=\"54\" xoffset=\"1\" yoffset=\"14\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"53\" x=\"232\" y=\"155\" width=\"35\" height=\"55\" xoffset=\"4\" yoffset=\"14\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"54\" x=\"267\" y=\"155\" width=\"35\" height=\"55\" xoffset=\"4\" yoffset=\"14\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"55\" x=\"167\" y=\"265\" width=\"37\" height=\"54\" xoffset=\"2\" yoffset=\"14\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"56\" x=\"414\" y=\"99\" width=\"35\" height=\"56\" xoffset=\"3\" yoffset=\"13\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"57\" x=\"302\" y=\"155\" width=\"34\" height=\"55\" xoffset=\"3\" yoffset=\"13\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"58\" x=\"495\" y=\"265\" width=\"10\" height=\"41\" xoffset=\"4\" yoffset=\"28\" xadvance=\"18\" page=\"0\" chnl=\"0\" />\n    <char id=\"59\" x=\"496\" y=\"211\" width=\"13\" height=\"52\" xoffset=\"0\" yoffset=\"28\" xadvance=\"15\" page=\"0\" chnl=\"0\" />\n    <char id=\"60\" x=\"279\" y=\"319\" width=\"31\" height=\"35\" xoffset=\"2\" yoffset=\"27\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"61\" x=\"402\" y=\"319\" width=\"31\" height=\"23\" xoffset=\"4\" yoffset=\"31\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"62\" x=\"310\" y=\"319\" width=\"32\" height=\"35\" xoffset=\"4\" yoffset=\"27\" xadvance=\"38\" page=\"0\" chnl=\"0\" />\n    <char id=\"63\" x=\"0\" y=\"155\" width=\"31\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"34\" page=\"0\" chnl=\"0\" />\n    <char id=\"64\" x=\"210\" y=\"0\" width=\"59\" height=\"69\" xoffset=\"3\" yoffset=\"15\" xadvance=\"65\" page=\"0\" chnl=\"0\" />\n    <char id=\"65\" x=\"336\" y=\"155\" width=\"49\" height=\"54\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"47\" page=\"0\" chnl=\"0\" />\n    <char id=\"66\" x=\"385\" y=\"155\" width=\"37\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"67\" x=\"0\" y=\"99\" width=\"42\" height=\"56\" xoffset=\"3\" yoffset=\"13\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"68\" x=\"422\" y=\"155\" width=\"39\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"47\" page=\"0\" chnl=\"0\" />\n    <char id=\"69\" x=\"461\" y=\"155\" width=\"35\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"70\" x=\"0\" y=\"211\" width=\"34\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"71\" x=\"42\" y=\"99\" width=\"42\" height=\"56\" xoffset=\"3\" yoffset=\"13\" xadvance=\"49\" page=\"0\" chnl=\"0\" />\n    <char id=\"72\" x=\"34\" y=\"211\" width=\"41\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"51\" page=\"0\" chnl=\"0\" />\n    <char id=\"73\" x=\"496\" y=\"155\" width=\"9\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"19\" page=\"0\" chnl=\"0\" />\n    <char id=\"74\" x=\"122\" y=\"155\" width=\"34\" height=\"55\" xoffset=\"1\" yoffset=\"14\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"75\" x=\"75\" y=\"211\" width=\"41\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"76\" x=\"116\" y=\"211\" width=\"33\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"77\" x=\"149\" y=\"211\" width=\"53\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"63\" page=\"0\" chnl=\"0\" />\n    <char id=\"78\" x=\"202\" y=\"211\" width=\"41\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"51\" page=\"0\" chnl=\"0\" />\n    <char id=\"79\" x=\"84\" y=\"99\" width=\"43\" height=\"56\" xoffset=\"3\" yoffset=\"13\" xadvance=\"49\" page=\"0\" chnl=\"0\" />\n    <char id=\"80\" x=\"243\" y=\"211\" width=\"39\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"81\" x=\"304\" y=\"0\" width=\"44\" height=\"64\" xoffset=\"3\" yoffset=\"13\" xadvance=\"49\" page=\"0\" chnl=\"0\" />\n    <char id=\"82\" x=\"282\" y=\"211\" width=\"40\" height=\"54\" xoffset=\"5\" yoffset=\"14\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"83\" x=\"127\" y=\"99\" width=\"39\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"84\" x=\"322\" y=\"211\" width=\"42\" height=\"54\" xoffset=\"1\" yoffset=\"14\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"85\" x=\"156\" y=\"155\" width=\"39\" height=\"55\" xoffset=\"4\" yoffset=\"14\" xadvance=\"47\" page=\"0\" chnl=\"0\" />\n    <char id=\"86\" x=\"364\" y=\"211\" width=\"47\" height=\"54\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"87\" x=\"411\" y=\"211\" width=\"63\" height=\"54\" xoffset=\"1\" yoffset=\"14\" xadvance=\"64\" page=\"0\" chnl=\"0\" />\n    <char id=\"88\" x=\"0\" y=\"265\" width=\"44\" height=\"54\" xoffset=\"1\" yoffset=\"14\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"89\" x=\"44\" y=\"265\" width=\"45\" height=\"54\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"90\" x=\"89\" y=\"265\" width=\"39\" height=\"54\" xoffset=\"2\" yoffset=\"14\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"91\" x=\"161\" y=\"0\" width=\"16\" height=\"72\" xoffset=\"4\" yoffset=\"7\" xadvance=\"19\" page=\"0\" chnl=\"0\" />\n    <char id=\"92\" x=\"385\" y=\"0\" width=\"30\" height=\"58\" xoffset=\"0\" yoffset=\"14\" xadvance=\"30\" page=\"0\" chnl=\"0\" />\n    <char id=\"93\" x=\"177\" y=\"0\" width=\"16\" height=\"72\" xoffset=\"0\" yoffset=\"7\" xadvance=\"20\" page=\"0\" chnl=\"0\" />\n    <char id=\"94\" x=\"374\" y=\"319\" width=\"28\" height=\"28\" xoffset=\"1\" yoffset=\"14\" xadvance=\"30\" page=\"0\" chnl=\"0\" />\n    <char id=\"95\" x=\"46\" y=\"360\" width=\"34\" height=\"8\" xoffset=\"0\" yoffset=\"65\" xadvance=\"34\" page=\"0\" chnl=\"0\" />\n    <char id=\"96\" x=\"0\" y=\"360\" width=\"17\" height=\"13\" xoffset=\"1\" yoffset=\"11\" xadvance=\"22\" page=\"0\" chnl=\"0\" />\n    <char id=\"97\" x=\"268\" y=\"265\" width=\"34\" height=\"42\" xoffset=\"3\" yoffset=\"27\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"98\" x=\"415\" y=\"0\" width=\"34\" height=\"57\" xoffset=\"4\" yoffset=\"12\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"99\" x=\"302\" y=\"265\" width=\"34\" height=\"42\" xoffset=\"2\" yoffset=\"27\" xadvance=\"38\" page=\"0\" chnl=\"0\" />\n    <char id=\"100\" x=\"449\" y=\"0\" width=\"34\" height=\"57\" xoffset=\"2\" yoffset=\"12\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"101\" x=\"336\" y=\"265\" width=\"34\" height=\"42\" xoffset=\"2\" yoffset=\"27\" xadvance=\"38\" page=\"0\" chnl=\"0\" />\n    <char id=\"102\" x=\"483\" y=\"0\" width=\"25\" height=\"57\" xoffset=\"1\" yoffset=\"11\" xadvance=\"26\" page=\"0\" chnl=\"0\" />\n    <char id=\"103\" x=\"166\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"2\" yoffset=\"27\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"104\" x=\"200\" y=\"99\" width=\"32\" height=\"56\" xoffset=\"4\" yoffset=\"12\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"105\" x=\"483\" y=\"99\" width=\"10\" height=\"55\" xoffset=\"4\" yoffset=\"13\" xadvance=\"18\" page=\"0\" chnl=\"0\" />\n    <char id=\"106\" x=\"193\" y=\"0\" width=\"17\" height=\"71\" xoffset=\"-4\" yoffset=\"13\" xadvance=\"17\" page=\"0\" chnl=\"0\" />\n    <char id=\"107\" x=\"232\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"4\" yoffset=\"12\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"108\" x=\"266\" y=\"99\" width=\"9\" height=\"56\" xoffset=\"4\" yoffset=\"12\" xadvance=\"17\" page=\"0\" chnl=\"0\" />\n    <char id=\"109\" x=\"439\" y=\"265\" width=\"56\" height=\"41\" xoffset=\"4\" yoffset=\"27\" xadvance=\"64\" page=\"0\" chnl=\"0\" />\n    <char id=\"110\" x=\"0\" y=\"319\" width=\"32\" height=\"41\" xoffset=\"4\" yoffset=\"27\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"111\" x=\"370\" y=\"265\" width=\"37\" height=\"42\" xoffset=\"2\" yoffset=\"27\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"112\" x=\"275\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"4\" yoffset=\"27\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"113\" x=\"309\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"2\" yoffset=\"27\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"114\" x=\"32\" y=\"319\" width=\"21\" height=\"41\" xoffset=\"4\" yoffset=\"27\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"115\" x=\"407\" y=\"265\" width=\"32\" height=\"42\" xoffset=\"2\" yoffset=\"27\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"116\" x=\"245\" y=\"265\" width=\"23\" height=\"51\" xoffset=\"0\" yoffset=\"18\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"117\" x=\"53\" y=\"319\" width=\"32\" height=\"41\" xoffset=\"4\" yoffset=\"28\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"118\" x=\"85\" y=\"319\" width=\"35\" height=\"40\" xoffset=\"0\" yoffset=\"28\" xadvance=\"35\" page=\"0\" chnl=\"0\" />\n    <char id=\"119\" x=\"120\" y=\"319\" width=\"54\" height=\"40\" xoffset=\"0\" yoffset=\"28\" xadvance=\"54\" page=\"0\" chnl=\"0\" />\n    <char id=\"120\" x=\"174\" y=\"319\" width=\"36\" height=\"40\" xoffset=\"0\" yoffset=\"28\" xadvance=\"36\" page=\"0\" chnl=\"0\" />\n    <char id=\"121\" x=\"343\" y=\"99\" width=\"36\" height=\"56\" xoffset=\"-1\" yoffset=\"28\" xadvance=\"34\" page=\"0\" chnl=\"0\" />\n    <char id=\"122\" x=\"210\" y=\"319\" width=\"32\" height=\"40\" xoffset=\"2\" yoffset=\"28\" xadvance=\"35\" page=\"0\" chnl=\"0\" />\n    <char id=\"123\" x=\"113\" y=\"0\" width=\"24\" height=\"73\" xoffset=\"1\" yoffset=\"9\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"124\" x=\"348\" y=\"0\" width=\"7\" height=\"63\" xoffset=\"5\" yoffset=\"14\" xadvance=\"17\" page=\"0\" chnl=\"0\" />\n    <char id=\"125\" x=\"137\" y=\"0\" width=\"24\" height=\"73\" xoffset=\"-1\" yoffset=\"9\" xadvance=\"24\" page=\"0\" chnl=\"0\" />\n    <char id=\"126\" x=\"462\" y=\"319\" width=\"42\" height=\"16\" xoffset=\"4\" yoffset=\"38\" xadvance=\"50\" page=\"0\" chnl=\"0\" />\n    <char id=\"127\" x=\"0\" y=\"0\" width=\"70\" height=\"99\" xoffset=\"2\" yoffset=\"-11\" xadvance=\"74\" page=\"0\" chnl=\"0\" />\n  </chars>\n  <kernings count=\"382\">\n    <kerning first=\"70\" second=\"74\" amount=\"-9\" />\n    <kerning first=\"34\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"84\" second=\"119\" amount=\"-2\" />\n    <kerning first=\"87\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"69\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"41\" amount=\"1\" />\n    <kerning first=\"76\" second=\"86\" amount=\"-6\" />\n    <kerning first=\"121\" second=\"34\" amount=\"1\" />\n    <kerning first=\"40\" second=\"86\" amount=\"1\" />\n    <kerning first=\"85\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"89\" amount=\"1\" />\n    <kerning first=\"72\" second=\"65\" amount=\"1\" />\n    <kerning first=\"104\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"114\" second=\"102\" amount=\"1\" />\n    <kerning first=\"89\" second=\"42\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"34\" amount=\"1\" />\n    <kerning first=\"84\" second=\"115\" amount=\"-4\" />\n    <kerning first=\"84\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"88\" amount=\"1\" />\n    <kerning first=\"68\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"91\" second=\"74\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"111\" amount=\"-3\" />\n    <kerning first=\"102\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"88\" amount=\"-1\" />\n    <kerning first=\"66\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"87\" amount=\"-2\" />\n    <kerning first=\"86\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"73\" second=\"88\" amount=\"1\" />\n    <kerning first=\"70\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"39\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"99\" amount=\"-3\" />\n    <kerning first=\"84\" second=\"65\" amount=\"-3\" />\n    <kerning first=\"112\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"39\" amount=\"-12\" />\n    <kerning first=\"78\" second=\"65\" amount=\"1\" />\n    <kerning first=\"88\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"121\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"85\" amount=\"-3\" />\n    <kerning first=\"114\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"125\" amount=\"1\" />\n    <kerning first=\"70\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"82\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"89\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"115\" amount=\"-3\" />\n    <kerning first=\"98\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"65\" amount=\"-6\" />\n    <kerning first=\"70\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"98\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"84\" amount=\"1\" />\n    <kerning first=\"114\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"77\" second=\"65\" amount=\"1\" />\n    <kerning first=\"120\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"121\" amount=\"1\" />\n    <kerning first=\"89\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"65\" amount=\"-5\" />\n    <kerning first=\"121\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"74\" amount=\"-8\" />\n    <kerning first=\"122\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"118\" amount=\"1\" />\n    <kerning first=\"102\" second=\"41\" amount=\"1\" />\n    <kerning first=\"122\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"38\" amount=\"-1\" />\n    <kerning first=\"81\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"46\" second=\"34\" amount=\"-6\" />\n    <kerning first=\"84\" second=\"112\" amount=\"-4\" />\n    <kerning first=\"112\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"34\" amount=\"-12\" />\n    <kerning first=\"102\" second=\"125\" amount=\"1\" />\n    <kerning first=\"39\" second=\"115\" amount=\"-3\" />\n    <kerning first=\"76\" second=\"118\" amount=\"-5\" />\n    <kerning first=\"86\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"84\" amount=\"1\" />\n    <kerning first=\"86\" second=\"65\" amount=\"-3\" />\n    <kerning first=\"87\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"67\" second=\"125\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"88\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"77\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"34\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"114\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"69\" second=\"84\" amount=\"1\" />\n    <kerning first=\"89\" second=\"46\" amount=\"-7\" />\n    <kerning first=\"97\" second=\"39\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"120\" amount=\"-3\" />\n    <kerning first=\"90\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"109\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"86\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"88\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"66\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"102\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"97\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"65\" amount=\"-2\" />\n    <kerning first=\"73\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"74\" amount=\"-3\" />\n    <kerning first=\"102\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"100\" amount=\"-3\" />\n    <kerning first=\"104\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"86\" second=\"41\" amount=\"1\" />\n    <kerning first=\"111\" second=\"34\" amount=\"-5\" />\n    <kerning first=\"40\" second=\"89\" amount=\"1\" />\n    <kerning first=\"121\" second=\"39\" amount=\"1\" />\n    <kerning first=\"68\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"88\" amount=\"-1\" />\n    <kerning first=\"98\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"110\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"119\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"119\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"118\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"84\" second=\"114\" amount=\"-3\" />\n    <kerning first=\"86\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"68\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"93\" amount=\"1\" />\n    <kerning first=\"97\" second=\"34\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"65\" amount=\"-4\" />\n    <kerning first=\"84\" second=\"118\" amount=\"-3\" />\n    <kerning first=\"76\" second=\"84\" amount=\"-10\" />\n    <kerning first=\"107\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"121\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"123\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"63\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"44\" amount=\"-7\" />\n    <kerning first=\"80\" second=\"118\" amount=\"1\" />\n    <kerning first=\"112\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"121\" amount=\"1\" />\n    <kerning first=\"118\" second=\"34\" amount=\"1\" />\n    <kerning first=\"87\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"69\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"112\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"86\" second=\"45\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"68\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"65\" second=\"89\" amount=\"-3\" />\n    <kerning first=\"69\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"47\" second=\"47\" amount=\"-8\" />\n    <kerning first=\"73\" second=\"65\" amount=\"1\" />\n    <kerning first=\"123\" second=\"74\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"102\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"116\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"77\" second=\"88\" amount=\"1\" />\n    <kerning first=\"84\" second=\"32\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"86\" amount=\"-3\" />\n    <kerning first=\"75\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"115\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"83\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"87\" amount=\"1\" />\n    <kerning first=\"114\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"116\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"122\" amount=\"-2\" />\n    <kerning first=\"68\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"32\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"117\" amount=\"-3\" />\n    <kerning first=\"74\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"46\" amount=\"-11\" />\n    <kerning first=\"89\" second=\"93\" amount=\"1\" />\n    <kerning first=\"89\" second=\"65\" amount=\"-3\" />\n    <kerning first=\"87\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"86\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"86\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"113\" amount=\"-3\" />\n    <kerning first=\"34\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"84\" amount=\"1\" />\n    <kerning first=\"84\" second=\"110\" amount=\"-4\" />\n    <kerning first=\"39\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"88\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"110\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"75\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"74\" amount=\"-7\" />\n    <kerning first=\"84\" second=\"97\" amount=\"-4\" />\n    <kerning first=\"82\" second=\"84\" amount=\"-3\" />\n    <kerning first=\"91\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"66\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"93\" amount=\"1\" />\n    <kerning first=\"75\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"81\" second=\"87\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"122\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"82\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"67\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"83\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"102\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"44\" second=\"39\" amount=\"-6\" />\n    <kerning first=\"84\" second=\"45\" amount=\"-8\" />\n    <kerning first=\"89\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"86\" amount=\"1\" />\n    <kerning first=\"87\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"98\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"88\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"34\" amount=\"1\" />\n    <kerning first=\"109\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"81\" second=\"84\" amount=\"-2\" />\n    <kerning first=\"121\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"125\" amount=\"1\" />\n    <kerning first=\"81\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"116\" amount=\"2\" />\n    <kerning first=\"114\" second=\"119\" amount=\"1\" />\n    <kerning first=\"84\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"102\" second=\"39\" amount=\"1\" />\n    <kerning first=\"44\" second=\"34\" amount=\"-6\" />\n    <kerning first=\"34\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"119\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"65\" amount=\"1\" />\n    <kerning first=\"84\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"121\" amount=\"-5\" />\n    <kerning first=\"69\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"97\" amount=\"-3\" />\n    <kerning first=\"89\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"86\" amount=\"1\" />\n    <kerning first=\"79\" second=\"88\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"79\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"41\" amount=\"1\" />\n    <kerning first=\"65\" second=\"118\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"87\" amount=\"-5\" />\n    <kerning first=\"77\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"118\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"87\" amount=\"1\" />\n    <kerning first=\"39\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"44\" amount=\"-11\" />\n    <kerning first=\"39\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"89\" amount=\"1\" />\n    <kerning first=\"84\" second=\"103\" amount=\"-3\" />\n    <kerning first=\"70\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"67\" second=\"41\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"121\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"97\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"73\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"101\" amount=\"-3\" />\n    <kerning first=\"75\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"67\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"81\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"65\" amount=\"-4\" />\n    <kerning first=\"76\" second=\"117\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"84\" amount=\"-5\" />\n    <kerning first=\"90\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"121\" amount=\"-3\" />\n    <kerning first=\"69\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"39\" amount=\"1\" />\n    <kerning first=\"84\" second=\"109\" amount=\"-4\" />\n    <kerning first=\"76\" second=\"119\" amount=\"-3\" />\n    <kerning first=\"76\" second=\"85\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"116\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"71\" amount=\"-2\" />\n    <kerning first=\"79\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"75\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"40\" second=\"87\" amount=\"1\" />\n    <kerning first=\"79\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"72\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"72\" second=\"88\" amount=\"1\" />\n    <kerning first=\"79\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"76\" second=\"89\" amount=\"-8\" />\n    <kerning first=\"68\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"69\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"39\" amount=\"1\" />\n    <kerning first=\"46\" second=\"39\" amount=\"-6\" />\n    <kerning first=\"72\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"69\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"69\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"39\" amount=\"-5\" />\n    <kerning first=\"90\" second=\"81\" amount=\"-1\" />\n  </kernings>\n</font>\n"
  },
  {
    "path": "src/web/static/fonts/bmfonts/RobotoBlack72White.fnt",
    "content": "<?xml version=\"1.0\"?>\n<font>\n  <info face=\"Roboto Black\" size=\"72\" bold=\"0\" italic=\"0\" charset=\"\" unicode=\"0\" stretchH=\"100\" smooth=\"1\" aa=\"1\" padding=\"1,1,1,1\" spacing=\"-2,-2\" outline=\"0\" />\n  <common lineHeight=\"85\" base=\"67\" scaleW=\"512\" scaleH=\"512\" pages=\"1\" packed=\"0\" alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\" />\n  <pages>\n    <page id=\"0\" file=\"RobotoBlack72White.png\" />  </pages>\n  <chars count=\"98\">\n    <char id=\"0\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"66\" xadvance=\"0\" page=\"0\" chnl=\"0\" />\n    <char id=\"10\" x=\"0\" y=\"0\" width=\"70\" height=\"99\" xoffset=\"2\" yoffset=\"-11\" xadvance=\"74\" page=\"0\" chnl=\"0\" />\n    <char id=\"32\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"66\" xadvance=\"18\" page=\"0\" chnl=\"0\" />\n    <char id=\"33\" x=\"460\" y=\"156\" width=\"15\" height=\"55\" xoffset=\"3\" yoffset=\"14\" xadvance=\"20\" page=\"0\" chnl=\"0\" />\n    <char id=\"34\" x=\"207\" y=\"362\" width=\"22\" height=\"22\" xoffset=\"0\" yoffset=\"12\" xadvance=\"23\" page=\"0\" chnl=\"0\" />\n    <char id=\"35\" x=\"404\" y=\"266\" width=\"41\" height=\"54\" xoffset=\"0\" yoffset=\"14\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"36\" x=\"220\" y=\"0\" width=\"38\" height=\"69\" xoffset=\"2\" yoffset=\"7\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"37\" x=\"167\" y=\"156\" width=\"49\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"53\" page=\"0\" chnl=\"0\" />\n    <char id=\"38\" x=\"216\" y=\"156\" width=\"48\" height=\"56\" xoffset=\"1\" yoffset=\"13\" xadvance=\"48\" page=\"0\" chnl=\"0\" />\n    <char id=\"39\" x=\"499\" y=\"320\" width=\"10\" height=\"22\" xoffset=\"1\" yoffset=\"12\" xadvance=\"11\" page=\"0\" chnl=\"0\" />\n    <char id=\"40\" x=\"70\" y=\"0\" width=\"22\" height=\"75\" xoffset=\"3\" yoffset=\"9\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"41\" x=\"92\" y=\"0\" width=\"23\" height=\"75\" xoffset=\"0\" yoffset=\"9\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"42\" x=\"103\" y=\"362\" width=\"36\" height=\"34\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"33\" page=\"0\" chnl=\"0\" />\n    <char id=\"43\" x=\"0\" y=\"362\" width=\"37\" height=\"40\" xoffset=\"1\" yoffset=\"23\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"44\" x=\"483\" y=\"320\" width=\"16\" height=\"25\" xoffset=\"0\" yoffset=\"57\" xadvance=\"20\" page=\"0\" chnl=\"0\" />\n    <char id=\"45\" x=\"308\" y=\"362\" width=\"23\" height=\"12\" xoffset=\"4\" yoffset=\"38\" xadvance=\"32\" page=\"0\" chnl=\"0\" />\n    <char id=\"46\" x=\"270\" y=\"362\" width=\"15\" height=\"15\" xoffset=\"3\" yoffset=\"54\" xadvance=\"22\" page=\"0\" chnl=\"0\" />\n    <char id=\"47\" x=\"374\" y=\"0\" width=\"29\" height=\"58\" xoffset=\"-3\" yoffset=\"14\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"48\" x=\"77\" y=\"156\" width=\"38\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"49\" x=\"299\" y=\"266\" width=\"26\" height=\"54\" xoffset=\"4\" yoffset=\"14\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"50\" x=\"383\" y=\"156\" width=\"39\" height=\"55\" xoffset=\"1\" yoffset=\"13\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"51\" x=\"434\" y=\"99\" width=\"39\" height=\"56\" xoffset=\"1\" yoffset=\"13\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"52\" x=\"325\" y=\"266\" width=\"40\" height=\"54\" xoffset=\"1\" yoffset=\"14\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"53\" x=\"422\" y=\"156\" width=\"38\" height=\"55\" xoffset=\"2\" yoffset=\"14\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"54\" x=\"0\" y=\"156\" width=\"39\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"55\" x=\"365\" y=\"266\" width=\"39\" height=\"54\" xoffset=\"1\" yoffset=\"14\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"56\" x=\"473\" y=\"99\" width=\"38\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"57\" x=\"39\" y=\"156\" width=\"38\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"58\" x=\"471\" y=\"266\" width=\"15\" height=\"43\" xoffset=\"3\" yoffset=\"26\" xadvance=\"21\" page=\"0\" chnl=\"0\" />\n    <char id=\"59\" x=\"150\" y=\"156\" width=\"17\" height=\"56\" xoffset=\"1\" yoffset=\"26\" xadvance=\"21\" page=\"0\" chnl=\"0\" />\n    <char id=\"60\" x=\"37\" y=\"362\" width=\"33\" height=\"38\" xoffset=\"1\" yoffset=\"26\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"61\" x=\"172\" y=\"362\" width=\"35\" height=\"27\" xoffset=\"3\" yoffset=\"31\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"62\" x=\"70\" y=\"362\" width=\"33\" height=\"38\" xoffset=\"3\" yoffset=\"26\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"63\" x=\"115\" y=\"156\" width=\"35\" height=\"56\" xoffset=\"0\" yoffset=\"13\" xadvance=\"36\" page=\"0\" chnl=\"0\" />\n    <char id=\"64\" x=\"258\" y=\"0\" width=\"61\" height=\"68\" xoffset=\"1\" yoffset=\"16\" xadvance=\"64\" page=\"0\" chnl=\"0\" />\n    <char id=\"65\" x=\"0\" y=\"212\" width=\"53\" height=\"54\" xoffset=\"-2\" yoffset=\"14\" xadvance=\"49\" page=\"0\" chnl=\"0\" />\n    <char id=\"66\" x=\"53\" y=\"212\" width=\"42\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"47\" page=\"0\" chnl=\"0\" />\n    <char id=\"67\" x=\"37\" y=\"99\" width=\"46\" height=\"56\" xoffset=\"1\" yoffset=\"13\" xadvance=\"47\" page=\"0\" chnl=\"0\" />\n    <char id=\"68\" x=\"95\" y=\"212\" width=\"42\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"47\" page=\"0\" chnl=\"0\" />\n    <char id=\"69\" x=\"137\" y=\"212\" width=\"38\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"70\" x=\"475\" y=\"156\" width=\"36\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"71\" x=\"83\" y=\"99\" width=\"45\" height=\"56\" xoffset=\"2\" yoffset=\"13\" xadvance=\"49\" page=\"0\" chnl=\"0\" />\n    <char id=\"72\" x=\"175\" y=\"212\" width=\"45\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"51\" page=\"0\" chnl=\"0\" />\n    <char id=\"73\" x=\"220\" y=\"212\" width=\"14\" height=\"54\" xoffset=\"4\" yoffset=\"14\" xadvance=\"22\" page=\"0\" chnl=\"0\" />\n    <char id=\"74\" x=\"264\" y=\"156\" width=\"37\" height=\"55\" xoffset=\"0\" yoffset=\"14\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"75\" x=\"234\" y=\"212\" width=\"45\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"76\" x=\"279\" y=\"212\" width=\"36\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"77\" x=\"315\" y=\"212\" width=\"58\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"63\" page=\"0\" chnl=\"0\" />\n    <char id=\"78\" x=\"373\" y=\"212\" width=\"45\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"51\" page=\"0\" chnl=\"0\" />\n    <char id=\"79\" x=\"128\" y=\"99\" width=\"47\" height=\"56\" xoffset=\"1\" yoffset=\"13\" xadvance=\"50\" page=\"0\" chnl=\"0\" />\n    <char id=\"80\" x=\"418\" y=\"212\" width=\"43\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"48\" page=\"0\" chnl=\"0\" />\n    <char id=\"81\" x=\"319\" y=\"0\" width=\"47\" height=\"65\" xoffset=\"2\" yoffset=\"13\" xadvance=\"50\" page=\"0\" chnl=\"0\" />\n    <char id=\"82\" x=\"461\" y=\"212\" width=\"43\" height=\"54\" xoffset=\"3\" yoffset=\"14\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"83\" x=\"175\" y=\"99\" width=\"42\" height=\"56\" xoffset=\"1\" yoffset=\"13\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"84\" x=\"0\" y=\"266\" width=\"45\" height=\"54\" xoffset=\"0\" yoffset=\"14\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"85\" x=\"301\" y=\"156\" width=\"42\" height=\"55\" xoffset=\"3\" yoffset=\"14\" xadvance=\"48\" page=\"0\" chnl=\"0\" />\n    <char id=\"86\" x=\"45\" y=\"266\" width=\"51\" height=\"54\" xoffset=\"-2\" yoffset=\"14\" xadvance=\"48\" page=\"0\" chnl=\"0\" />\n    <char id=\"87\" x=\"96\" y=\"266\" width=\"64\" height=\"54\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"63\" page=\"0\" chnl=\"0\" />\n    <char id=\"88\" x=\"160\" y=\"266\" width=\"48\" height=\"54\" xoffset=\"-1\" yoffset=\"14\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"89\" x=\"208\" y=\"266\" width=\"49\" height=\"54\" xoffset=\"-2\" yoffset=\"14\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"90\" x=\"257\" y=\"266\" width=\"42\" height=\"54\" xoffset=\"1\" yoffset=\"14\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"91\" x=\"115\" y=\"0\" width=\"18\" height=\"75\" xoffset=\"3\" yoffset=\"5\" xadvance=\"21\" page=\"0\" chnl=\"0\" />\n    <char id=\"92\" x=\"403\" y=\"0\" width=\"37\" height=\"58\" xoffset=\"-2\" yoffset=\"14\" xadvance=\"31\" page=\"0\" chnl=\"0\" />\n    <char id=\"93\" x=\"133\" y=\"0\" width=\"18\" height=\"75\" xoffset=\"0\" yoffset=\"5\" xadvance=\"21\" page=\"0\" chnl=\"0\" />\n    <char id=\"94\" x=\"139\" y=\"362\" width=\"33\" height=\"28\" xoffset=\"0\" yoffset=\"14\" xadvance=\"32\" page=\"0\" chnl=\"0\" />\n    <char id=\"95\" x=\"331\" y=\"362\" width=\"34\" height=\"12\" xoffset=\"-1\" yoffset=\"65\" xadvance=\"33\" page=\"0\" chnl=\"0\" />\n    <char id=\"96\" x=\"285\" y=\"362\" width=\"23\" height=\"13\" xoffset=\"0\" yoffset=\"12\" xadvance=\"24\" page=\"0\" chnl=\"0\" />\n    <char id=\"97\" x=\"0\" y=\"320\" width=\"37\" height=\"42\" xoffset=\"1\" yoffset=\"27\" xadvance=\"38\" page=\"0\" chnl=\"0\" />\n    <char id=\"98\" x=\"440\" y=\"0\" width=\"37\" height=\"57\" xoffset=\"2\" yoffset=\"12\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"99\" x=\"37\" y=\"320\" width=\"36\" height=\"42\" xoffset=\"1\" yoffset=\"27\" xadvance=\"38\" page=\"0\" chnl=\"0\" />\n    <char id=\"100\" x=\"0\" y=\"99\" width=\"37\" height=\"57\" xoffset=\"1\" yoffset=\"12\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"101\" x=\"73\" y=\"320\" width=\"38\" height=\"42\" xoffset=\"1\" yoffset=\"27\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"102\" x=\"477\" y=\"0\" width=\"28\" height=\"57\" xoffset=\"0\" yoffset=\"11\" xadvance=\"27\" page=\"0\" chnl=\"0\" />\n    <char id=\"103\" x=\"217\" y=\"99\" width=\"38\" height=\"56\" xoffset=\"1\" yoffset=\"27\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"104\" x=\"255\" y=\"99\" width=\"36\" height=\"56\" xoffset=\"2\" yoffset=\"12\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"105\" x=\"291\" y=\"99\" width=\"15\" height=\"56\" xoffset=\"2\" yoffset=\"12\" xadvance=\"19\" page=\"0\" chnl=\"0\" />\n    <char id=\"106\" x=\"197\" y=\"0\" width=\"23\" height=\"71\" xoffset=\"-5\" yoffset=\"12\" xadvance=\"20\" page=\"0\" chnl=\"0\" />\n    <char id=\"107\" x=\"306\" y=\"99\" width=\"40\" height=\"56\" xoffset=\"2\" yoffset=\"12\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"108\" x=\"346\" y=\"99\" width=\"14\" height=\"56\" xoffset=\"3\" yoffset=\"12\" xadvance=\"20\" page=\"0\" chnl=\"0\" />\n    <char id=\"109\" x=\"186\" y=\"320\" width=\"58\" height=\"41\" xoffset=\"2\" yoffset=\"27\" xadvance=\"63\" page=\"0\" chnl=\"0\" />\n    <char id=\"110\" x=\"244\" y=\"320\" width=\"36\" height=\"41\" xoffset=\"2\" yoffset=\"27\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"111\" x=\"111\" y=\"320\" width=\"39\" height=\"42\" xoffset=\"1\" yoffset=\"27\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"112\" x=\"360\" y=\"99\" width=\"37\" height=\"56\" xoffset=\"2\" yoffset=\"27\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"113\" x=\"397\" y=\"99\" width=\"37\" height=\"56\" xoffset=\"1\" yoffset=\"27\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"114\" x=\"486\" y=\"266\" width=\"25\" height=\"41\" xoffset=\"2\" yoffset=\"27\" xadvance=\"27\" page=\"0\" chnl=\"0\" />\n    <char id=\"115\" x=\"150\" y=\"320\" width=\"36\" height=\"42\" xoffset=\"0\" yoffset=\"27\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"116\" x=\"445\" y=\"266\" width=\"26\" height=\"51\" xoffset=\"0\" yoffset=\"18\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"117\" x=\"280\" y=\"320\" width=\"36\" height=\"41\" xoffset=\"2\" yoffset=\"28\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"118\" x=\"316\" y=\"320\" width=\"39\" height=\"40\" xoffset=\"-1\" yoffset=\"28\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"119\" x=\"355\" y=\"320\" width=\"54\" height=\"40\" xoffset=\"-1\" yoffset=\"28\" xadvance=\"52\" page=\"0\" chnl=\"0\" />\n    <char id=\"120\" x=\"409\" y=\"320\" width=\"40\" height=\"40\" xoffset=\"-1\" yoffset=\"28\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"121\" x=\"343\" y=\"156\" width=\"40\" height=\"55\" xoffset=\"-1\" yoffset=\"28\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"122\" x=\"449\" y=\"320\" width=\"34\" height=\"40\" xoffset=\"1\" yoffset=\"28\" xadvance=\"36\" page=\"0\" chnl=\"0\" />\n    <char id=\"123\" x=\"151\" y=\"0\" width=\"23\" height=\"72\" xoffset=\"0\" yoffset=\"9\" xadvance=\"23\" page=\"0\" chnl=\"0\" />\n    <char id=\"124\" x=\"366\" y=\"0\" width=\"8\" height=\"63\" xoffset=\"5\" yoffset=\"14\" xadvance=\"18\" page=\"0\" chnl=\"0\" />\n    <char id=\"125\" x=\"174\" y=\"0\" width=\"23\" height=\"72\" xoffset=\"0\" yoffset=\"9\" xadvance=\"23\" page=\"0\" chnl=\"0\" />\n    <char id=\"126\" x=\"229\" y=\"362\" width=\"41\" height=\"19\" xoffset=\"2\" yoffset=\"36\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"127\" x=\"0\" y=\"0\" width=\"70\" height=\"99\" xoffset=\"2\" yoffset=\"-11\" xadvance=\"74\" page=\"0\" chnl=\"0\" />\n  </chars>\n  <kernings count=\"385\">\n    <kerning first=\"84\" second=\"74\" amount=\"-8\" />\n    <kerning first=\"86\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"46\" amount=\"-9\" />\n    <kerning first=\"87\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"69\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"41\" amount=\"1\" />\n    <kerning first=\"121\" second=\"34\" amount=\"1\" />\n    <kerning first=\"40\" second=\"86\" amount=\"1\" />\n    <kerning first=\"85\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"72\" second=\"65\" amount=\"1\" />\n    <kerning first=\"114\" second=\"102\" amount=\"1\" />\n    <kerning first=\"89\" second=\"42\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"34\" amount=\"1\" />\n    <kerning first=\"75\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"85\" amount=\"-3\" />\n    <kerning first=\"77\" second=\"88\" amount=\"1\" />\n    <kerning first=\"84\" second=\"115\" amount=\"-3\" />\n    <kerning first=\"84\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"45\" amount=\"-5\" />\n    <kerning first=\"78\" second=\"88\" amount=\"1\" />\n    <kerning first=\"68\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"44\" amount=\"-5\" />\n    <kerning first=\"67\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"111\" amount=\"-7\" />\n    <kerning first=\"84\" second=\"83\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"88\" amount=\"-2\" />\n    <kerning first=\"66\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"87\" amount=\"-1\" />\n    <kerning first=\"122\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"73\" second=\"65\" amount=\"1\" />\n    <kerning first=\"70\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"73\" second=\"88\" amount=\"1\" />\n    <kerning first=\"82\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"120\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"99\" amount=\"-3\" />\n    <kerning first=\"84\" second=\"65\" amount=\"-4\" />\n    <kerning first=\"112\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"39\" amount=\"-10\" />\n    <kerning first=\"78\" second=\"65\" amount=\"1\" />\n    <kerning first=\"88\" second=\"45\" amount=\"-5\" />\n    <kerning first=\"34\" second=\"111\" amount=\"-3\" />\n    <kerning first=\"114\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"125\" amount=\"1\" />\n    <kerning first=\"70\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"89\" amount=\"1\" />\n    <kerning first=\"89\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"65\" amount=\"1\" />\n    <kerning first=\"34\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"115\" amount=\"-3\" />\n    <kerning first=\"80\" second=\"44\" amount=\"-14\" />\n    <kerning first=\"98\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"65\" amount=\"-7\" />\n    <kerning first=\"89\" second=\"116\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"46\" amount=\"-10\" />\n    <kerning first=\"98\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"84\" amount=\"1\" />\n    <kerning first=\"114\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"118\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"77\" second=\"65\" amount=\"1\" />\n    <kerning first=\"120\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"110\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"121\" amount=\"1\" />\n    <kerning first=\"89\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"65\" amount=\"-6\" />\n    <kerning first=\"121\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"118\" amount=\"1\" />\n    <kerning first=\"102\" second=\"41\" amount=\"1\" />\n    <kerning first=\"122\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"88\" amount=\"-1\" />\n    <kerning first=\"81\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"46\" second=\"34\" amount=\"-10\" />\n    <kerning first=\"84\" second=\"112\" amount=\"-3\" />\n    <kerning first=\"76\" second=\"34\" amount=\"-10\" />\n    <kerning first=\"39\" second=\"115\" amount=\"-3\" />\n    <kerning first=\"76\" second=\"118\" amount=\"-4\" />\n    <kerning first=\"86\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"84\" amount=\"1\" />\n    <kerning first=\"120\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"67\" second=\"125\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"46\" amount=\"-6\" />\n    <kerning first=\"88\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"77\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"46\" amount=\"-6\" />\n    <kerning first=\"34\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"121\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"44\" amount=\"-6\" />\n    <kerning first=\"69\" second=\"84\" amount=\"1\" />\n    <kerning first=\"89\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"97\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"120\" amount=\"-3\" />\n    <kerning first=\"90\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"86\" amount=\"1\" />\n    <kerning first=\"86\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"88\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"122\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"66\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"102\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"97\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"65\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"74\" amount=\"-3\" />\n    <kerning first=\"102\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"100\" amount=\"-3\" />\n    <kerning first=\"120\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"104\" second=\"34\" amount=\"-3\" />\n    <kerning first=\"86\" second=\"41\" amount=\"1\" />\n    <kerning first=\"111\" second=\"34\" amount=\"-3\" />\n    <kerning first=\"40\" second=\"89\" amount=\"1\" />\n    <kerning first=\"121\" second=\"39\" amount=\"1\" />\n    <kerning first=\"70\" second=\"74\" amount=\"-7\" />\n    <kerning first=\"68\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"98\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"110\" second=\"34\" amount=\"-3\" />\n    <kerning first=\"119\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"69\" second=\"102\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"44\" amount=\"-6\" />\n    <kerning first=\"84\" second=\"114\" amount=\"-2\" />\n    <kerning first=\"86\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"40\" second=\"87\" amount=\"1\" />\n    <kerning first=\"65\" second=\"109\" amount=\"-2\" />\n    <kerning first=\"68\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"93\" amount=\"1\" />\n    <kerning first=\"65\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"97\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"65\" amount=\"-4\" />\n    <kerning first=\"84\" second=\"118\" amount=\"-3\" />\n    <kerning first=\"112\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"84\" amount=\"-7\" />\n    <kerning first=\"107\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"123\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"125\" amount=\"1\" />\n    <kerning first=\"65\" second=\"63\" amount=\"-3\" />\n    <kerning first=\"89\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"80\" second=\"118\" amount=\"1\" />\n    <kerning first=\"112\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"121\" amount=\"1\" />\n    <kerning first=\"118\" second=\"34\" amount=\"1\" />\n    <kerning first=\"87\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"69\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"112\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"65\" amount=\"-3\" />\n    <kerning first=\"65\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"86\" second=\"45\" amount=\"-6\" />\n    <kerning first=\"39\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"72\" second=\"88\" amount=\"1\" />\n    <kerning first=\"68\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"65\" second=\"89\" amount=\"-5\" />\n    <kerning first=\"69\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"38\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"91\" second=\"74\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"119\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"72\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"86\" amount=\"-3\" />\n    <kerning first=\"84\" second=\"119\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"115\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"87\" amount=\"1\" />\n    <kerning first=\"114\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"116\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"122\" amount=\"-2\" />\n    <kerning first=\"68\" second=\"84\" amount=\"-3\" />\n    <kerning first=\"76\" second=\"86\" amount=\"-7\" />\n    <kerning first=\"74\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"46\" amount=\"-14\" />\n    <kerning first=\"89\" second=\"93\" amount=\"1\" />\n    <kerning first=\"89\" second=\"65\" amount=\"-5\" />\n    <kerning first=\"87\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"86\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"86\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"113\" amount=\"-3\" />\n    <kerning first=\"87\" second=\"46\" amount=\"-5\" />\n    <kerning first=\"47\" second=\"47\" amount=\"-9\" />\n    <kerning first=\"75\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"84\" amount=\"1\" />\n    <kerning first=\"84\" second=\"110\" amount=\"-3\" />\n    <kerning first=\"39\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"88\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"110\" second=\"39\" amount=\"-3\" />\n    <kerning first=\"88\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"74\" amount=\"-6\" />\n    <kerning first=\"84\" second=\"97\" amount=\"-6\" />\n    <kerning first=\"82\" second=\"84\" amount=\"-2\" />\n    <kerning first=\"91\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"66\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"93\" amount=\"1\" />\n    <kerning first=\"75\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"44\" second=\"39\" amount=\"-10\" />\n    <kerning first=\"111\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"81\" second=\"87\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"122\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"82\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"102\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"44\" amount=\"-10\" />\n    <kerning first=\"104\" second=\"39\" amount=\"-3\" />\n    <kerning first=\"84\" second=\"45\" amount=\"-10\" />\n    <kerning first=\"89\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"109\" second=\"34\" amount=\"-3\" />\n    <kerning first=\"84\" second=\"86\" amount=\"1\" />\n    <kerning first=\"87\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"32\" second=\"84\" amount=\"-2\" />\n    <kerning first=\"98\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"116\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"34\" amount=\"1\" />\n    <kerning first=\"109\" second=\"39\" amount=\"-3\" />\n    <kerning first=\"81\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"121\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"125\" amount=\"1\" />\n    <kerning first=\"81\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"116\" amount=\"2\" />\n    <kerning first=\"114\" second=\"119\" amount=\"1\" />\n    <kerning first=\"84\" second=\"44\" amount=\"-9\" />\n    <kerning first=\"102\" second=\"39\" amount=\"1\" />\n    <kerning first=\"44\" second=\"34\" amount=\"-10\" />\n    <kerning first=\"34\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"101\" amount=\"-3\" />\n    <kerning first=\"75\" second=\"119\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"121\" amount=\"-4\" />\n    <kerning first=\"69\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"88\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"79\" amount=\"-2\" />\n    <kerning first=\"34\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"41\" amount=\"1\" />\n    <kerning first=\"75\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"87\" amount=\"-3\" />\n    <kerning first=\"77\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"90\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"87\" amount=\"1\" />\n    <kerning first=\"39\" second=\"111\" amount=\"-3\" />\n    <kerning first=\"39\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"83\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"89\" amount=\"1\" />\n    <kerning first=\"84\" second=\"103\" amount=\"-3\" />\n    <kerning first=\"70\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"67\" second=\"41\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"121\" second=\"44\" amount=\"-6\" />\n    <kerning first=\"97\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"73\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"121\" second=\"46\" amount=\"-6\" />\n    <kerning first=\"75\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"112\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"67\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"81\" amount=\"-2\" />\n    <kerning first=\"102\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"65\" amount=\"-4\" />\n    <kerning first=\"65\" second=\"84\" amount=\"-4\" />\n    <kerning first=\"90\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"121\" amount=\"-3\" />\n    <kerning first=\"114\" second=\"39\" amount=\"1\" />\n    <kerning first=\"84\" second=\"109\" amount=\"-3\" />\n    <kerning first=\"123\" second=\"74\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"119\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"117\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"71\" amount=\"-2\" />\n    <kerning first=\"79\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"75\" second=\"45\" amount=\"-6\" />\n    <kerning first=\"79\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"76\" second=\"89\" amount=\"-10\" />\n    <kerning first=\"68\" second=\"65\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"84\" amount=\"-3\" />\n    <kerning first=\"87\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"32\" amount=\"-2\" />\n    <kerning first=\"90\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"69\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"39\" amount=\"1\" />\n    <kerning first=\"46\" second=\"39\" amount=\"-10\" />\n    <kerning first=\"72\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"69\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"73\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"39\" amount=\"-3\" />\n    <kerning first=\"90\" second=\"81\" amount=\"-1\" />\n  </kernings>\n</font>\n"
  },
  {
    "path": "src/web/static/fonts/bmfonts/RobotoMono72White.fnt",
    "content": "<?xml version=\"1.0\"?>\n<font>\n  <info face=\"Roboto Mono\" size=\"72\" bold=\"0\" italic=\"0\" charset=\"\" unicode=\"0\" stretchH=\"100\" smooth=\"1\" aa=\"1\" padding=\"1,1,1,1\" spacing=\"-2,-2\" outline=\"0\" />\n  <common lineHeight=\"96\" base=\"76\" scaleW=\"512\" scaleH=\"512\" pages=\"1\" packed=\"0\" alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\" />\n  <pages>\n    <page id=\"0\" file=\"RobotoMono72White.png\" />  </pages>\n  <chars count=\"98\">\n    <char id=\"0\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"75\" xadvance=\"0\" page=\"0\" chnl=\"0\" />\n    <char id=\"10\" x=\"0\" y=\"0\" width=\"45\" height=\"99\" xoffset=\"-1\" yoffset=\"-2\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"32\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"75\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"33\" x=\"498\" y=\"99\" width=\"10\" height=\"55\" xoffset=\"16\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"34\" x=\"434\" y=\"319\" width=\"20\" height=\"19\" xoffset=\"11\" yoffset=\"21\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"35\" x=\"175\" y=\"265\" width=\"41\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"36\" x=\"200\" y=\"0\" width=\"35\" height=\"69\" xoffset=\"5\" yoffset=\"15\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"37\" x=\"0\" y=\"155\" width=\"42\" height=\"56\" xoffset=\"1\" yoffset=\"22\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"38\" x=\"42\" y=\"155\" width=\"41\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"39\" x=\"502\" y=\"211\" width=\"7\" height=\"19\" xoffset=\"16\" yoffset=\"21\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"40\" x=\"45\" y=\"0\" width=\"21\" height=\"78\" xoffset=\"12\" yoffset=\"16\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"41\" x=\"66\" y=\"0\" width=\"22\" height=\"78\" xoffset=\"9\" yoffset=\"16\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"42\" x=\"256\" y=\"319\" width=\"37\" height=\"37\" xoffset=\"4\" yoffset=\"32\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"43\" x=\"219\" y=\"319\" width=\"37\" height=\"40\" xoffset=\"3\" yoffset=\"32\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"44\" x=\"421\" y=\"319\" width=\"13\" height=\"22\" xoffset=\"11\" yoffset=\"67\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"45\" x=\"17\" y=\"360\" width=\"29\" height=\"8\" xoffset=\"7\" yoffset=\"49\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"46\" x=\"496\" y=\"319\" width=\"12\" height=\"13\" xoffset=\"16\" yoffset=\"65\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"47\" x=\"319\" y=\"0\" width=\"31\" height=\"58\" xoffset=\"7\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"48\" x=\"431\" y=\"99\" width=\"35\" height=\"56\" xoffset=\"4\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"49\" x=\"36\" y=\"265\" width=\"23\" height=\"54\" xoffset=\"6\" yoffset=\"23\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"50\" x=\"189\" y=\"155\" width=\"37\" height=\"55\" xoffset=\"2\" yoffset=\"22\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"51\" x=\"361\" y=\"99\" width=\"35\" height=\"56\" xoffset=\"2\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"52\" x=\"59\" y=\"265\" width=\"39\" height=\"54\" xoffset=\"2\" yoffset=\"23\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"53\" x=\"226\" y=\"155\" width=\"35\" height=\"55\" xoffset=\"5\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"54\" x=\"261\" y=\"155\" width=\"35\" height=\"55\" xoffset=\"4\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"55\" x=\"98\" y=\"265\" width=\"37\" height=\"54\" xoffset=\"3\" yoffset=\"23\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"56\" x=\"396\" y=\"99\" width=\"35\" height=\"56\" xoffset=\"5\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"57\" x=\"296\" y=\"155\" width=\"34\" height=\"55\" xoffset=\"4\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"58\" x=\"490\" y=\"211\" width=\"12\" height=\"43\" xoffset=\"18\" yoffset=\"35\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"59\" x=\"486\" y=\"0\" width=\"14\" height=\"55\" xoffset=\"16\" yoffset=\"35\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"60\" x=\"293\" y=\"319\" width=\"32\" height=\"35\" xoffset=\"5\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"61\" x=\"388\" y=\"319\" width=\"33\" height=\"23\" xoffset=\"5\" yoffset=\"41\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"62\" x=\"325\" y=\"319\" width=\"33\" height=\"35\" xoffset=\"5\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"63\" x=\"466\" y=\"99\" width=\"32\" height=\"56\" xoffset=\"6\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"64\" x=\"135\" y=\"265\" width=\"40\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"65\" x=\"330\" y=\"155\" width=\"42\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"66\" x=\"372\" y=\"155\" width=\"35\" height=\"54\" xoffset=\"5\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"67\" x=\"448\" y=\"0\" width=\"38\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"68\" x=\"407\" y=\"155\" width=\"37\" height=\"54\" xoffset=\"4\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"69\" x=\"444\" y=\"155\" width=\"34\" height=\"54\" xoffset=\"5\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"70\" x=\"0\" y=\"211\" width=\"34\" height=\"54\" xoffset=\"6\" yoffset=\"23\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"71\" x=\"0\" y=\"99\" width=\"38\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"72\" x=\"34\" y=\"211\" width=\"36\" height=\"54\" xoffset=\"4\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"73\" x=\"478\" y=\"155\" width=\"33\" height=\"54\" xoffset=\"5\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"74\" x=\"83\" y=\"155\" width=\"36\" height=\"55\" xoffset=\"2\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"75\" x=\"70\" y=\"211\" width=\"38\" height=\"54\" xoffset=\"5\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"76\" x=\"108\" y=\"211\" width=\"34\" height=\"54\" xoffset=\"6\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"77\" x=\"142\" y=\"211\" width=\"36\" height=\"54\" xoffset=\"4\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"78\" x=\"178\" y=\"211\" width=\"35\" height=\"54\" xoffset=\"4\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"79\" x=\"38\" y=\"99\" width=\"38\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"80\" x=\"213\" y=\"211\" width=\"36\" height=\"54\" xoffset=\"6\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"81\" x=\"242\" y=\"0\" width=\"40\" height=\"64\" xoffset=\"2\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"82\" x=\"249\" y=\"211\" width=\"36\" height=\"54\" xoffset=\"5\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"83\" x=\"76\" y=\"99\" width=\"38\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"84\" x=\"285\" y=\"211\" width=\"40\" height=\"54\" xoffset=\"2\" yoffset=\"23\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"85\" x=\"119\" y=\"155\" width=\"36\" height=\"55\" xoffset=\"4\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"86\" x=\"325\" y=\"211\" width=\"41\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"87\" x=\"366\" y=\"211\" width=\"42\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"88\" x=\"408\" y=\"211\" width=\"41\" height=\"54\" xoffset=\"2\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"89\" x=\"449\" y=\"211\" width=\"41\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"90\" x=\"0\" y=\"265\" width=\"36\" height=\"54\" xoffset=\"3\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"91\" x=\"88\" y=\"0\" width=\"16\" height=\"72\" xoffset=\"14\" yoffset=\"16\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"92\" x=\"350\" y=\"0\" width=\"30\" height=\"58\" xoffset=\"7\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"93\" x=\"104\" y=\"0\" width=\"17\" height=\"72\" xoffset=\"13\" yoffset=\"16\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"94\" x=\"358\" y=\"319\" width=\"30\" height=\"30\" xoffset=\"7\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"95\" x=\"46\" y=\"360\" width=\"34\" height=\"8\" xoffset=\"4\" yoffset=\"74\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"96\" x=\"0\" y=\"360\" width=\"17\" height=\"12\" xoffset=\"13\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"97\" x=\"251\" y=\"265\" width=\"35\" height=\"42\" xoffset=\"4\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"98\" x=\"380\" y=\"0\" width=\"34\" height=\"57\" xoffset=\"5\" yoffset=\"21\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"99\" x=\"286\" y=\"265\" width=\"35\" height=\"42\" xoffset=\"4\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"100\" x=\"414\" y=\"0\" width=\"34\" height=\"57\" xoffset=\"4\" yoffset=\"21\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"101\" x=\"321\" y=\"265\" width=\"36\" height=\"42\" xoffset=\"4\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"102\" x=\"282\" y=\"0\" width=\"37\" height=\"58\" xoffset=\"4\" yoffset=\"19\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"103\" x=\"114\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"4\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"104\" x=\"148\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"5\" yoffset=\"21\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"105\" x=\"155\" y=\"155\" width=\"34\" height=\"55\" xoffset=\"6\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"106\" x=\"121\" y=\"0\" width=\"26\" height=\"71\" xoffset=\"6\" yoffset=\"22\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"107\" x=\"182\" y=\"99\" width=\"36\" height=\"56\" xoffset=\"5\" yoffset=\"21\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"108\" x=\"218\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"6\" yoffset=\"21\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"109\" x=\"428\" y=\"265\" width=\"39\" height=\"41\" xoffset=\"2\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"110\" x=\"467\" y=\"265\" width=\"34\" height=\"41\" xoffset=\"5\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"111\" x=\"357\" y=\"265\" width=\"37\" height=\"42\" xoffset=\"3\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"112\" x=\"252\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"5\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"113\" x=\"286\" y=\"99\" width=\"34\" height=\"56\" xoffset=\"4\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"114\" x=\"0\" y=\"319\" width=\"29\" height=\"41\" xoffset=\"11\" yoffset=\"36\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"115\" x=\"394\" y=\"265\" width=\"34\" height=\"42\" xoffset=\"5\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"116\" x=\"216\" y=\"265\" width=\"35\" height=\"51\" xoffset=\"4\" yoffset=\"27\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"117\" x=\"29\" y=\"319\" width=\"33\" height=\"41\" xoffset=\"5\" yoffset=\"37\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"118\" x=\"62\" y=\"319\" width=\"39\" height=\"40\" xoffset=\"2\" yoffset=\"37\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"119\" x=\"101\" y=\"319\" width=\"43\" height=\"40\" xoffset=\"0\" yoffset=\"37\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"120\" x=\"144\" y=\"319\" width=\"40\" height=\"40\" xoffset=\"2\" yoffset=\"37\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"121\" x=\"320\" y=\"99\" width=\"41\" height=\"56\" xoffset=\"1\" yoffset=\"37\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"122\" x=\"184\" y=\"319\" width=\"35\" height=\"40\" xoffset=\"5\" yoffset=\"37\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"123\" x=\"147\" y=\"0\" width=\"26\" height=\"71\" xoffset=\"10\" yoffset=\"19\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"124\" x=\"235\" y=\"0\" width=\"7\" height=\"68\" xoffset=\"18\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"125\" x=\"173\" y=\"0\" width=\"27\" height=\"71\" xoffset=\"10\" yoffset=\"19\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"126\" x=\"454\" y=\"319\" width=\"42\" height=\"16\" xoffset=\"1\" yoffset=\"47\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"127\" x=\"0\" y=\"0\" width=\"45\" height=\"99\" xoffset=\"-1\" yoffset=\"-2\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n  </chars>\n  <kernings count=\"1\">\n    <kerning first=\"0\" second=\"0\" amount=\"0\" />\n  </kernings>\n</font>\n"
  },
  {
    "path": "src/web/static/fonts/bmfonts/RobotoSlab72White.fnt",
    "content": "<?xml version=\"1.0\"?>\n<font>\n  <info face=\"Roboto Slab Regular\" size=\"72\" bold=\"0\" italic=\"0\" charset=\"\" unicode=\"0\" stretchH=\"100\" smooth=\"1\" aa=\"1\" padding=\"1,1,1,1\" spacing=\"-2,-2\" outline=\"0\" />\n  <common lineHeight=\"96\" base=\"76\" scaleW=\"512\" scaleH=\"512\" pages=\"1\" packed=\"0\" alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\" />\n  <pages>\n    <page id=\"0\" file=\"RobotoSlab72White.png\" />  </pages>\n  <chars count=\"98\">\n    <char id=\"0\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"75\" xadvance=\"0\" page=\"0\" chnl=\"0\" />\n    <char id=\"10\" x=\"0\" y=\"0\" width=\"70\" height=\"98\" xoffset=\"0\" yoffset=\"-1\" xadvance=\"70\" page=\"0\" chnl=\"0\" />\n    <char id=\"32\" x=\"0\" y=\"0\" width=\"0\" height=\"0\" xoffset=\"-1\" yoffset=\"75\" xadvance=\"18\" page=\"0\" chnl=\"0\" />\n    <char id=\"33\" x=\"497\" y=\"156\" width=\"9\" height=\"54\" xoffset=\"4\" yoffset=\"23\" xadvance=\"17\" page=\"0\" chnl=\"0\" />\n    <char id=\"34\" x=\"191\" y=\"362\" width=\"19\" height=\"20\" xoffset=\"5\" yoffset=\"20\" xadvance=\"28\" page=\"0\" chnl=\"0\" />\n    <char id=\"35\" x=\"406\" y=\"266\" width=\"41\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"36\" x=\"212\" y=\"0\" width=\"35\" height=\"69\" xoffset=\"2\" yoffset=\"15\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"37\" x=\"174\" y=\"156\" width=\"48\" height=\"56\" xoffset=\"2\" yoffset=\"22\" xadvance=\"52\" page=\"0\" chnl=\"0\" />\n    <char id=\"38\" x=\"222\" y=\"156\" width=\"44\" height=\"56\" xoffset=\"2\" yoffset=\"22\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"39\" x=\"210\" y=\"362\" width=\"8\" height=\"20\" xoffset=\"5\" yoffset=\"20\" xadvance=\"17\" page=\"0\" chnl=\"0\" />\n    <char id=\"40\" x=\"70\" y=\"0\" width=\"21\" height=\"77\" xoffset=\"3\" yoffset=\"17\" xadvance=\"23\" page=\"0\" chnl=\"0\" />\n    <char id=\"41\" x=\"91\" y=\"0\" width=\"21\" height=\"77\" xoffset=\"-1\" yoffset=\"17\" xadvance=\"23\" page=\"0\" chnl=\"0\" />\n    <char id=\"42\" x=\"100\" y=\"362\" width=\"31\" height=\"33\" xoffset=\"1\" yoffset=\"23\" xadvance=\"33\" page=\"0\" chnl=\"0\" />\n    <char id=\"43\" x=\"0\" y=\"362\" width=\"37\" height=\"40\" xoffset=\"2\" yoffset=\"32\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"44\" x=\"492\" y=\"320\" width=\"13\" height=\"21\" xoffset=\"-1\" yoffset=\"67\" xadvance=\"14\" page=\"0\" chnl=\"0\" />\n    <char id=\"45\" x=\"287\" y=\"362\" width=\"19\" height=\"8\" xoffset=\"4\" yoffset=\"50\" xadvance=\"27\" page=\"0\" chnl=\"0\" />\n    <char id=\"46\" x=\"278\" y=\"362\" width=\"9\" height=\"9\" xoffset=\"4\" yoffset=\"68\" xadvance=\"17\" page=\"0\" chnl=\"0\" />\n    <char id=\"47\" x=\"470\" y=\"0\" width=\"30\" height=\"58\" xoffset=\"-1\" yoffset=\"23\" xadvance=\"29\" page=\"0\" chnl=\"0\" />\n    <char id=\"48\" x=\"139\" y=\"156\" width=\"35\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"49\" x=\"305\" y=\"266\" width=\"25\" height=\"54\" xoffset=\"3\" yoffset=\"23\" xadvance=\"30\" page=\"0\" chnl=\"0\" />\n    <char id=\"50\" x=\"357\" y=\"156\" width=\"36\" height=\"55\" xoffset=\"2\" yoffset=\"22\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"51\" x=\"0\" y=\"156\" width=\"34\" height=\"56\" xoffset=\"2\" yoffset=\"22\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"52\" x=\"330\" y=\"266\" width=\"39\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"53\" x=\"393\" y=\"156\" width=\"33\" height=\"55\" xoffset=\"2\" yoffset=\"23\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"54\" x=\"34\" y=\"156\" width=\"35\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"55\" x=\"369\" y=\"266\" width=\"37\" height=\"54\" xoffset=\"2\" yoffset=\"23\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"56\" x=\"69\" y=\"156\" width=\"35\" height=\"56\" xoffset=\"2\" yoffset=\"22\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"57\" x=\"104\" y=\"156\" width=\"35\" height=\"56\" xoffset=\"2\" yoffset=\"22\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"58\" x=\"500\" y=\"0\" width=\"9\" height=\"40\" xoffset=\"4\" yoffset=\"37\" xadvance=\"15\" page=\"0\" chnl=\"0\" />\n    <char id=\"59\" x=\"447\" y=\"266\" width=\"13\" height=\"52\" xoffset=\"0\" yoffset=\"37\" xadvance=\"15\" page=\"0\" chnl=\"0\" />\n    <char id=\"60\" x=\"37\" y=\"362\" width=\"31\" height=\"35\" xoffset=\"2\" yoffset=\"39\" xadvance=\"36\" page=\"0\" chnl=\"0\" />\n    <char id=\"61\" x=\"160\" y=\"362\" width=\"31\" height=\"23\" xoffset=\"4\" yoffset=\"40\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"62\" x=\"68\" y=\"362\" width=\"32\" height=\"35\" xoffset=\"3\" yoffset=\"39\" xadvance=\"37\" page=\"0\" chnl=\"0\" />\n    <char id=\"63\" x=\"480\" y=\"98\" width=\"31\" height=\"55\" xoffset=\"1\" yoffset=\"22\" xadvance=\"33\" page=\"0\" chnl=\"0\" />\n    <char id=\"64\" x=\"247\" y=\"0\" width=\"60\" height=\"68\" xoffset=\"1\" yoffset=\"25\" xadvance=\"64\" page=\"0\" chnl=\"0\" />\n    <char id=\"65\" x=\"426\" y=\"156\" width=\"51\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"53\" page=\"0\" chnl=\"0\" />\n    <char id=\"66\" x=\"0\" y=\"212\" width=\"44\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"47\" page=\"0\" chnl=\"0\" />\n    <char id=\"67\" x=\"191\" y=\"98\" width=\"42\" height=\"56\" xoffset=\"1\" yoffset=\"22\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"68\" x=\"44\" y=\"212\" width=\"46\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"50\" page=\"0\" chnl=\"0\" />\n    <char id=\"69\" x=\"90\" y=\"212\" width=\"42\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"70\" x=\"132\" y=\"212\" width=\"42\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"71\" x=\"233\" y=\"98\" width=\"43\" height=\"56\" xoffset=\"1\" yoffset=\"22\" xadvance=\"49\" page=\"0\" chnl=\"0\" />\n    <char id=\"72\" x=\"174\" y=\"212\" width=\"52\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"55\" page=\"0\" chnl=\"0\" />\n    <char id=\"73\" x=\"477\" y=\"156\" width=\"20\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"22\" page=\"0\" chnl=\"0\" />\n    <char id=\"74\" x=\"266\" y=\"156\" width=\"39\" height=\"55\" xoffset=\"1\" yoffset=\"23\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"75\" x=\"226\" y=\"212\" width=\"48\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"50\" page=\"0\" chnl=\"0\" />\n    <char id=\"76\" x=\"274\" y=\"212\" width=\"39\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"77\" x=\"313\" y=\"212\" width=\"64\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"66\" page=\"0\" chnl=\"0\" />\n    <char id=\"78\" x=\"377\" y=\"212\" width=\"52\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"54\" page=\"0\" chnl=\"0\" />\n    <char id=\"79\" x=\"276\" y=\"98\" width=\"47\" height=\"56\" xoffset=\"2\" yoffset=\"22\" xadvance=\"51\" page=\"0\" chnl=\"0\" />\n    <char id=\"80\" x=\"429\" y=\"212\" width=\"43\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"81\" x=\"307\" y=\"0\" width=\"48\" height=\"64\" xoffset=\"2\" yoffset=\"22\" xadvance=\"51\" page=\"0\" chnl=\"0\" />\n    <char id=\"82\" x=\"0\" y=\"266\" width=\"46\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"48\" page=\"0\" chnl=\"0\" />\n    <char id=\"83\" x=\"323\" y=\"98\" width=\"38\" height=\"56\" xoffset=\"3\" yoffset=\"22\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"84\" x=\"46\" y=\"266\" width=\"45\" height=\"54\" xoffset=\"0\" yoffset=\"23\" xadvance=\"45\" page=\"0\" chnl=\"0\" />\n    <char id=\"85\" x=\"305\" y=\"156\" width=\"52\" height=\"55\" xoffset=\"1\" yoffset=\"23\" xadvance=\"54\" page=\"0\" chnl=\"0\" />\n    <char id=\"86\" x=\"91\" y=\"266\" width=\"50\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"52\" page=\"0\" chnl=\"0\" />\n    <char id=\"87\" x=\"141\" y=\"266\" width=\"67\" height=\"54\" xoffset=\"0\" yoffset=\"23\" xadvance=\"67\" page=\"0\" chnl=\"0\" />\n    <char id=\"88\" x=\"208\" y=\"266\" width=\"49\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"51\" page=\"0\" chnl=\"0\" />\n    <char id=\"89\" x=\"257\" y=\"266\" width=\"48\" height=\"54\" xoffset=\"1\" yoffset=\"23\" xadvance=\"50\" page=\"0\" chnl=\"0\" />\n    <char id=\"90\" x=\"472\" y=\"212\" width=\"38\" height=\"54\" xoffset=\"2\" yoffset=\"23\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"91\" x=\"180\" y=\"0\" width=\"16\" height=\"72\" xoffset=\"5\" yoffset=\"16\" xadvance=\"21\" page=\"0\" chnl=\"0\" />\n    <char id=\"92\" x=\"0\" y=\"98\" width=\"31\" height=\"58\" xoffset=\"0\" yoffset=\"23\" xadvance=\"30\" page=\"0\" chnl=\"0\" />\n    <char id=\"93\" x=\"196\" y=\"0\" width=\"16\" height=\"72\" xoffset=\"-1\" yoffset=\"16\" xadvance=\"19\" page=\"0\" chnl=\"0\" />\n    <char id=\"94\" x=\"131\" y=\"362\" width=\"29\" height=\"28\" xoffset=\"1\" yoffset=\"23\" xadvance=\"30\" page=\"0\" chnl=\"0\" />\n    <char id=\"95\" x=\"306\" y=\"362\" width=\"34\" height=\"8\" xoffset=\"3\" yoffset=\"74\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"96\" x=\"260\" y=\"362\" width=\"18\" height=\"12\" xoffset=\"1\" yoffset=\"22\" xadvance=\"20\" page=\"0\" chnl=\"0\" />\n    <char id=\"97\" x=\"0\" y=\"320\" width=\"36\" height=\"42\" xoffset=\"3\" yoffset=\"36\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"98\" x=\"363\" y=\"0\" width=\"41\" height=\"58\" xoffset=\"-2\" yoffset=\"20\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"99\" x=\"36\" y=\"320\" width=\"34\" height=\"42\" xoffset=\"2\" yoffset=\"36\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"100\" x=\"404\" y=\"0\" width=\"40\" height=\"58\" xoffset=\"2\" yoffset=\"20\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"101\" x=\"70\" y=\"320\" width=\"34\" height=\"42\" xoffset=\"2\" yoffset=\"36\" xadvance=\"39\" page=\"0\" chnl=\"0\" />\n    <char id=\"102\" x=\"444\" y=\"0\" width=\"26\" height=\"58\" xoffset=\"1\" yoffset=\"19\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"103\" x=\"31\" y=\"98\" width=\"34\" height=\"57\" xoffset=\"2\" yoffset=\"36\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"104\" x=\"65\" y=\"98\" width=\"44\" height=\"57\" xoffset=\"1\" yoffset=\"20\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"105\" x=\"109\" y=\"98\" width=\"20\" height=\"57\" xoffset=\"2\" yoffset=\"20\" xadvance=\"23\" page=\"0\" chnl=\"0\" />\n    <char id=\"106\" x=\"112\" y=\"0\" width=\"18\" height=\"73\" xoffset=\"-2\" yoffset=\"20\" xadvance=\"20\" page=\"0\" chnl=\"0\" />\n    <char id=\"107\" x=\"129\" y=\"98\" width=\"42\" height=\"57\" xoffset=\"1\" yoffset=\"20\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"108\" x=\"171\" y=\"98\" width=\"20\" height=\"57\" xoffset=\"1\" yoffset=\"20\" xadvance=\"22\" page=\"0\" chnl=\"0\" />\n    <char id=\"109\" x=\"171\" y=\"320\" width=\"66\" height=\"41\" xoffset=\"1\" yoffset=\"36\" xadvance=\"68\" page=\"0\" chnl=\"0\" />\n    <char id=\"110\" x=\"237\" y=\"320\" width=\"44\" height=\"41\" xoffset=\"1\" yoffset=\"36\" xadvance=\"46\" page=\"0\" chnl=\"0\" />\n    <char id=\"111\" x=\"104\" y=\"320\" width=\"36\" height=\"42\" xoffset=\"2\" yoffset=\"36\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"112\" x=\"361\" y=\"98\" width=\"40\" height=\"56\" xoffset=\"1\" yoffset=\"36\" xadvance=\"43\" page=\"0\" chnl=\"0\" />\n    <char id=\"113\" x=\"401\" y=\"98\" width=\"39\" height=\"56\" xoffset=\"2\" yoffset=\"36\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"114\" x=\"484\" y=\"266\" width=\"27\" height=\"41\" xoffset=\"2\" yoffset=\"36\" xadvance=\"30\" page=\"0\" chnl=\"0\" />\n    <char id=\"115\" x=\"140\" y=\"320\" width=\"31\" height=\"42\" xoffset=\"3\" yoffset=\"36\" xadvance=\"36\" page=\"0\" chnl=\"0\" />\n    <char id=\"116\" x=\"460\" y=\"266\" width=\"24\" height=\"51\" xoffset=\"1\" yoffset=\"27\" xadvance=\"26\" page=\"0\" chnl=\"0\" />\n    <char id=\"117\" x=\"281\" y=\"320\" width=\"43\" height=\"41\" xoffset=\"0\" yoffset=\"37\" xadvance=\"44\" page=\"0\" chnl=\"0\" />\n    <char id=\"118\" x=\"324\" y=\"320\" width=\"39\" height=\"40\" xoffset=\"0\" yoffset=\"37\" xadvance=\"40\" page=\"0\" chnl=\"0\" />\n    <char id=\"119\" x=\"363\" y=\"320\" width=\"57\" height=\"40\" xoffset=\"1\" yoffset=\"37\" xadvance=\"59\" page=\"0\" chnl=\"0\" />\n    <char id=\"120\" x=\"420\" y=\"320\" width=\"40\" height=\"40\" xoffset=\"1\" yoffset=\"37\" xadvance=\"42\" page=\"0\" chnl=\"0\" />\n    <char id=\"121\" x=\"440\" y=\"98\" width=\"40\" height=\"56\" xoffset=\"0\" yoffset=\"37\" xadvance=\"41\" page=\"0\" chnl=\"0\" />\n    <char id=\"122\" x=\"460\" y=\"320\" width=\"32\" height=\"40\" xoffset=\"3\" yoffset=\"37\" xadvance=\"38\" page=\"0\" chnl=\"0\" />\n    <char id=\"123\" x=\"130\" y=\"0\" width=\"25\" height=\"73\" xoffset=\"1\" yoffset=\"18\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"124\" x=\"355\" y=\"0\" width=\"8\" height=\"63\" xoffset=\"4\" yoffset=\"23\" xadvance=\"16\" page=\"0\" chnl=\"0\" />\n    <char id=\"125\" x=\"155\" y=\"0\" width=\"25\" height=\"73\" xoffset=\"-1\" yoffset=\"18\" xadvance=\"25\" page=\"0\" chnl=\"0\" />\n    <char id=\"126\" x=\"218\" y=\"362\" width=\"42\" height=\"16\" xoffset=\"3\" yoffset=\"47\" xadvance=\"49\" page=\"0\" chnl=\"0\" />\n    <char id=\"127\" x=\"0\" y=\"0\" width=\"70\" height=\"98\" xoffset=\"0\" yoffset=\"-1\" xadvance=\"70\" page=\"0\" chnl=\"0\" />\n  </chars>\n  <kernings count=\"389\">\n    <kerning first=\"86\" second=\"45\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"40\" second=\"87\" amount=\"1\" />\n    <kerning first=\"70\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"110\" amount=\"-3\" />\n    <kerning first=\"114\" second=\"116\" amount=\"1\" />\n    <kerning first=\"39\" second=\"65\" amount=\"-4\" />\n    <kerning first=\"104\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"88\" amount=\"1\" />\n    <kerning first=\"109\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"100\" amount=\"-3\" />\n    <kerning first=\"68\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"84\" second=\"103\" amount=\"-3\" />\n    <kerning first=\"34\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"81\" amount=\"-2\" />\n    <kerning first=\"73\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"68\" second=\"65\" amount=\"-3\" />\n    <kerning first=\"97\" second=\"34\" amount=\"-2\" />\n    <kerning first=\"111\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"65\" amount=\"-9\" />\n    <kerning first=\"75\" second=\"71\" amount=\"-4\" />\n    <kerning first=\"39\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"44\" second=\"39\" amount=\"-6\" />\n    <kerning first=\"89\" second=\"46\" amount=\"-7\" />\n    <kerning first=\"89\" second=\"74\" amount=\"-7\" />\n    <kerning first=\"34\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"112\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"122\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"68\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"68\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"39\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"114\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"88\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"112\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"67\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"34\" amount=\"-5\" />\n    <kerning first=\"88\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"66\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"66\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"121\" amount=\"-5\" />\n    <kerning first=\"84\" second=\"119\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"112\" amount=\"-3\" />\n    <kerning first=\"87\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"117\" amount=\"-2\" />\n    <kerning first=\"65\" second=\"89\" amount=\"-9\" />\n    <kerning first=\"72\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"119\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"69\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"109\" amount=\"-3\" />\n    <kerning first=\"84\" second=\"122\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"118\" amount=\"-5\" />\n    <kerning first=\"90\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"89\" amount=\"-2\" />\n    <kerning first=\"90\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"115\" amount=\"-4\" />\n    <kerning first=\"76\" second=\"65\" amount=\"1\" />\n    <kerning first=\"90\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"87\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"34\" amount=\"1\" />\n    <kerning first=\"69\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"97\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"72\" second=\"88\" amount=\"1\" />\n    <kerning first=\"76\" second=\"87\" amount=\"-5\" />\n    <kerning first=\"69\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"121\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"45\" amount=\"-8\" />\n    <kerning first=\"65\" second=\"86\" amount=\"-9\" />\n    <kerning first=\"46\" second=\"34\" amount=\"-6\" />\n    <kerning first=\"76\" second=\"84\" amount=\"-10\" />\n    <kerning first=\"116\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"97\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"85\" amount=\"-2\" />\n    <kerning first=\"90\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"65\" second=\"79\" amount=\"-3\" />\n    <kerning first=\"98\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"41\" amount=\"1\" />\n    <kerning first=\"84\" second=\"118\" amount=\"-3\" />\n    <kerning first=\"70\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"121\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"81\" second=\"87\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"93\" amount=\"1\" />\n    <kerning first=\"114\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"39\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"110\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"89\" amount=\"1\" />\n    <kerning first=\"87\" second=\"65\" amount=\"-2\" />\n    <kerning first=\"119\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"34\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"88\" second=\"79\" amount=\"-2\" />\n    <kerning first=\"79\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"119\" amount=\"-3\" />\n    <kerning first=\"75\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"116\" amount=\"-4\" />\n    <kerning first=\"86\" second=\"65\" amount=\"-9\" />\n    <kerning first=\"70\" second=\"84\" amount=\"1\" />\n    <kerning first=\"75\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"65\" amount=\"-9\" />\n    <kerning first=\"34\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"90\" second=\"119\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"81\" amount=\"-4\" />\n    <kerning first=\"88\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"84\" amount=\"1\" />\n    <kerning first=\"121\" second=\"34\" amount=\"1\" />\n    <kerning first=\"91\" second=\"74\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"77\" second=\"88\" amount=\"1\" />\n    <kerning first=\"75\" second=\"119\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"104\" amount=\"-1\" />\n    <kerning first=\"68\" second=\"88\" amount=\"-2\" />\n    <kerning first=\"121\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"81\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"39\" amount=\"1\" />\n    <kerning first=\"74\" second=\"65\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"118\" amount=\"1\" />\n    <kerning first=\"84\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"111\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"71\" amount=\"-2\" />\n    <kerning first=\"88\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"74\" amount=\"-8\" />\n    <kerning first=\"39\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"98\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"81\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"74\" amount=\"-11\" />\n    <kerning first=\"89\" second=\"83\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"41\" amount=\"1\" />\n    <kerning first=\"89\" second=\"97\" amount=\"-3\" />\n    <kerning first=\"89\" second=\"87\" amount=\"1\" />\n    <kerning first=\"67\" second=\"125\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"93\" amount=\"1\" />\n    <kerning first=\"80\" second=\"118\" amount=\"1\" />\n    <kerning first=\"107\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"34\" amount=\"1\" />\n    <kerning first=\"89\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"45\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"34\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"88\" second=\"67\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"102\" second=\"41\" amount=\"1\" />\n    <kerning first=\"89\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"111\" amount=\"-4\" />\n    <kerning first=\"89\" second=\"115\" amount=\"-4\" />\n    <kerning first=\"114\" second=\"102\" amount=\"1\" />\n    <kerning first=\"89\" second=\"125\" amount=\"1\" />\n    <kerning first=\"89\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"108\" amount=\"-1\" />\n    <kerning first=\"47\" second=\"47\" amount=\"-8\" />\n    <kerning first=\"65\" second=\"63\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"67\" amount=\"-4\" />\n    <kerning first=\"87\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"104\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"107\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"84\" amount=\"-6\" />\n    <kerning first=\"39\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"121\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"89\" second=\"85\" amount=\"-3\" />\n    <kerning first=\"75\" second=\"79\" amount=\"-4\" />\n    <kerning first=\"107\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"110\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"84\" amount=\"1\" />\n    <kerning first=\"84\" second=\"111\" amount=\"-3\" />\n    <kerning first=\"120\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"114\" amount=\"-3\" />\n    <kerning first=\"112\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"79\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"117\" amount=\"-3\" />\n    <kerning first=\"89\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"44\" amount=\"-11\" />\n    <kerning first=\"79\" second=\"88\" amount=\"-2\" />\n    <kerning first=\"98\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"118\" amount=\"-4\" />\n    <kerning first=\"65\" second=\"34\" amount=\"-4\" />\n    <kerning first=\"88\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"77\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"75\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"88\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"65\" amount=\"-3\" />\n    <kerning first=\"87\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"67\" second=\"41\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"93\" amount=\"1\" />\n    <kerning first=\"84\" second=\"83\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"84\" second=\"86\" amount=\"1\" />\n    <kerning first=\"122\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"89\" amount=\"1\" />\n    <kerning first=\"70\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"74\" amount=\"-8\" />\n    <kerning first=\"89\" second=\"38\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"97\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"86\" amount=\"-9\" />\n    <kerning first=\"40\" second=\"86\" amount=\"1\" />\n    <kerning first=\"90\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"111\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"41\" amount=\"1\" />\n    <kerning first=\"65\" second=\"121\" amount=\"-4\" />\n    <kerning first=\"89\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"42\" amount=\"-2\" />\n    <kerning first=\"76\" second=\"117\" amount=\"-2\" />\n    <kerning first=\"69\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"46\" second=\"39\" amount=\"-6\" />\n    <kerning first=\"118\" second=\"39\" amount=\"1\" />\n    <kerning first=\"91\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"90\" amount=\"-1\" />\n    <kerning first=\"90\" second=\"81\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"39\" amount=\"-5\" />\n    <kerning first=\"90\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"87\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"120\" amount=\"-3\" />\n    <kerning first=\"89\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"102\" second=\"125\" amount=\"1\" />\n    <kerning first=\"76\" second=\"85\" amount=\"-2\" />\n    <kerning first=\"79\" second=\"65\" amount=\"-3\" />\n    <kerning first=\"65\" second=\"71\" amount=\"-3\" />\n    <kerning first=\"79\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"97\" second=\"39\" amount=\"-2\" />\n    <kerning first=\"90\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"87\" amount=\"-5\" />\n    <kerning first=\"79\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"87\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"101\" amount=\"-2\" />\n    <kerning first=\"40\" second=\"89\" amount=\"1\" />\n    <kerning first=\"76\" second=\"89\" amount=\"-8\" />\n    <kerning first=\"69\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"120\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"69\" second=\"102\" amount=\"-1\" />\n    <kerning first=\"104\" second=\"39\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"121\" amount=\"1\" />\n    <kerning first=\"86\" second=\"46\" amount=\"-8\" />\n    <kerning first=\"65\" second=\"81\" amount=\"-3\" />\n    <kerning first=\"86\" second=\"44\" amount=\"-8\" />\n    <kerning first=\"120\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"98\" second=\"120\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"115\" amount=\"-3\" />\n    <kerning first=\"121\" second=\"39\" amount=\"1\" />\n    <kerning first=\"88\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"65\" amount=\"-6\" />\n    <kerning first=\"65\" second=\"39\" amount=\"-4\" />\n    <kerning first=\"84\" second=\"79\" amount=\"-1\" />\n    <kerning first=\"65\" second=\"119\" amount=\"-4\" />\n    <kerning first=\"70\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"75\" second=\"100\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"111\" amount=\"-2\" />\n    <kerning first=\"122\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"81\" second=\"84\" amount=\"-2\" />\n    <kerning first=\"107\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"118\" second=\"44\" amount=\"-4\" />\n    <kerning first=\"87\" second=\"46\" amount=\"-4\" />\n    <kerning first=\"87\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"79\" amount=\"-2\" />\n    <kerning first=\"87\" second=\"74\" amount=\"-2\" />\n    <kerning first=\"123\" second=\"74\" amount=\"-1\" />\n    <kerning first=\"76\" second=\"71\" amount=\"-2\" />\n    <kerning first=\"39\" second=\"100\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"88\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"121\" amount=\"-3\" />\n    <kerning first=\"112\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"71\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"86\" amount=\"1\" />\n    <kerning first=\"84\" second=\"113\" amount=\"-3\" />\n    <kerning first=\"120\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"44\" amount=\"-7\" />\n    <kerning first=\"84\" second=\"99\" amount=\"-3\" />\n    <kerning first=\"34\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"80\" second=\"46\" amount=\"-11\" />\n    <kerning first=\"86\" second=\"117\" amount=\"-1\" />\n    <kerning first=\"110\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"80\" second=\"74\" amount=\"-7\" />\n    <kerning first=\"120\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"73\" second=\"88\" amount=\"1\" />\n    <kerning first=\"108\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"115\" amount=\"-3\" />\n    <kerning first=\"89\" second=\"113\" amount=\"-2\" />\n    <kerning first=\"82\" second=\"86\" amount=\"-3\" />\n    <kerning first=\"114\" second=\"39\" amount=\"1\" />\n    <kerning first=\"34\" second=\"109\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"101\" amount=\"-3\" />\n    <kerning first=\"70\" second=\"121\" amount=\"-1\" />\n    <kerning first=\"123\" second=\"85\" amount=\"-1\" />\n    <kerning first=\"122\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"86\" second=\"97\" amount=\"-2\" />\n    <kerning first=\"82\" second=\"89\" amount=\"-4\" />\n    <kerning first=\"66\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"97\" amount=\"-4\" />\n    <kerning first=\"86\" second=\"103\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"87\" amount=\"1\" />\n    <kerning first=\"75\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"111\" amount=\"-1\" />\n    <kerning first=\"39\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"107\" second=\"101\" amount=\"-1\" />\n    <kerning first=\"82\" second=\"84\" amount=\"-3\" />\n    <kerning first=\"114\" second=\"121\" amount=\"1\" />\n    <kerning first=\"34\" second=\"99\" amount=\"-2\" />\n    <kerning first=\"70\" second=\"81\" amount=\"-2\" />\n    <kerning first=\"111\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"67\" amount=\"-1\" />\n    <kerning first=\"111\" second=\"108\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"84\" amount=\"1\" />\n    <kerning first=\"76\" second=\"79\" amount=\"-2\" />\n    <kerning first=\"85\" second=\"65\" amount=\"-2\" />\n    <kerning first=\"44\" second=\"34\" amount=\"-6\" />\n    <kerning first=\"65\" second=\"67\" amount=\"-3\" />\n    <kerning first=\"109\" second=\"34\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"103\" amount=\"-1\" />\n    <kerning first=\"78\" second=\"89\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"114\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"112\" amount=\"-1\" />\n    <kerning first=\"34\" second=\"65\" amount=\"-4\" />\n    <kerning first=\"70\" second=\"65\" amount=\"-11\" />\n    <kerning first=\"81\" second=\"86\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"119\" amount=\"1\" />\n    <kerning first=\"89\" second=\"102\" amount=\"-1\" />\n    <kerning first=\"84\" second=\"45\" amount=\"-8\" />\n    <kerning first=\"86\" second=\"125\" amount=\"1\" />\n    <kerning first=\"70\" second=\"67\" amount=\"-2\" />\n    <kerning first=\"89\" second=\"116\" amount=\"-1\" />\n    <kerning first=\"102\" second=\"34\" amount=\"1\" />\n    <kerning first=\"114\" second=\"99\" amount=\"-1\" />\n    <kerning first=\"67\" second=\"84\" amount=\"-1\" />\n    <kerning first=\"114\" second=\"113\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"122\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"118\" amount=\"-1\" />\n    <kerning first=\"70\" second=\"71\" amount=\"-2\" />\n    <kerning first=\"114\" second=\"107\" amount=\"-1\" />\n    <kerning first=\"89\" second=\"120\" amount=\"-1\" />\n  </kernings>\n</font>\n"
  },
  {
    "path": "src/web/static/ga.html",
    "content": "\n\n<!-- Begin Google Analytics -->\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=G-G9R4C1H8SR\"></script>\n<script>\n    window.dataLayer = window.dataLayer || [];\n    function gtag(){dataLayer.push(arguments);}\n    gtag('js', new Date());\n    gtag('config', 'G-G9R4C1H8SR');\n</script>\n<!-- End Google Analytics -->\n\n"
  },
  {
    "path": "src/web/static/images/IMAGE_LICENCES.md",
    "content": "## Image attribution\n\nThe following image files in this directory are taken from open sources:\n\n| File                  | Licence                                   | Attribution                                                                   |\n| --------------------- | ----------------------------------------- | ----------------------------------------------------------------------------- |\n| cook_female-32x32.png | Creative Commons Attribution 3.0 Unported | https://commons.wikimedia.org/wiki/File:Farm-Fresh_user_cook_female_white.png |\n| cook_male-32x32.png   | Creative Commons Attribution 3.0 Unported | https://commons.wikimedia.org/wiki/File:Farm-Fresh_user_cook_male_white.png   |\n| file-32x32.png        | Free for commercial use                   | https://www.iconfinder.com/icons/66768/file_info_icon                         |\n| file-128x128.png      | Free for commercial use                   | https://www.iconfinder.com/icons/66768/file_info_icon                         |\n"
  },
  {
    "path": "src/web/static/sitemap.mjs",
    "content": "import sm from \"sitemap\";\nimport OperationConfig from \"../../core/config/OperationConfig.json\" assert { type: \"json\" };\n\n/**\n * Generates an XML sitemap for all CyberChef operations and a number of recipes.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nconst baseUrl = \"https://gchq.github.io/CyberChef/\";\n\nconst smStream = new sm.SitemapStream({});\n\nsmStream.write({\n    url: baseUrl,\n    changefreq: \"weekly\",\n    priority: 1.0,\n});\n\nfor (const op in OperationConfig) {\n    smStream.write({\n        url: `${baseUrl}?op=${encodeURIComponent(op)}`,\n        changeFreq: \"yearly\",\n        priority: 0.5,\n    });\n}\nsmStream.end();\n\nsm.streamToPromise(smStream).then(\n    (buffer) => console.log(buffer.toString()), // eslint-disable-line no-console\n);\n"
  },
  {
    "path": "src/web/static/structuredData.json",
    "content": "[\n    {\n        \"@context\": \"http://schema.org\",\n        \"@graph\": [\n            {\n                \"@type\": \"Organization\",\n                \"url\": \"https://gchq.github.io/CyberChef/\",\n                \"logo\": \"https://gchq.github.io/CyberChef/images/cyberchef-128x128.png\",\n                \"sameAs\": [\n                    \"https://github.com/gchq/CyberChef\",\n                    \"https://www.npmjs.com/package/cyberchef\"\n                ]\n            },\n            {\n                \"@type\": \"WebSite\",\n                \"url\": \"https://gchq.github.io/CyberChef/\",\n                \"name\": \"CyberChef\",\n                \"potentialAction\": {\n                    \"@type\": \"SearchAction\",\n                    \"target\": \"https://gchq.github.io/CyberChef/?op={operation_search_term}\",\n                    \"query-input\": \"required name=operation_search_term\"\n                }\n            }\n        ]\n    }\n]"
  },
  {
    "path": "src/web/stylesheets/components/_button.css",
    "content": "/**\n * Button styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nbutton img,\nspan.btn img {\n    margin-right: 3px;\n    margin-bottom: 1px;\n}\n"
  },
  {
    "path": "src/web/stylesheets/components/_list.css",
    "content": "/**\n * Operation list styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n.op-list {\n    list-style-type: none;\n    margin: 0;\n    padding: 0;\n}\n\n.category-title {\n    display: block;\n    padding: 10px;\n    background-color: var(--secondary-background-colour);\n    border-bottom: 1px solid var(--secondary-border-colour);\n    font-weight: var(--title-weight);\n}\n\n.category-title[href='#catFavourites'] {\n    border-bottom-color: var(--primary-border-colour);\n}\n\n.category-title[aria-expanded=true] {\n    border-bottom-color: var(--primary-border-colour);\n}\n\n.category-title.collapsed {\n    border-bottom-color: var(--secondary-border-colour);\n}\n\n.category-title:hover {\n    color: var(--op-list-operation-font-colour);\n}\n\n.category {\n    margin: 0 !important;\n    border-radius: 0 !important;\n    border: none;\n}\n\n.op-count {\n    float: right;\n    color: var(--subtext-font-colour);\n    font-weight: normal;\n    font-size: xx-small;\n    opacity: 0.5;\n    padding-left: .5em;\n}\n"
  },
  {
    "path": "src/web/stylesheets/components/_operation.css",
    "content": "/**\n * Operation styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n.operation {\n    cursor: grab;\n    padding: 10px;\n    list-style-type: none;\n    position: relative;\n    border-width: 1px;\n    border-style: solid;\n    border-top: none;\n    border-left: none;\n    border-right: none;\n}\n\n#rec-list .operation {\n    padding: 14px;\n}\n\n.op-title {\n    font-weight: var(--op-title-font-weight);\n}\n\n.ingredients {\n    display: flex;\n    flex-flow: row wrap;\n    justify-content: flex-start;\n    column-gap: 14px;\n    row-gap: 0;\n}\n\n.ing-very-wide {\n    flex: 4 400px;\n}\n\n.ing-wide {\n    flex: 3 200px;\n}\n\n.ing-medium {\n    flex: 2 120px;\n}\n\n.ing-short {\n    flex: 1 80px;\n}\n\n.ing-flexible {\n    flex-grow: 1;\n}\n\n.ingredients .form-group {\n    margin-top: 1rem;\n    padding-top: 0;\n}\n\n.arg {\n    font-family: var(--fixed-width-font-family);\n    text-overflow: ellipsis;\n}\n\nselect.arg {\n    font-family: var(--primary-font-family);\n    min-width: 100px;\n}\n\nselect.arg.form-control:not([size]):not([multiple]), select.custom-file-control:not([size]):not([multiple]) {\n    height: 100% !important;\n}\n\ntextarea.arg {\n    min-height: 74px;\n    resize: vertical;\n}\n\ndiv.toggle-string {\n    flex: 1;\n}\n\ninput.toggle-string {\n    border-top-right-radius: 0 !important;\n    height: 100%;\n}\n\n.operation [class^='bmd-label'],\n.operation [class*=' bmd-label'] {\n    top: 13px !important;\n    left: 12px;\n    z-index: 10;\n}\n\n.operation label,\n.operation .checkbox label {\n    color: var(--arg-label-colour);\n}\n\n.operation .is-focused [class^='bmd-label'],\n.operation .is-focused [class*=' bmd-label'],\n.operation .is-focused [class^='bmd-label'],\n.operation .is-focused [class*=' bmd-label'],\n.operation .is-focused label,\n.operation .checkbox label:hover {\n    color: var(--input-highlight-colour);\n}\n\n.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,\n.ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before {\n    border-color: var(--input-border-colour);\n    color: var(--input-highlight-colour);\n}\n\n.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,\n.ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {\n    border-color: var(--input-highlight-colour);\n    color: var(--input-highlight-colour);\n}\n\n.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,\n.disabled .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,\n.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,\n.disabled .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {\n    border-color: var(--disabled-font-colour);\n    color: var(--disabled-font-colour);\n}\n\n.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,\n.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,\n.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,\n.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {\n    border-color: var(--breakpoint-font-colour);\n    color: var(--breakpoint-font-colour);\n}\n\n.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check,\n.flow-control-op.break .ingredients .checkbox label input[type=checkbox]+.checkbox-decorator .check::before,\n.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,\n.flow-control-op.break .ingredients .checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {\n    border-color: var(--fc-breakpoint-operation-font-colour);\n    color: var(--fc-breakpoint-operation-font-colour);\n}\n\n.operation .form-control {\n    padding: 20px 12px 6px 12px !important;\n    border-top-left-radius: 4px;\n    border-top-right-radius: 4px;\n    background-image: none;\n    background-color: var(--arg-background);\n    background-position-y: 100%, 100%;\n    color: var(--arg-font-colour);\n}\n\n.operation .form-control:hover {\n    background-image:\n        linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px),\n        linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);\n    filter: brightness(97%);\n}\n\n.operation .form-control:focus {\n    background-color: var(--arg-background);\n    background-image:\n        linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(25, 118, 210, 0) 2px),\n        linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);\n    filter: brightness(100%);\n}\n\n.operation .bmd-form-group.is-filled label.bmd-label-floating,\n.operation .bmd-form-group.is-focused label.bmd-label-floating {\n    top: 4px !important;\n    left: 12px;\n}\n\n.operation label.bmd-label-floating {\n    white-space: nowrap;\n    text-overflow: ellipsis;\n    overflow: hidden;\n    width: calc(100% - 13px);\n}\n\n.input-group .form-control {\n    border-top-left-radius: 4px;\n}\n\n.input-group-append button {\n    border-top-right-radius: 4px;\n    background-color: var(--arg-background) !important;\n    margin: unset;\n}\n\n.input-group-append button:hover {\n    filter: brightness(97%);\n}\n\n.editable-option-menu {\n    height: auto;\n    max-height: 350px;\n    overflow-x: hidden;\n}\n\n.editable-option-menu .dropdown-item {\n    padding: 0.3rem 1rem 0.3rem 1rem;\n    min-height: 1.6rem;\n    max-width: 20rem;\n}\n\n.ingredients .dropdown-toggle-split {\n    height: 40px !important;\n}\n\n.boolean-arg {\n    height: 46px;\n}\n\n.boolean-arg .checkbox {\n    height: 100%;\n}\n\n.boolean-arg .checkbox label {\n    height: 100%;\n    display: flex;\n    align-items: center;\n}\n\n.boolean-arg .checkbox-decorator {\n    top: 13px;\n}\n\n.register-list {\n    background-color: var(--fc-operation-border-colour);\n    font-family: var(--fixed-width-font-family);\n    padding: 10px;\n    word-break: break-all;\n}\n\n.op-icon {\n    float: right;\n    color: #f44336;\n    font-size: 18px;\n    cursor: pointer;\n}\n\n.recip-icons {\n    position: absolute;\n    top: 13px;\n    right: 10px;\n    height: 16px;\n}\n\n.recip-icons i {\n    margin-right: 10px;\n    vertical-align: baseline;\n    float: right;\n    font-size: 18px;\n    cursor: pointer;\n}\n\n.disable-icon {\n    color: var(--disable-icon-colour);\n}\n\n.disable-icon-selected {\n    color: var(--disable-icon-selected-colour);\n}\n\n.breakpoint {\n    color: var(--breakpoint-icon-colour);\n}\n\n.breakpoint-selected {\n    color: var(--breakpoint-icon-selected-colour);\n}\n\n.break {\n    color: var(--breakpoint-font-colour) !important;\n    background-color: var(--breakpoint-bg-colour) !important;\n    border-color: var(--breakpoint-border-colour) !important;\n}\n\n.break .form-group * { color: var(--breakpoint-font-colour) !important; }\n\n.selected-op {\n    color: var(--selected-operation-font-color) !important;\n    background-color: var(--selected-operation-bg-colour) !important;\n    border-color: var(--selected-operation-border-colour) !important;\n}\n\n.selected-op .form-group * { color: var(--selected-operation-font-color) !important; }\n\n.flow-control-op {\n    color: var(--fc-operation-font-colour) !important;\n    background-color: var(--fc-operation-bg-colour) !important;\n    border-color: var(--fc-operation-border-colour) !important;\n}\n\n.flow-control-op .form-group *:not(.arg) { color: var(--fc-operation-font-colour) }\n\n.flow-control-op.break {\n    color: var(--fc-breakpoint-operation-font-colour) !important;\n    background-color: var(--fc-breakpoint-operation-bg-colour) !important;\n    border-color: var(--fc-breakpoint-operation-border-colour) !important;\n}\n\n.flow-control-op.break .form-group * { color: var(--fc-breakpoint-operation-font-colour) !important; }\n\n.disabled {\n    color: var(--disabled-font-colour) !important;\n    background-color: var(--disabled-bg-colour) !important;\n    border-color: var(--disabled-border-colour) !important;\n}\n\n.disabled .form-group * { color: var(--disabled-font-colour) !important; }\n\n.break .register-list {\n    color: var(--fc-breakpoint-operation-font-colour) !important;\n    background-color: var(--fc-breakpoint-operation-border-colour) !important;\n}\n\n.disabled .register-list {\n    color: var(--disabled-font-colour) !important;\n    background-color: var(--disabled-border-colour) !important;\n}\n"
  },
  {
    "path": "src/web/stylesheets/components/_pane.css",
    "content": "/**\n * Workspace pane styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n:root {\n    --title-height: 48px;\n    --tab-height: 40px;\n}\n\n.title {\n    padding: 8px;\n    padding-left: 12px;\n    padding-right: 12px;\n    height: var(--title-height);\n    border-bottom: 1px solid var(--primary-border-colour);\n    font-weight: var(--title-weight);\n    font-size: var(--title-size);\n    color: var(--title-colour);\n    background-color: var(--title-background-colour);\n    line-height: calc(var(--title-height) - 14px);\n}\n\n.pane-controls {\n    position: absolute;\n    right: 8px;\n    top: 8px;\n    display: flex;\n    flex-direction: row;\n}\n\n.pane-controls .btn {\n    margin-left: 2px;\n}\n\n.list-area {\n    position: absolute;\n    top: var(--title-height);\n    bottom: 0;\n    width: 100%;\n    list-style-type: none;\n    margin: 0;\n    padding: 0;\n}\n\n#files .card-header .float-right a:hover {\n    text-decoration: none;\n}\n"
  },
  {
    "path": "src/web/stylesheets/index.css",
    "content": "/**\n * CyberChef styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n/* Themes */\n@import \"./themes/_classic.css\";\n@import \"./themes/_dark.css\";\n@import \"./themes/_geocities.css\";\n@import \"./themes/_solarizedDark.css\";\n@import \"./themes/_solarizedLight.css\";\n\n/* Utilities */\n@import \"./utils/_overrides.css\";\n@import \"./utils/_general.css\";\n\n/* Preloader styles */\n@import \"./preloader.css\";\n\n/* Components */\n@import \"./components/_button.css\";\n@import \"./components/_list.css\";\n@import \"./components/_operation.css\";\n@import \"./components/_pane.css\";\n\n/* Layout */\n@import \"./layout/_banner.css\";\n@import \"./layout/_controls.css\";\n@import \"./layout/_io.css\";\n@import \"./layout/_modals.css\";\n@import \"./layout/_operations.css\";\n@import \"./layout/_recipe.css\";\n@import \"./layout/_structure.css\";\n\n/* Operations */\n@import \"./operations/diff.css\";\n@import \"./operations/json.css\";\n"
  },
  {
    "path": "src/web/stylesheets/index.js",
    "content": "/**\n * Styles index\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n/* Libraries */\nimport \"highlight.js/styles/vs.css\";\n\n/* Frameworks */\nimport \"bootstrap-material-design/dist/css/bootstrap-material-design.css\";\nimport \"bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css\";\n\n/* CyberChef styles */\nimport \"./index.css\";\n"
  },
  {
    "path": "src/web/stylesheets/layout/_banner.css",
    "content": "/**\n * Banner area styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n#banner {\n    position: absolute;\n    height: 30px;\n    width: 100%;\n    line-height: 30px;\n    border-bottom: 1px solid var(--primary-border-colour);\n    color: var(--banner-font-colour);\n    background-color: var(--banner-bg-colour);\n    margin: 0;\n}\n\n#banner i {\n    vertical-align: middle;\n    padding-right: 10px;\n}\n\n#banner a {\n    color: var(--banner-url-colour);\n}\n\n#notice-wrapper {\n    text-align: center;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n}\n"
  },
  {
    "path": "src/web/stylesheets/layout/_controls.css",
    "content": "/**\n * Controls area styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n#controls {\n    position: absolute;\n    width: 100%;\n    bottom: 0;\n    padding: 10px 0;\n    border-top: 1px solid var(--primary-border-colour);\n    background-color: var(--secondary-background-colour);\n}\n\n#controls-content {\n    position: relative;\n    display: flex;\n    flex-flow: row nowrap;\n    align-items: center;\n}\n\n#auto-bake-label {\n    display: inline-block;\n    width: 100px;\n    padding: 0;\n    margin: 0;\n    text-align: center;\n    color: var(--primary-font-colour);\n    font-size: 14px;\n    cursor: pointer;\n}\n\n#auto-bake-label .check,\n#auto-bake-label .check::before {\n    border-color: var(--input-highlight-colour);\n    color: var(--input-highlight-colour);\n}\n\n#auto-bake-label .checkbox-decorator {\n    position: relative;\n}\n\n#bake {\n    box-shadow: none;\n}\n\n#controls .btn {\n    border-radius: 30px;\n    margin: 0;\n}\n\n.output-maximised .hide-on-maximised-output {\n    display: none !important;\n}\n\n.spin {\n    animation-name: spin;\n    animation-duration: 3s;\n    animation-iteration-count: infinite;\n    animation-timing-function: linear;\n}\n\n@keyframes spin {\n    0%      {transform: rotate(0deg);}\n    100%    {transform: rotate(360deg);}\n}\n"
  },
  {
    "path": "src/web/stylesheets/layout/_io.css",
    "content": "/**\n * Input/Output area styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n#input-text,\n#output-text {\n    position: relative;\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    background-color: transparent;\n    overflow: hidden;\n    user-select: auto;\n}\n\n#output-text.html-output .cm-content,\n#output-text.html-output .cm-line,\n#output-html {\n    display: block;\n    height: 100%;\n    user-select: auto;\n}\n#output-text.html-output .cm-line .cm-widgetBuffer,\n#output-text.html-output .cm-line>br {\n    display: none;\n}\n\n.cm-editor {\n    height: 100%;\n}\n\n.cm-editor .cm-content {\n    font-family: var(--fixed-width-font-family);\n    font-size: var(--fixed-width-font-size);\n    color: var(--fixed-width-font-colour);\n}\n\n#input-tabs-wrapper #input-tabs,\n#output-tabs-wrapper #output-tabs {\n    list-style: none;\n    background-color: var(--title-background-colour);\n    padding: 0;\n    margin: 0;\n    overflow-x: auto;\n    overflow-y: hidden;\n    display: flex;\n    flex-direction: row;\n    border-bottom: 1px solid var(--primary-border-colour);\n    border-left: 1px solid var(--primary-border-colour);\n    height: var(--tab-height);\n    clear: none;\n}\n\n#input-tabs li,\n#output-tabs li {\n    display: flex;\n    flex-direction: row;\n    width: 100%;\n    min-width: 120px;\n    float: left;\n    padding: 0px;\n    text-align: center;\n    border-right: 1px solid var(--primary-border-colour);\n    height: var(--tab-height);\n    vertical-align: middle;\n}\n\n#input-tabs li:hover,\n#output-tabs li:hover {\n    cursor: pointer;\n    background-color: var(--primary-background-colour);\n}\n\n.active-input-tab,\n.active-output-tab {\n    font-weight: bold;\n    background-color: var(--primary-background-colour);\n}\n\n.input-tab-content+.btn-close-tab {\n    display: block;\n    margin-top: auto;\n    margin-bottom: auto;\n    margin-right: 2px;\n}\n\n.input-tab-content+.btn-close-tab i {\n    font-size: 0.8em;\n}\n\n.input-tab-buttons,\n.output-tab-buttons {\n    width: 25px;\n    text-align: center;\n    margin: 0;\n    height: var(--tab-height);\n    line-height: var(--tab-height);\n    font-weight: bold;\n    background-color: var(--title-background-colour);\n    border-bottom: 1px solid var(--primary-border-colour);\n}\n\n.input-tab-buttons:hover,\n.output-tab-buttons:hover {\n    cursor: pointer;\n    background-color: var(--primary-background-colour);\n}\n\n\n#btn-next-input-tab,\n#btn-input-tab-dropdown,\n#btn-next-output-tab,\n#btn-output-tab-dropdown {\n    float: right;\n}\n\n#btn-previous-input-tab,\n#btn-previous-output-tab {\n    float: left;\n}\n\n#btn-close-all-tabs {\n    color: var(--breakpoint-font-colour) !important;\n}\n\n.input-tab-content,\n.output-tab-content {\n    width: 100%;\n    max-width: 100%;\n    padding-left: 5px;\n    padding-right: 5px;\n    padding-top: 10px;\n    padding-bottom: 10px;\n    height: var(--tab-height);\n    vertical-align: middle;\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n}\n\n.btn-close-tab {\n    height: var(--tab-height);\n    vertical-align: middle;\n    width: fit-content;\n}\n\n.tabs-left > li:first-child {\n    box-shadow: 15px 0px 15px -15px var(--primary-border-colour) inset;\n}\n\n.tabs-right > li:last-child {\n    box-shadow: -15px 0px 15px -15px var(--primary-border-colour) inset;\n}\n\n#input-wrapper,\n#output-wrapper {\n    height: calc(100% - var(--title-height));\n}\n\n#input-wrapper.show-tabs,\n#output-wrapper.show-tabs {\n    height: calc(100% - var(--tab-height) - var(--title-height));\n}\n\n#output-loader {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    background-color: var(--secondary-background-colour);\n    opacity: 0;\n    visibility: hidden;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n\n    transition: all 0.5s ease;\n}\n\n#output-loader-animation {\n    display: block;\n    position: absolute;\n    width: 60%;\n    height: 60%;\n    top: 10%;\n    transition: all 0.5s ease;\n}\n\n#output-loader .loading-msg {\n    opacity: 1;\n    font-family: var(--primary-font-family);\n    line-height: var(--primary-line-height);\n    color: var(--primary-font-colour);\n    left: unset;\n    top: 30%;\n    position: relative;\n\n    transition: all 0.5s ease;\n}\n\n.io-info {\n    margin-right: 18px;\n    margin-top: 1px;\n    height: 30px;\n    text-align: right;\n    line-height: 12px;\n    font-family: var(--fixed-width-font-family);\n    font-weight: normal;\n    font-size: 8pt;\n    display: flex;\n    align-items: center;\n}\n\n.dropping-file {\n    border: 5px dashed var(--drop-file-border-colour) !important;\n}\n\n#stale-indicator {\n    opacity: 1;\n    visibility: visible;\n    transition: margin 0s, opacity 0.3s;\n    margin-left: 5px;\n    cursor: help;\n}\n\n#stale-indicator i {\n    vertical-align: middle;\n    margin-bottom: 5px;\n}\n\n#magic {\n    opacity: 1;\n    visibility: visible;\n    transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s;\n    margin-left: 5px;\n    margin-bottom: 5px;\n}\n\n#magic.hidden,\n#stale-indicator.hidden {\n    visibility: hidden;\n    transition: opacity 0.3s, margin 0.3s 0.3s, visibility 0.3s;\n    opacity: 0;\n}\n\n#magic.hidden {\n    margin-left: -32px;\n}\n\n#magic svg path {\n    fill: var(--primary-font-colour);\n}\n\n.pulse {\n    box-shadow: 0 0 0 0 rgba(90, 153, 212, .3);\n    animation: pulse 1.5s 1;\n}\n\n.pulse:hover {\n    animation-play-state: paused;\n}\n\n@keyframes pulse {\n    0% {\n        transform: scale(1);\n    }\n    70% {\n        transform: scale(1.1);\n        box-shadow: 0 0 0 20px rgba(90, 153, 212, 0);\n    }\n    100% {\n        transform: scale(1);\n        box-shadow: 0 0 0 0 rgba(90, 153, 212, 0);\n    }\n}\n\n#input-find-options,\n#output-find-options {\n    display: flex;\n    flex-direction: row;\n    flex-wrap: wrap;\n    width: 100%;\n}\n\n#input-tab-body .form-group.input-group,\n#output-tab-body .form-group.input-group {\n    width: 70%;\n    float: left;\n    margin-bottom: 2rem;\n}\n\n.input-find-option .toggle-string {\n    width: 70%;\n    display: inline-block;\n}\n\n.input-find-option-append button {\n    border-top-right-radius: 4px;\n    background-color: var(--arg-background) !important;\n    margin: unset;\n}\n\n.input-find-option-append button:hover {\n    filter: brightness(97%);\n}\n\n.form-group.output-find-option {\n    width: 70%;\n    float: left;\n}\n\n#input-num-results-container,\n#output-num-results-container {\n    width: 20%;\n    float: right;\n    margin: 0;\n    margin-left: 10%;\n}\n\n#input-find-options-checkboxes,\n#output-find-options-checkboxes {\n    list-style: none;\n    padding: 0;\n    margin: auto;\n    overflow-x: auto;\n    overflow-y: hidden;\n    text-align: center;\n    width: fit-content;\n}\n\n#input-find-options-checkboxes li,\n#output-find-options-checkboxes li {\n    display: flex;\n    flex-direction: row;\n    float: left;\n    padding: 10px;\n    text-align: center;\n}\n\n\n#input-search-results,\n#output-search-results {\n    list-style: none;\n    width: 75%;\n    min-width: 200px;\n    margin-left: auto;\n    margin-right: auto;\n}\n\n#input-search-results li,\n#output-search-results li {\n    padding-left: 5px;\n    padding-right: 5px;\n    padding-top: 10px;\n    padding-bottom: 10px;\n    text-align: center;\n    width: 100%;\n    color: var(--op-list-operation-font-colour);\n    background-color: var(--op-list-operation-bg-colour);\n    border-bottom: 2px solid var(--op-list-operation-border-colour);\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n}\n\n#input-search-results li:first-of-type,\n#output-search-results li:first-of-type {\n    border-top: 2px solid var(--op-list-operation-border-colour);\n}\n\n#input-search-results li:hover,\n#output-search-results li:hover {\n    cursor: pointer;\n    filter: brightness(98%);\n}\n\n/* Highlighting */\n.ͼ2.cm-focused .cm-selectionBackground {\n    background-color: var(--hl5);\n}\n\n.ͼ2 .cm-selectionBackground {\n    background-color: var(--hl1);\n}\n\n.ͼ1 .cm-selectionMatch {\n    background-color: var(--hl2);\n}\n\n.ͼ1.cm-focused .cm-cursor.cm-cursor-primary {\n    border-color: var(--primary-font-colour);\n}\n\n.ͼ1 .cm-cursor.cm-cursor-primary {\n    display: block;\n    border-color: var(--subtext-font-colour);\n}\n\n\n/* Status bar */\n\n.cm-panel input::placeholder {\n    font-size: 12px !important;\n}\n\n.ͼ2 .cm-panels,\n.ͼ2 .cm-side-panels {\n    background-color: var(--secondary-background-colour);\n    border-color: var(--primary-border-colour);\n    color: var(--primary-font-colour);\n}\n\n.cm-status-bar {\n    font-family: var(--fixed-width-font-family);\n    font-weight: normal;\n    font-size: 8pt;\n    margin: 0 5px;\n    display: flex;\n    flex-flow: row nowrap;\n    justify-content: space-between;\n    align-items: center;\n}\n\n.cm-status-bar i {\n    font-size: 12pt;\n    vertical-align: middle;\n    margin-left: 8px;\n}\n.cm-status-bar>div>span:first-child i {\n    margin-left: 0;\n}\n\n.cm-status-bar .disabled {\n    background-color: unset !important;\n    cursor: not-allowed;\n}\n\n/* Dropup Button */\n.cm-status-bar-select-btn {\n    border: none;\n    cursor: pointer;\n}\n\n/* The container <div> - needed to position the dropup content */\n.cm-status-bar-select {\n    position: relative;\n    display: inline-block;\n}\n\n/* Dropup content (Hidden by Default) */\n.cm-status-bar-select-content {\n    display: none;\n    position: absolute;\n    bottom: 20px;\n    right: 0;\n    background-color: #f1f1f1;\n    min-width: 200px;\n    box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.2);\n    z-index: 1;\n}\n\n/* Links inside the dropup */\n.cm-status-bar-select-content a {\n    color: black;\n    padding: 2px 5px;\n    text-decoration: none;\n    display: block;\n}\n\n/* Change color of dropup links on hover */\n.cm-status-bar-select-content a:hover {\n    background-color: #ddd\n}\n\n/* Change the background color of the dropup button when the dropup content is shown */\n.cm-status-bar-select:hover .cm-status-bar-select-btn {\n    background-color: #f1f1f1;\n}\n\n/* The search field */\n.cm-status-bar-filter-input {\n    box-sizing: border-box;\n    font-size: 12px;\n    padding-left: 10px !important;\n    border: none;\n}\n\n.cm-status-bar-filter-search {\n    border-top: 1px solid #ddd;\n}\n\n/* Show the dropup menu */\n.cm-status-bar-select .show {\n    display: block;\n}\n\n.cm-status-bar-select-scroll {\n    overflow-y: auto;\n    max-height: 300px;\n}\n\n.chr-enc-value {\n    max-width: 150px;\n    display: inline-block;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    vertical-align: middle;\n}\n\n\n/* File details panel */\n\n.cm-file-details {\n    text-align: center;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    overflow-y: auto;\n    padding-bottom: 21px;\n    height: 100%;\n}\n\n.file-details-toggle-shown,\n.file-details-toggle-hidden {\n    width: 8px;\n    height: 40px;\n    border: 1px solid var(--secondary-border-colour);\n    position: absolute;\n    top: calc(50% - 20px);\n    cursor: pointer;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    background-color: var(--secondary-border-colour);\n    color: var(--subtext-font-colour);\n    z-index: 1;\n}\n\n.file-details-toggle-shown {\n    left: 0;\n    border-left: none;\n    border-top-right-radius: 5px;\n    border-bottom-right-radius: 5px;\n}\n\n.file-details-toggle-hidden {\n    left: -8px;\n    border-right: none;\n    border-top-left-radius: 5px;\n    border-bottom-left-radius: 5px;\n}\n\n.file-details-toggle-shown:hover,\n.file-details-toggle-hidden:hover {\n    background-color: var(--primary-border-colour);\n    border-color: var(--primary-border-colour);\n    color: var(--primary-font-colour);\n}\n\n.file-details-heading {\n    font-weight: bold;\n    margin: 10px 0 10px 0;\n}\n\n.file-details-data {\n    text-align: left;\n    margin: 10px 2px;\n}\n\n.file-details-data td {\n    padding: 0 3px;\n    max-width: 130px;\n    min-width: 60px;\n    overflow: hidden;\n    vertical-align: top;\n    word-break: break-all;\n}\n\n.file-details-error {\n    color: #f00;\n}\n\n.file-details-thumbnail {\n    max-width: 180px;\n}\n"
  },
  {
    "path": "src/web/stylesheets/layout/_modals.css",
    "content": "/**\n * Modal layout styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n.modal-content {\n    background-color: var(--primary-background-colour);\n}\n\n.option-item {\n    margin-bottom: 20px;\n}\n\n#edit-favourites-list {\n    margin: 10px;\n    border: 1px solid var(--op-list-operation-border-colour);\n}\n\n#edit-favourites-list .operation {\n    border-left: none;\n    border-right: none;\n}\n\n#edit-favourites-list .operation:last-child {\n    border-bottom: none;\n}\n\n.about-img-left {\n    float: left;\n    margin: 10px 20px 20px 0;\n}\n\n.about-img-right {\n    float: right;\n    margin: 10px 0 20px 20px;\n}\n\n#save-link-group {\n    padding-top: 0;\n}\n\n.save-link-options {\n    float: right;\n}\n\n.save-link-options label {\n    margin-left: 10px;\n}\n\n#save-footer {\n    border-top: none;\n    margin-top: 0;\n    border-bottom: 1px solid var(--primary-border-colour);\n}\n\n#support-modal textarea {\n    font-family: var(--primary-font-family);\n}\n\n#save-texts textarea,\n#load-text {\n    font-family: var(--fixed-width-font-family);\n}\n\n#save-texts textarea {\n    height: 200px;\n}\n\n#faqs a.btn {\n    text-transform: unset;\n}\n\n#faqs > div {\n    padding: 20px;\n    border-left: 2px solid var(--primary-border-colour);\n}\n\n.checkbox label input[type=checkbox]+.checkbox-decorator .check,\n.checkbox label input[type=checkbox]+.checkbox-decorator .check::before {\n    border-color: var(--input-border-colour);\n    color: var(--input-highlight-colour);\n}\n\n.checkbox label input[type=checkbox]:checked+.checkbox-decorator .check,\n.checkbox label input[type=checkbox]:checked+.checkbox-decorator .check::before {\n    border-color: var(--input-highlight-colour);\n    color: var(--input-highlight-colour);\n}\n\n.bmd-form-group.is-focused .option-item label {\n    color: var(--input-highlight-colour);\n}\n\n.bmd-form-group.is-focused [class^='bmd-label'],\n.bmd-form-group.is-focused [class*=' bmd-label'],\n.bmd-form-group.is-focused [class^='bmd-label'],\n.bmd-form-group.is-focused [class*=' bmd-label'],\n.bmd-form-group.is-focused label,\n.checkbox label:hover,\n.bmd-form-group.is-filled:focus-within .checkbox.option-item label {\n    color: var(--input-highlight-colour);\n}\n\n\n.bmd-form-group.option-item label+.form-control{\n    background-image:\n        linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px),\n        linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);\n}\n"
  },
  {
    "path": "src/web/stylesheets/layout/_operations.css",
    "content": "/**\n * Operation area styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n.op-list .operation {\n    color: var(--op-list-operation-font-colour);\n    background-color: var(--op-list-operation-bg-colour);\n    border-color: var(--op-list-operation-border-colour);\n}\n\n#search {\n    padding-left: 10px;\n    padding-right: 10px;\n    background-image:\n        linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px),\n        linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);\n}\n\n#edit-favourites {\n    float: right;\n    margin-top: -7px;\n}\n\n.favourites-hover {\n    color: var(--rec-list-operation-font-colour);\n    background-color: var(--rec-list-operation-bg-colour);\n    border: 2px dashed var(--rec-list-operation-font-colour) !important;\n    padding: 8px 8px 9px 8px;\n}\n\n#categories a {\n    color: var(--category-list-font-colour);\n    cursor: pointer;\n}\n\n#categories a:hover,\n.op-list .operation:hover {\n    filter: brightness(98%);\n}\n"
  },
  {
    "path": "src/web/stylesheets/layout/_recipe.css",
    "content": "/**\n * Recipe area styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n#rec-list {\n    overflow: auto;\n}\n\n#rec-list .operation {\n    color: var(--rec-list-operation-font-colour);\n    background-color: var(--rec-list-operation-bg-colour);\n    border-color: var(--rec-list-operation-border-colour);\n}\n"
  },
  {
    "path": "src/web/stylesheets/layout/_structure.css",
    "content": "/**\n * Overall page structure styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nbody {\n    overflow: hidden;\n}\n\n#content-wrapper {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n}\n\n#workspace-wrapper {\n    position: absolute;\n    top: 30px;\n    bottom: 0;\n    width: 100%;\n}\n\ndiv#operations,\ndiv#recipe {\n    width: 50%;\n    height: 100%;\n}\n\ndiv#input,\ndiv#output {\n    width: 100%;\n    height: 50%;\n}\n\n.split {\n    box-sizing: border-box;\n    /* overflow: auto; */\n    /* Removed to enable Background Magic button pulse to overflow.\n    Replace this rule if it seems to be causing problems. */\n    position: relative;\n}\n\n#operations.split {\n    overflow: auto;\n}\n\n.split.split-horizontal, .gutter.gutter-horizontal {\n    height: 100%;\n    float: left;\n}\n\n.gutter {\n    background-color: var(--secondary-border-colour);\n    background-repeat: no-repeat;\n    background-position: 50%;\n}\n\n.gutter.gutter-horizontal {\n    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+EFBhEwBDmIiYYAAAAjSURBVBjTYzxz5sx/BgYGBiYGKGB89+4dA4oIy71790aGGgCn+DBbOcAB0wAAAABJRU5ErkJggg==');\n    cursor: ew-resize;\n}\n\n.gutter.gutter-vertical {\n    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAACCAYAAABPJGxCAAAABGdBTUEAALGOfPtRkwAACkNpQ0NQSUNDIFByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/vcxDeEAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH4QUGETI0LWfbqAAAACNJREFUCNdjPHPmzH8GBgYGJSUlRgYGBoZ79+7Rhc/EMEAAAHd6H2e3/71BAAAAAElFTkSuQmCC');\n    cursor: ns-resize;\n}\n"
  },
  {
    "path": "src/web/stylesheets/operations/diff.css",
    "content": "del {\n    background-color: var(--hl3);\n}\n\nins {\n    text-decoration: underline; /* shouldn't be needed, but Chromium doesn't copy to clipboard without it */\n    background-color: var(--hl5);\n}\n"
  },
  {
    "path": "src/web/stylesheets/operations/json.css",
    "content": "/**\n * JSON styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n *\n * Adapted for CyberChef by @n1474335 from jQuery json-viewer\n * @author Alexandre Bodelot <alexandre.bodelot@gmail.com>\n * @link https://github.com/abodelot/jquery.json-viewer\n * @license MIT\n */\n\n/* Root element */\n.json-document {\n    padding: .5em 1.5em;\n}\n\n/* Syntax highlighting for JSON objects */\nul.json-dict, ol.json-array {\n    list-style-type: none;\n    margin: 0 0 0 1px;\n    border-left: 1px dotted #ccc;\n    padding-left: 2em;\n}\n.json-string {\n    color: green;\n}\n.json-literal {\n    color: red;\n}\n.json-brace,\n.json-bracket,\n.json-colon,\n.json-comma {\n    color: gray;\n}\n\n/* Collapse */\n.json-details {\n    display: inline;\n}\n.json-details[open] {\n    display: contents;\n}\n.json-summary {\n    display: inline;\n    list-style: none;\n}\n\n/* Display object and array brackets when closed */\n.json-summary.json-obj::after {\n    color: gray;\n    content: \"{ ... }\"\n}\n.json-summary.json-arr::after {\n    color: gray;\n    content: \"[ ... ]\"\n}\n.json-details[open] > .json-summary.json-obj::after,\n.json-details[open] > .json-summary.json-arr::after  {\n    content: \"\";\n}\n\n/* Show arrows, even in inline mode */\n.json-summary::before {\n    content: \"\\25BC\";\n    color: #c0c0c0;\n    margin-left: -12px;\n    margin-right: 5px;\n    display: inline-block;\n    transform: rotate(-90deg);\n}\n.json-summary:hover::before {\n    color: #aaa;\n}\n.json-details[open] > .json-summary::before {\n    transform: rotate(0deg);\n}\n"
  },
  {
    "path": "src/web/stylesheets/preloader.css",
    "content": "/**\n * Preloader styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n #loader-wrapper {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    z-index: 1000;\n    background-color: var(--loader-background-colour);\n}\n\n.loader {\n    display: block;\n    position: relative;\n    left: 50%;\n    top: 50%;\n    width: 150px;\n    height: 150px;\n    margin: -75px 0 0 -75px;\n\n    border: 3px solid transparent;\n    border-top-color: var(--loader-outer-colour);\n    border-radius: 50%;\n\n    animation: spin 2s linear infinite;\n}\n\n.loader:before,\n.loader:after {\n    content: \"\";\n    position: absolute;\n    border: 3px solid transparent;\n    border-radius: 50%;\n}\n\n.loader:before {\n    top: 5px;\n    left: 5px;\n    right: 5px;\n    bottom: 5px;\n    border-top-color: var(--loader-middle-colour);\n    animation: spin 3s linear infinite;\n}\n\n.loader:after {\n    top: 13px;\n    left: 13px;\n    right: 13px;\n    bottom: 13px;\n    border-top-color: var(--loader-inner-colour);\n    animation: spin 1.5s linear infinite;\n}\n\n.loading-msg {\n    display: block;\n    position: relative;\n    width: 400px;\n    left: calc(50% - 200px);\n    top: calc(50% + 50px);\n    text-align: center;\n    opacity: 0;\n    font-size: 18px;\n}\n\n.loading-msg.loading {\n    opacity: 1;\n    transition: all 0.1s ease-in;\n}\n\n.loading-error {\n    display: block;\n    position: relative;\n    width: 600px;\n    left: calc(50% - 300px);\n    top: 10%;\n}\n\n\n/* Loaded */\n.loaded .loading-msg {\n    opacity: 0;\n    transition: all 0.3s ease-out;\n}\n\n.loaded #loader-wrapper {\n    opacity: 0;\n    transition: all 0.5s 0.3s ease-out;\n}\n\n.loaded #rec-list li {\n\tanimation: bump 0.7s cubic-bezier(0.7, 0, 0.3, 1) both;\n}\n\n.loaded #content-wrapper {\n\tanimation-delay: 0.10s;\n}\n\n.loaded #rec-list li:first-child {\n\tanimation-delay: 0.20s;\n}\n\n.loaded #rec-list li:nth-child(2) {\n\tanimation-delay: 0.25s;\n}\n\n.loaded #rec-list li:nth-child(3) {\n\tanimation-delay: 0.30s;\n}\n\n.loaded #rec-list li:nth-child(4) {\n\tanimation-delay: 0.35s;\n}\n\n.loaded #rec-list li:nth-child(5) {\n\tanimation-delay: 0.40s;\n}\n\n.loaded #rec-list li:nth-child(6) {\n\tanimation-delay: 0.45s;\n}\n\n.loaded #rec-list li:nth-child(7) {\n\tanimation-delay: 0.50s;\n}\n\n.loaded #rec-list li:nth-child(8) {\n\tanimation-delay: 0.55s;\n}\n\n.loaded #rec-list li:nth-child(9) {\n\tanimation-delay: 0.60s;\n}\n\n.loaded #rec-list li:nth-child(10) {\n\tanimation-delay: 0.65s;\n}\n\n\n/* Animations */\n\n@keyframes spin {\n    0%   {\n        transform: rotate(0deg);\n    }\n    100% {\n        transform: rotate(360deg);\n    }\n}\n\n@keyframes bump {\n\tfrom {\n        opacity: 0;\n        transform: translate3d(0, 200px, 0);\n    }\n}\n"
  },
  {
    "path": "src/web/stylesheets/themes/_classic.css",
    "content": "/**\n * Classic theme definitions\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n:root,\n:root.classic {\n    --primary-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n        Roboto, \"Helvetica Neue\", Arial, sans-serif;\n    --primary-font-colour: #333;\n    --primary-font-size: 14px;\n    --primary-line-height: 20px;\n\n    --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas,\n        \"Liberation Mono\", \"Courier New\", monospace;\n    --fixed-width-font-colour: inherit;\n    --fixed-width-font-size: inherit;\n\n    --subtext-font-colour: #999;\n    --subtext-font-size: 13px;\n\n    --primary-background-colour: #fff;\n    --secondary-background-colour: #fafafa;\n\n    --primary-border-colour: #ddd;\n    --secondary-border-colour: #eee;\n\n    --title-colour: #424242;\n    --title-weight: bold;\n    --title-size: 16px;\n    --title-background-colour: #fafafa;\n\n    --banner-font-colour: #468847;\n    --banner-bg-colour: #dff0d8;\n    --banner-url-colour: #1976d2;\n\n    --category-list-font-colour: #1976d2;\n\n    --loader-background-colour: var(--secondary-border-colour);\n    --loader-outer-colour: #3498db;\n    --loader-middle-colour: #e74c3c;\n    --loader-inner-colour: #f9c922;\n\n\n    /* Operation colours */\n    --op-list-operation-font-colour: #3a87ad;\n    --op-list-operation-bg-colour: #d9edf7;\n    --op-list-operation-border-colour: #bce8f1;\n\n    --rec-list-operation-font-colour: #468847;\n    --rec-list-operation-bg-colour: #dff0d8;\n    --rec-list-operation-border-colour: #d3e8c0;\n\n    --selected-operation-font-color: #c09853;\n    --selected-operation-bg-colour: #fcf8e3;\n    --selected-operation-border-colour: #fbeed5;\n\n    --breakpoint-font-colour: #b94a48;\n    --breakpoint-bg-colour: #f2dede;\n    --breakpoint-border-colour: #eed3d7;\n\n    --disabled-font-colour: #999;\n    --disabled-bg-colour: #dfdfdf;\n    --disabled-border-colour: #cdcdcd;\n\n    --fc-operation-font-colour: #396f3a;\n    --fc-operation-bg-colour: #c7e4ba;\n    --fc-operation-border-colour: #b3dba2;\n\n    --fc-breakpoint-operation-font-colour: #94312f;\n    --fc-breakpoint-operation-bg-colour: #eabfbf;\n    --fc-breakpoint-operation-border-colour: #e2aeb5;\n\n\n    /* Operation arguments */\n    --op-title-font-weight: bold;\n    --arg-font-colour: #424242;\n    --arg-background: #fff;\n    --arg-border-colour: #ddd;\n    --arg-disabled-background: #eee;\n    --arg-label-colour: #388e3c;\n\n\n    /* Operation buttons */\n    --disable-icon-colour: #9e9e9e;\n    --disable-icon-selected-colour: #f44336;\n    --breakpoint-icon-colour: #9e9e9e;\n    --breakpoint-icon-selected-colour: #f44336;\n\n\n    /* Buttons */\n    --btn-default-font-colour: #333;\n    --btn-default-bg-colour: #fff;\n    --btn-default-border-colour: #ddd;\n\n    --btn-default-hover-font-colour: #333;\n    --btn-default-hover-bg-colour: #ebebeb;\n    --btn-default-hover-border-colour: #adadad;\n\n    --btn-success-font-colour: #fff;\n    --btn-success-bg-colour: #5cb85c;\n    --btn-success-border-colour: #4cae4c;\n\n    --btn-success-hover-font-colour: #fff;\n    --btn-success-hover-bg-colour: #449d44;\n    --btn-success-hover-border-colour: #398439;\n\n\n    /* Highlighter colours */\n    --hl1: #ffee00aa;\n    --hl2: #95dfffaa;\n    --hl3: #ffb6b6aa;\n    --hl4: #fcf8e3aa;\n    --hl5: #8de768aa;\n\n\n    /* Scrollbar */\n    --scrollbar-track: var(--secondary-background-colour);\n    --scrollbar-thumb: #ccc;\n    --scrollbar-hover: #bbb;\n\n\n    /* Misc. */\n    --drop-file-border-colour: #3a87ad;\n    --table-border-colour: #ccc;\n    --popover-background: #fff;\n    --popover-border-colour: #ccc;\n    --code-background: #f9f2f4;\n    --code-font-colour: #c7254e;\n    --input-highlight-colour: #1976d2;\n    --input-border-colour: #424242;\n}\n"
  },
  {
    "path": "src/web/stylesheets/themes/_dark.css",
    "content": "/**\n * Dark theme definitions\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n:root.dark {\n    --primary-font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n    --primary-font-colour: #c5c5c5;\n    --primary-font-size: 14px;\n    --primary-line-height: 20px;\n\n    --fixed-width-font-family: \"Monaco\", \"Droid Sans Mono\", \"Consolas\", monospace;\n    --fixed-width-font-colour: inherit;\n    --fixed-width-font-size: inherit;\n\n    --subtext-font-colour: #999;\n    --subtext-font-size: 13px;\n\n    --primary-background-colour: #1e1e1e;\n    --secondary-background-colour: #252525;\n\n    --primary-border-colour: #444;\n    --secondary-border-colour: #3c3c3c;\n\n    --title-colour: #fff;\n    --title-weight: bold;\n    --title-background-colour: #333;\n\n    --banner-font-colour: #c5c5c5;\n    --banner-bg-colour: #252525;\n    --banner-url-colour: #1976d2;\n\n    --category-list-font-colour: #1976d2;\n\n    --loader-background-colour: var(--secondary-border-colour);\n    --loader-outer-colour: #3498db;\n    --loader-middle-colour: #e74c3c;\n    --loader-inner-colour: #f9c922;\n\n\n    /* Operation colours */\n    --op-list-operation-font-colour: #c5c5c5;\n    --op-list-operation-bg-colour: #333;\n    --op-list-operation-border-colour: #444;\n\n    --rec-list-operation-font-colour: #c5c5c5;\n    --rec-list-operation-bg-colour: #252525;\n    --rec-list-operation-border-colour: #444;\n\n    --selected-operation-font-color: #c5c5c5;\n    --selected-operation-bg-colour: #3f3f3f;\n    --selected-operation-border-colour: #444;\n\n    --breakpoint-font-colour: #ddd;\n    --breakpoint-bg-colour: #073655;\n    --breakpoint-border-colour: #444;\n\n    --disabled-font-colour: #666;\n    --disabled-bg-colour: #444;\n    --disabled-border-colour: #444;\n\n    --fc-operation-font-colour: #c5c5c5;\n    --fc-operation-bg-colour: #2d2d2d;\n    --fc-operation-border-colour: #444;\n\n    --fc-breakpoint-operation-font-colour: #ddd;\n    --fc-breakpoint-operation-bg-colour: #072b49;\n    --fc-breakpoint-operation-border-colour: #444;\n\n\n    /* Operation arguments */\n    --op-title-font-weight: bold;\n    --arg-font-colour: #bbb;\n    --arg-background: #3c3c3c;\n    --arg-border-colour: #3c3c3c;\n    --arg-disabled-background: #4f4f4f;\n    --arg-label-colour: rgb(25, 118, 210);\n\n\n    /* Operation buttons */\n    --disable-icon-colour: #9e9e9e;\n    --disable-icon-selected-colour: #f44336;\n    --breakpoint-icon-colour: #9e9e9e;\n    --breakpoint-icon-selected-colour: #f44336;\n\n\n    /* Buttons */\n    --btn-default-font-colour: #c5c5c5;\n    --btn-default-bg-colour: #2d2d2d;\n    --btn-default-border-colour: #3c3c3c;\n\n    --btn-default-hover-font-colour: #c5c5c5;\n    --btn-default-hover-bg-colour: #2d2d2d;\n    --btn-default-hover-border-colour: #205375;\n\n    --btn-success-font-colour: #fff;\n    --btn-success-bg-colour: #073655;\n    --btn-success-border-colour: #0e639c;\n\n    --btn-success-hover-font-colour: #fff;\n    --btn-success-hover-bg-colour: #0e639c;\n    --btn-success-hover-border-colour: #0e639c;\n\n\n    /* Highlighter colours */\n    --hl1: #264f78;\n    --hl2: #675351;\n    --hl3: #c40000;\n    --hl4: #fcf8e3;\n    --hl5: #38811b;\n\n\n    /* Scrollbar */\n    --scrollbar-track: #1e1e1e;\n    --scrollbar-thumb: #424242;\n    --scrollbar-hover: #4e4e4e;\n\n\n    /* Misc. */\n    --drop-file-border-colour: #0e639c;\n    --table-border-colour: #555;\n    --popover-background: #444;\n    --popover-border-colour: #555;\n    --code-background: #0e639c;\n    --code-font-colour: #fff;\n    --input-highlight-colour: #1976d2;\n    --input-border-colour: #424242;\n}\n"
  },
  {
    "path": "src/web/stylesheets/themes/_geocities.css",
    "content": "/**\n * GeoCities theme definitions\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n:root.geocities {\n    --primary-font-family: \"Comic Sans\", \"Comic Sans MS\", \"Chalkboard\", \"ChalkboardSE-Regular\", \"Marker Felt\", \"Purisa\", \"URW Chancery L\", cursive, sans-serif;\n    --primary-font-colour: black;\n    --primary-font-size: 14px;\n    --primary-line-height: 20px;\n\n    --fixed-width-font-family: \"Courier New\", Courier, monospace;\n    --fixed-width-font-colour: yellow;\n    --fixed-width-font-size: inherit;\n\n    --subtext-font-colour: darkgrey;\n    --subtext-font-size: 13px;\n\n    --primary-background-colour: #00f;\n    --secondary-background-colour: #f00;\n\n    --primary-border-colour: pink;\n    --secondary-border-colour: springgreen;\n\n    --title-colour: red;\n    --title-weight: bold;\n    --title-background-colour: yellow;\n\n    --banner-font-colour: white;\n    --banner-bg-colour: maroon;\n    --banner-url-colour: yellow;\n\n    --category-list-font-colour: yellow;\n\n    --loader-background-colour: #00f;\n    --loader-outer-colour: #0f0;\n    --loader-middle-colour: red;\n    --loader-inner-colour: yellow;\n\n\n    /* Operation colours */\n    --op-list-operation-font-colour: blue;\n    --op-list-operation-bg-colour: yellow;\n    --op-list-operation-border-colour: green;\n\n    --rec-list-operation-font-colour: white;\n    --rec-list-operation-bg-colour: purple;\n    --rec-list-operation-border-colour: green;\n\n    --selected-operation-font-color: white;\n    --selected-operation-bg-colour: pink;\n    --selected-operation-border-colour: blue;\n\n    --breakpoint-font-colour: white;\n    --breakpoint-bg-colour: red;\n    --breakpoint-border-colour: blue;\n\n    --disabled-font-colour: grey;\n    --disabled-bg-colour: black;\n    --disabled-border-colour: grey;\n\n    --fc-operation-font-colour: sienna;\n    --fc-operation-bg-colour: pink;\n    --fc-operation-border-colour: yellow;\n\n    --fc-breakpoint-operation-font-colour: darkgrey;\n    --fc-breakpoint-operation-bg-colour: deeppink;\n    --fc-breakpoint-operation-border-colour: yellowgreen;\n\n\n    /* Operation arguments */\n    --op-title-font-weight: bold;\n    --arg-font-colour: white;\n    --arg-background: black;\n    --arg-border-colour: lime;\n    --arg-disabled-background: grey;\n    --arg-label-colour: red;\n\n\n    /* Operation buttons */\n    --disable-icon-colour: #0f0;\n    --disable-icon-selected-colour: yellow;\n    --breakpoint-icon-colour: #0f0;\n    --breakpoint-icon-selected-colour: yellow;\n\n\n    /* Buttons */\n    --btn-default-font-colour: black;\n    --btn-default-bg-colour: white;\n    --btn-default-border-colour: grey;\n\n    --btn-default-hover-font-colour: black;\n    --btn-default-hover-bg-colour: white;\n    --btn-default-hover-border-colour: grey;\n\n    --btn-success-font-colour: white;\n    --btn-success-bg-colour: lawngreen;\n    --btn-success-border-colour: grey;\n\n    --btn-success-hover-font-colour: white;\n    --btn-success-hover-bg-colour: lime;\n    --btn-success-hover-border-colour: grey;\n\n\n    /* Highlighter colours */\n    --hl1: #fff000;\n    --hl2: #95dfff;\n    --hl3: #ffb6b6;\n    --hl4: #fcf8e3;\n    --hl5: #8de768;\n\n\n    /* Scrollbar */\n    --scrollbar-track: lightsteelblue;\n    --scrollbar-thumb: lightslategrey;\n    --scrollbar-hover: grey;\n\n\n    /* Misc. */\n    --drop-file-border-colour: purple;\n    --table-border-colour: var(--hl3);\n    --popover-background: turquoise;\n    --popover-border-colour: violet;\n    --code-background: black;\n    --code-font-colour: limegreen;\n    --input-highlight-colour: limegreen;\n    --input-border-colour: limegreen;\n}\n"
  },
  {
    "path": "src/web/stylesheets/themes/_solarizedDark.css",
    "content": "/**\n * Solarized dark theme definitions\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n:root.solarizedDark {\n    --base03: #002b36;\n    --base02: #073642;\n    --base01: #586e75;\n    --base00: #657b83;\n    --base0: #839496;\n    --base1: #93a1a1;\n    --base2: #eee8d5;\n    --base3: #fdf6e3;\n    --sol-yellow: #b58900;\n    --sol-orange: #cb4b16;\n    --sol-red: #dc322f;\n    --sol-magenta: #d33682;\n    --sol-violet: #6c71c4;\n    --sol-blue: #268bd2;\n    --sol-cyan: #2aa198;\n    --sol-green: #859900;\n\n    --primary-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n        Roboto, \"Helvetica Neue\", Arial, sans-serif;\n    --primary-font-colour: var(--base0);\n    --primary-font-size: 14px;\n    --primary-line-height: 20px;\n\n    --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas,\n        \"Liberation Mono\", \"Courier New\", monospace;\n    --fixed-width-font-colour: inherit;\n    --fixed-width-font-size: inherit;\n\n    --subtext-font-colour: var(--base01);\n    --subtext-font-size: 13px;\n\n    --primary-background-colour: var(--base03);\n    --secondary-background-colour: var(--base02);\n\n    --primary-border-colour: var(--base00);\n    --secondary-border-colour: var(--base01);\n\n    --title-colour: var(--base1);\n    --title-weight: bold;\n    --title-background-colour: var(--base02);\n\n    --banner-font-colour: var(--base0);\n    --banner-bg-colour: var(--base03);\n    --banner-url-colour: var(--base1);\n\n    --category-list-font-colour: var(--base1);\n\n    --loader-background-colour: var(--base03);\n    --loader-outer-colour: var(--base1);\n    --loader-middle-colour: var(--base0);\n    --loader-inner-colour: var(--base00);\n\n\n    /* Operation colours */\n    --op-list-operation-font-colour: var(--base0);\n    --op-list-operation-bg-colour: var(--base03);\n    --op-list-operation-border-colour: var(--base02);\n\n    --rec-list-operation-font-colour: var(--base0);\n    --rec-list-operation-bg-colour: var(--base02);\n    --rec-list-operation-border-colour: var(--base01);\n\n    --selected-operation-font-color: var(--base1);\n    --selected-operation-bg-colour: var(--base02);\n    --selected-operation-border-colour: var(--base01);\n\n    --breakpoint-font-colour: var(--sol-red);\n    --breakpoint-bg-colour: var(--base02);\n    --breakpoint-border-colour: var(--base00);\n\n    --disabled-font-colour: var(--base01);\n    --disabled-bg-colour: var(--base03);\n    --disabled-border-colour: var(--base02);\n\n    --fc-operation-font-colour: var(--base1);\n    --fc-operation-bg-colour: var(--base02);\n    --fc-operation-border-colour: var(--base01);\n\n    --fc-breakpoint-operation-font-colour: var(--sol-orange);\n    --fc-breakpoint-operation-bg-colour: var(--base02);\n    --fc-breakpoint-operation-border-colour: var(--base00);\n\n\n    /* Operation arguments */\n    --op-title-font-weight: bold;\n    --arg-font-colour: var(--base0);\n    --arg-background: var(--base03);\n    --arg-border-colour: var(--base00);\n    --arg-disabled-background: var(--base03);\n    --arg-label-colour: var(--base1);\n\n\n    /* Operation buttons */\n    --disable-icon-colour: var(--base00);\n    --disable-icon-selected-colour: var(--sol-red);\n    --breakpoint-icon-colour: var(--base00);\n    --breakpoint-icon-selected-colour: var(--sol-red);\n\n    /* Buttons */\n    --btn-default-font-colour: var(--base0);\n    --btn-default-bg-colour: var(--base02);\n    --btn-default-border-colour: var(--base01);\n\n    --btn-default-hover-font-colour: var(--base1);\n    --btn-default-hover-bg-colour: var(--base01);\n    --btn-default-hover-border-colour: var(--base00);\n\n    --btn-success-font-colour: var(--base0);\n    --btn-success-bg-colour: var(--base03);\n    --btn-success-border-colour: var(--base00);\n\n    --btn-success-hover-font-colour: var(--base1);\n    --btn-success-hover-bg-colour: var(--base01);\n    --btn-success-hover-border-colour: var(--base00);\n\n    /* Highlighter colours */\n    --hl1: var(--base01);\n    --hl2: var(--sol-blue);\n    --hl3: var(--sol-green);\n    --hl4: var(--sol-yellow);\n    --hl5: var(--sol-magenta);\n\n\n    /* Scrollbar */\n    --scrollbar-track: var(--base03);\n    --scrollbar-thumb: var(--base00);\n    --scrollbar-hover: var(--base01);\n\n\n    /* Misc. */\n    --drop-file-border-colour: var(--base01);\n    --table-border-colour: var(--base01);\n    --popover-background: var(--base02);\n    --popover-border-colour: var(--base01);\n    --code-background: var(--base03);\n    --code-font-colour: var(--base1);\n    --input-highlight-colour: var(--base1);\n    --input-border-colour: var(--base0);\n}\n"
  },
  {
    "path": "src/web/stylesheets/themes/_solarizedLight.css",
    "content": "/**\n * Solarized light theme definitions\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n:root.solarizedLight {\n    --base03: #002b36;\n    --base02: #073642;\n    --base01: #586e75;\n    --base00: #657b83;\n    --base0: #839496;\n    --base1: #93a1a1;\n    --base2: #eee8d5;\n    --base3: #fdf6e3;\n    --sol-yellow: #b58900;\n    --sol-orange: #cb4b16;\n    --sol-red: #dc322f;\n    --sol-magenta: #d33682;\n    --sol-violet: #6c71c4;\n    --sol-blue: #268bd2;\n    --sol-cyan: #2aa198;\n    --sol-green: #859900;\n\n    --primary-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n        Roboto, \"Helvetica Neue\", Arial, sans-serif;\n    --primary-font-colour: var(--base00);\n    --primary-font-size: 14px;\n    --primary-line-height: 20px;\n\n    --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas,\n        \"Liberation Mono\", \"Courier New\", monospace;\n    --fixed-width-font-colour: inherit;\n    --fixed-width-font-size: inherit;\n\n    --subtext-font-colour: var(--base1);\n    --subtext-font-size: 13px;\n\n    --primary-background-colour: var(--base3);\n    --secondary-background-colour: var(--base2);\n\n    --primary-border-colour: var(--base0);\n    --secondary-border-colour: var(--base1);\n\n    --title-colour: var(--base01);\n    --title-weight: bold;\n    --title-background-colour: var(--base2);\n\n    --banner-font-colour: var(--base00);\n    --banner-bg-colour: var(--base3);\n    --banner-url-colour: var(--base01);\n\n    --category-list-font-colour: var(--base01);\n\n    --loader-background-colour: var(--base3);\n    --loader-outer-colour: var(--base01);\n    --loader-middle-colour: var(--base00);\n    --loader-inner-colour: var(--base0);\n\n\n    /* Operation colours */\n    --op-list-operation-font-colour: var(--base00);\n    --op-list-operation-bg-colour: var(--base3);\n    --op-list-operation-border-colour: var(--base2);\n\n    --rec-list-operation-font-colour: var(--base00);\n    --rec-list-operation-bg-colour: var(--base2);\n    --rec-list-operation-border-colour: var(--base1);\n\n    --selected-operation-font-color: var(--base01);\n    --selected-operation-bg-colour: var(--base2);\n    --selected-operation-border-colour: var(--base1);\n\n    --breakpoint-font-colour: var(--sol-red);\n    --breakpoint-bg-colour: var(--base2);\n    --breakpoint-border-colour: var(--base0);\n\n    --disabled-font-colour: var(--base1);\n    --disabled-bg-colour: var(--base3);\n    --disabled-border-colour: var(--base2);\n\n    --fc-operation-font-colour: var(--base01);\n    --fc-operation-bg-colour: var(--base2);\n    --fc-operation-border-colour: var(--base1);\n\n    --fc-breakpoint-operation-font-colour: var(--base02);\n    --fc-breakpoint-operation-bg-colour: var(--base1);\n    --fc-breakpoint-operation-border-colour: var(--base0);\n\n\n    /* Operation arguments */\n    --op-title-font-weight: bold;\n    --arg-font-colour: var(--base00);\n    --arg-background: var(--base3);\n    --arg-border-colour: var(--base0);\n    --arg-disabled-background: var(--base3);\n    --arg-label-colour: var(--base01);\n\n\n    /* Operation buttons */\n    --disable-icon-colour: #9e9e9e;\n    --disable-icon-selected-colour: #f44336;\n    --breakpoint-icon-colour: #9e9e9e;\n    --breakpoint-icon-selected-colour: #f44336;\n\n\n    /* Buttons */\n    --btn-default-font-colour: var(--base00);\n    --btn-default-bg-colour: var(--base2);\n    --btn-default-border-colour: var(--base1);\n\n    --btn-default-hover-font-colour: var(--base01);\n    --btn-default-hover-bg-colour: var(--base1);\n    --btn-default-hover-border-colour: var(--base0);\n\n    --btn-success-font-colour: var(--base00);\n    --btn-success-bg-colour: var(--base3);\n    --btn-success-border-colour: var(--base0);\n\n    --btn-success-hover-font-colour: var(--base01);\n    --btn-success-hover-bg-colour: var(--base1);\n    --btn-success-hover-border-colour: var(--base0);\n\n\n    /* Highlighter colours */\n    --hl1: var(--base1);\n    --hl2: var(--sol-blue);\n    --hl3: var(--sol-green);\n    --hl4: var(--sol-yellow);\n    --hl5: var(--sol-magenta);\n\n\n    /* Scrollbar */\n    --scrollbar-track: var(--base3);\n    --scrollbar-thumb: var(--base1);\n    --scrollbar-hover: var(--base0);\n\n\n    /* Misc. */\n    --drop-file-border-colour: var(--base1);\n    --table-border-colour: var(--base1);\n    --popover-background: var(--base2);\n    --popover-border-colour: var(--base1);\n    --code-background: var(--base3);\n    --code-font-colour: var(--base01);\n    --input-highlight-colour: var(--base01);\n    --input-border-colour: var(--base00);\n}\n"
  },
  {
    "path": "src/web/stylesheets/utils/_general.css",
    "content": "/**\n * General styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nbody {\n    font-family: var(--primary-font-family);\n    font-size: var(--primary-font-size);\n    line-height: var(--primary-line-height);\n    color: var(--primary-font-colour);\n    background-color: var(--primary-background-colour);\n}\n\n.subtext {\n    font-style: italic;\n    font-size: var(--subtext-font-size);\n    color: var(--subtext-font-colour);\n}\n\n.data-text {\n    font-family: var(--fixed-width-font-family);\n}\n\n.word-wrap {\n    white-space: pre !important;\n    word-wrap: normal !important;\n    overflow-x: scroll !important;\n}\n\n.clearfix {\n    clear: both;\n    height: 0;\n    line-height: 0;\n}\n\n.hidden {\n    display: none;\n}\n\n.blur {\n    color: transparent !important;\n    text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important;\n}\n\n.no-select {\n    user-select: none;\n}\n\n.inline-icon {\n    font-size: 12px;\n    padding-left: 2px;\n}\n\n.modal-icon {\n    position: absolute;\n    right: 25px;\n}\n\n.konami {\n    transform: rotate(180deg);\n}\n\n::-webkit-scrollbar {\n    width: 10px;\n    height: 10px;\n}\n\n::-webkit-scrollbar-track {\n    background-color: var(--scrollbar-track);\n}\n\n::-webkit-scrollbar-thumb {\n    background-color: var(--scrollbar-thumb);\n}\n\n::-webkit-scrollbar-thumb:hover {\n    background-color: var(--scrollbar-hover);\n}\n\n::-webkit-scrollbar-corner {\n    background-color: var(--scrollbar-track);\n}\n\n/* Highlighters */\n.hl1 { background-color: var(--hl1); }\n.hl2 { background-color: var(--hl2); }\n.hl3 { background-color: var(--hl3); } /* Half-Life 3 confirmed :O */\n.hl4 { background-color: var(--hl4); }\n.hl5 { background-color: var(--hl5); }\n"
  },
  {
    "path": "src/web/stylesheets/utils/_overrides.css",
    "content": "/**\n * Overrides for vendor styles\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\n/* Bootstrap */\n\n/* fallback */\n@font-face {\n    font-family: 'Material Icons';\n    font-style: normal;\n    font-weight: 400;\n    src: url(\"../static/fonts/MaterialIcons-Regular.ttf\") format('truetype');\n}\n\n.material-icons {\n    font-family: 'Material Icons';\n    font-weight: normal;\n    font-style: normal;\n    font-size: 24px;\n    line-height: 1;\n    letter-spacing: normal;\n    text-transform: none;\n    display: inline-block;\n    white-space: nowrap;\n    word-wrap: normal;\n    direction: ltr;\n    font-feature-settings: 'liga';\n    -webkit-font-smoothing: antialiased;\n}\n\n.form-group {\n    margin-bottom: 0;\n}\n\n\nbutton,\na:focus {\n    outline: none;\n}\n\n.btn.btn-raised.btn-secondary {\n    color: var(--btn-default-font-colour);\n    background-color: var(--btn-default-bg-colour);\n    border-color: var(--btn-default-border-colour);\n}\n\n.btn.btn-raised.btn-secondary:hover,\n.btn.btn-raised.btn-secondary:active,\n.btn.btn-raised.btn-secondary:hover:active {\n    color: var(--btn-default-hover-font-colour);\n    background-color: var(--btn-default-hover-bg-colour);\n    border-color: var(--btn-default-hover-border-colour);\n}\n\n.btn.btn-raised.btn-secondary:focus {\n    color: var(--btn-default-font-colour);\n    background-color: var(--btn-default-bg-colour);\n    border-color: var(--btn-default-hover-border-colour);\n}\n\n.btn.btn-raised.btn-secondary[disabled]:hover {\n    background-color: var(--primary-background-colour);\n    border-color: var(--primary-border-colour);\n}\n\n.btn.btn-raised.btn-success {\n    color: var(--btn-success-font-colour);\n    background-color: var(--btn-success-bg-colour);\n    border-color: var(--btn-success-border-colour);\n}\n\n.btn.btn-raised.btn-success:hover,\n.btn.btn-raised.btn-success:active,\n.btn.btn-raised.btn-success:focus,\n.btn.btn-raised.btn-success:hover:active {\n    color: var(--btn-success-hover-font-colour);\n    background-color: var(--btn-success-hover-bg-colour);\n    border-color: var(--btn-success-hover-border-colour);\n}\n\nselect.form-control,\nselect.form-control:focus {\n    background-color: var(--primary-background-colour) !important;\n}\n\nselect.form-control:focus {\n    transition: none !important;\n}\n\nselect.form-control:not([size]):not([multiple]),\nselect.custom-file-control:not([size]):not([multiple]) {\n    height: unset !important;\n}\n\n.checkbox label,\n.checkbox-inline,\n.is-focused .checkbox-inline,\n.is-focused .checkbox-inline:hover,\n[class^=\"bmd-label\"],\n.form-control,\n.is-focused .form-control {\n    color: var(--primary-font-colour);\n}\n\n.form-control,\n.is-focused .form-control {\n    background-image:\n        linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px),\n        linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);\n}\n\ncode {\n    border: 0;\n    white-space: pre-wrap;\n    font-family: var(--fixed-width-font-family);\n    background-color: var(--code-background);\n    color: var(--code-font-colour);\n}\n\npre {\n    border-radius: 0 !important;\n    background-color: var(--secondary-background-colour);\n    border-color: var(--secondary-border-colour);\n    color: var(--fixed-width-font-colour);\n}\n\nblockquote {\n    font-size: inherit;\n    border-left-color: var(--secondary-border-colour);\n}\n\nblockquote a {\n    cursor: pointer;\n}\n\noptgroup {\n    font-weight: bold;\n}\n\n.panel-body:before,\n.panel-body:after {\n    content: \"\";\n}\n\n.table-nonfluid {\n    width: auto !important;\n}\n\n.table,\n.table-hover tbody tr:hover {\n    color: var(--primary-font-colour);\n}\n\n.table-bordered th,\n.table-bordered td {\n    border: 1px solid var(--table-border-colour);\n}\n\n.popover {\n    background-color: var(--popover-background);\n    border-color: var(--popover-border-colour);\n}\n\n.popover-body {\n    max-height: 95vh;\n    overflow-y: auto;\n    color: var(--primary-font-colour);\n}\n\n.bs-popover-right>.arrow {\n    border-right-color: var(--popover-border-colour);\n}\n\n.bs-popover-right>.arrow:after {\n    border-right-color: var(--popover-background);\n}\n\n.nav-tabs .nav-link {\n    color: var(--subtext-font-colour);\n}\n\n.nav-tabs>li>a.nav-link.active,\n.nav-tabs>li>a.nav-link.active:focus,\n.nav-tabs>li>a.nav-link.active:hover {\n    background-color: var(--secondary-background-colour);\n    border-color: var(--secondary-border-colour);\n    border-bottom-color: transparent;\n    color: var(--primary-font-colour);\n}\n\n.nav-tabs {\n    border-color: var(--primary-border-colour);\n}\n\n.nav a.nav-link:focus,\n.nav a.nav-link:hover {\n    background-color: var(--secondary-border-colour);\n}\n\n.nav-tabs a.nav-link:hover {\n    border-color: var(--secondary-border-colour) var(--secondary-border-colour) var(--primary-border-colour);\n}\n\n.dropdown-menu {\n    background-color: var(--primary-background-colour);\n}\n\n.dropdown-menu a {\n    color: var(--primary-font-colour);\n}\n\n.dropdown-menu a:focus,\n.dropdown-menu a:hover {\n    background-color: var(--secondary-background-colour);\n    color: var(--primary-font-colour);\n}\n\n.input-group-addon:not(:first-child):not(:last-child) {\n    border-left: 0;\n    border-right: 0;\n}\n\n.input-group-btn:first-child>.btn {\n    border-right: 0;\n}\n\n\n/* Sortable */\n\n.sortable-ghost {\n    opacity: 0.6;\n}\n\n\n/* Bootstrap Colorpicker */\n\n.colorpicker-element {\n    float: left;\n    margin-right: 15px;\n}\n\n.colorpicker-color,\n.colorpicker-color div {\n    height: 100px;\n}\n\n\n/* Bootstrap form inside CodeMirror editor */\n\n.cm-panel > .bmd-form-group {\n    padding-top: 0;\n}\n\n\n/* CodeMirror */\n\n.ͼ2 .cm-specialChar,\n.cm-specialChar {\n    color: red;\n}\n"
  },
  {
    "path": "src/web/utils/copyOverride.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n *\n * In order to render whitespace characters as control character pictures in the output, even\n * when they are the designated line separator, CyberChef sometimes chooses to represent them\n * internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas).\n * See `Utils.escapeWhitespace()` for an example of this.\n *\n * The `renderSpecialChar()` function understands that it should display these characters as\n * control pictures. When copying data from the Output, we need to replace these PUA characters\n * with their original values, so we override the DOM \"copy\" event and modify the copied data\n * if required. This handler is based closely on the built-in CodeMirror handler and defers to the\n * built-in handler if PUA characters are not present in the copied data, in order to minimise the\n * impact of breaking changes.\n */\n\nimport {EditorView} from \"@codemirror/view\";\n\n/**\n * Copies the currently selected text from the state doc.\n * Based on the built-in implementation with a few unrequired bits taken out:\n * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L604\n *\n * @param {EditorState} state\n * @returns {Object}\n */\nfunction copiedRange(state) {\n    const content = [];\n    let linewise = false;\n    for (const range of state.selection.ranges) if (!range.empty) {\n        content.push(state.sliceDoc(range.from, range.to));\n    }\n    if (!content.length) {\n        // Nothing selected, do a line-wise copy\n        let upto = -1;\n        for (const {from} of state.selection.ranges) {\n            const line = state.doc.lineAt(from);\n            if (line.number > upto) {\n                content.push(line.text);\n            }\n            upto = line.number;\n        }\n        linewise = true;\n    }\n\n    return {text: content.join(state.lineBreak), linewise};\n}\n\n/**\n * Regex to match characters in the Private Use Area of the Unicode table.\n */\nconst PUARegex = new RegExp(\"[\\ue000-\\uf8ff]\");\nconst PUARegexG = new RegExp(\"[\\ue000-\\uf8ff]\", \"g\");\n/**\n * Regex tto match Unicode Control Pictures.\n */\nconst CPRegex = new RegExp(\"[\\u2400-\\u243f]\");\nconst CPRegexG = new RegExp(\"[\\u2400-\\u243f]\", \"g\");\n\n/**\n * Overrides the DOM \"copy\" handler in the CodeMirror editor in order to return the original\n * values of control characters that have been represented in the Unicode Private Use Area for\n * visual purposes.\n * Based on the built-in copy handler with some modifications:\n * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L629\n *\n * This handler will defer to the built-in version if no PUA characters are present.\n *\n * @returns {Extension}\n */\nexport function copyOverride() {\n    return EditorView.domEventHandlers({\n        copy(event, view) {\n            const {text, linewise} = copiedRange(view.state);\n            if (!text && !linewise) return;\n\n            // If there are no PUA chars in the copied text, return false and allow the built-in\n            // copy handler to fire\n            if (!PUARegex.test(text)) return false;\n\n            // If PUA chars are detected, modify them back to their original values and copy that instead\n            const rawText = text.replace(PUARegexG, function(c) {\n                return String.fromCharCode(c.charCodeAt(0) - 0xe000);\n            });\n\n            event.preventDefault();\n            event.clipboardData.clearData();\n            event.clipboardData.setData(\"text/plain\", rawText);\n\n            // Returning true prevents CodeMirror default handlers from firing\n            return true;\n        }\n    });\n}\n\n\n/**\n * Handler for copy events in output-html decorations. If there are control pictures present,\n * this handler will convert them back to their raw form before copying. If there are no\n * control pictures present, it will do nothing and defer to the default browser handler.\n *\n * @param {ClipboardEvent} event\n * @returns {boolean}\n */\nexport function htmlCopyOverride(event) {\n    const text = window.getSelection().toString();\n    if (!text) return;\n\n    // If there are no control picture chars in the copied text, return false and allow the built-in\n    // copy handler to fire\n    if (!CPRegex.test(text)) return false;\n\n    // If control picture chars are detected, modify them back to their original values and copy that instead\n    const rawText = text.replace(CPRegexG, function(c) {\n        return String.fromCharCode(c.charCodeAt(0) - 0x2400);\n    });\n\n    event.preventDefault();\n    event.clipboardData.clearData();\n    event.clipboardData.setData(\"text/plain\", rawText);\n\n    return true;\n}\n"
  },
  {
    "path": "src/web/utils/editorUtils.mjs",
    "content": "/**\n * CodeMirror utilities that are relevant to both the input and output\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport Utils from \"../../core/Utils.mjs\";\n\n// Descriptions for named control characters\nconst Names = {\n    0: \"null\",\n    7: \"bell\",\n    8: \"backspace\",\n    10: \"line feed\",\n    11: \"vertical tab\",\n    13: \"carriage return\",\n    27: \"escape\",\n    8203: \"zero width space\",\n    8204: \"zero width non-joiner\",\n    8205: \"zero width joiner\",\n    8206: \"left-to-right mark\",\n    8207: \"right-to-left mark\",\n    8232: \"line separator\",\n    8237: \"left-to-right override\",\n    8238: \"right-to-left override\",\n    8294: \"left-to-right isolate\",\n    8295: \"right-to-left isolate\",\n    8297: \"pop directional isolate\",\n    8233: \"paragraph separator\",\n    65279: \"zero width no-break space\",\n    65532: \"object replacement\"\n};\n\n// Regex for Special Characters to be replaced\nconst UnicodeRegexpSupport = /x/.unicode != null ? \"gu\" : \"g\";\nconst Specials = new RegExp(\"[\\u0000-\\u0008\\u000a-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u200b\\u200e\\u200f\\u2028\\u2029\\u202d\\u202e\\u2066\\u2067\\u2069\\ufeff\\ufff9-\\ufffc\\ue000-\\uf8ff]\", UnicodeRegexpSupport);\n\n\n/**\n * Override for rendering special characters.\n * Should mirror the toDOM function in\n * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L153\n * But reverts the replacement of line feeds with newline control pictures.\n *\n * @param {number} code\n * @param {string} desc\n * @param {string} placeholder\n * @returns {element}\n */\nexport function renderSpecialChar(code, desc, placeholder) {\n    const s = document.createElement(\"span\");\n\n    // CodeMirror changes 0x0a to \"NL\" instead of \"LF\". We change it back along with its description.\n    if (code === 0x0a) {\n        placeholder = \"\\u240a\";\n        desc = desc.replace(\"newline\", \"line feed\");\n    }\n\n    // Render CyberChef escaped characters correctly - see Utils.escapeWhitespace\n    if (code >= 0xe000 && code <= 0xf8ff) {\n        code = code - 0xe000;\n        placeholder = String.fromCharCode(0x2400 + code);\n        desc = \"Control character \" + (Names[code] || \"0x\" + code.toString(16));\n    }\n\n    s.textContent = placeholder;\n    s.title = desc;\n    s.setAttribute(\"aria-label\", desc);\n    s.className = \"cm-specialChar\";\n    return s;\n}\n\n\n/**\n * Given a string, returns that string with any control characters replaced with HTML\n * renderings of control pictures.\n *\n * @param {string} str\n * @param {boolean} [preserveWs=false]\n * @param {string} [lineBreak=\"\\n\"]\n * @returns {html}\n */\nexport function escapeControlChars(str, preserveWs=false, lineBreak=\"\\n\") {\n    if (!preserveWs)\n        str = Utils.escapeWhitespace(str);\n\n    return str.replace(Specials, function(c) {\n        if (lineBreak.includes(c)) return c;\n        const code = c.charCodeAt(0);\n        const desc = \"Control character \" + (Names[code] || \"0x\" + code.toString(16));\n        const placeholder = code > 32 ? \"\\u2022\" : String.fromCharCode(9216 + code);\n        const n = renderSpecialChar(code, desc, placeholder);\n        return n.outerHTML;\n    });\n}\n\n/**\n * Convert and EOL sequence to its name\n */\nexport const eolSeqToCode = {\n    \"\\u000a\": \"LF\",\n    \"\\u000b\": \"VT\",\n    \"\\u000c\": \"FF\",\n    \"\\u000d\": \"CR\",\n    \"\\u000d\\u000a\": \"CRLF\",\n    \"\\u0085\": \"NEL\",\n    \"\\u2028\": \"LS\",\n    \"\\u2029\": \"PS\"\n};\n\n/**\n * Convert an EOL name to its sequence\n */\nexport const eolCodeToSeq = {\n    \"LF\": \"\\u000a\",\n    \"VT\": \"\\u000b\",\n    \"FF\": \"\\u000c\",\n    \"CR\": \"\\u000d\",\n    \"CRLF\": \"\\u000d\\u000a\",\n    \"NEL\": \"\\u0085\",\n    \"LS\": \"\\u2028\",\n    \"PS\": \"\\u2029\"\n};\n\nexport const eolCodeToName = {\n    \"LF\": \"Line Feed\",\n    \"VT\": \"Vertical Tab\",\n    \"FF\": \"Form Feed\",\n    \"CR\": \"Carriage Return\",\n    \"CRLF\": \"Carriage Return + Line Feed\",\n    \"NEL\": \"Next Line\",\n    \"LS\": \"Line Separator\",\n    \"PS\": \"Paragraph Separator\"\n};\n"
  },
  {
    "path": "src/web/utils/fileDetails.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport {showSidePanel} from \"./sidePanel.mjs\";\nimport Utils from \"../../core/Utils.mjs\";\nimport {isImage, detectFileType} from \"../../core/lib/FileType.mjs\";\n\n/**\n * A File Details extension for CodeMirror\n */\nclass FileDetailsPanel {\n\n    /**\n     * FileDetailsPanel constructor\n     * @param {Object} opts\n     */\n    constructor(opts) {\n        this.fileDetails = opts?.fileDetails;\n        this.progress = opts?.progress ?? 0;\n        this.status = opts?.status;\n        this.buffer = opts?.buffer;\n        this.renderPreview = opts?.renderPreview;\n        this.toggleHandler = opts?.toggleHandler;\n        this.hidden = opts?.hidden;\n        this.dom = this.buildDOM();\n        this.renderFileThumb();\n    }\n\n    /**\n     * Builds the file details DOM tree\n     * @returns {DOMNode}\n     */\n    buildDOM() {\n        const dom = document.createElement(\"div\");\n\n        dom.className = \"cm-file-details\";\n        const fileThumb = require(\"../static/images/file-128x128.png\");\n        dom.innerHTML = `\n            <div class=\"${this.hidden ? \"file-details-toggle-hidden\" : \"file-details-toggle-shown\"}\"\n                data-toggle=\"tooltip\"\n                title=\"${this.hidden ? \"Show\" : \"Hide\"} file details\">\n                ${this.hidden ? \"&#10096;\" : \"&#10097;\"}\n            </div>\n            <p class=\"file-details-heading\">File details</p>\n            <img aria-hidden=\"true\" src=\"${fileThumb}\" alt=\"File icon\" class=\"file-details-thumbnail\"/>\n            <table class=\"file-details-data\">\n                <tr>\n                    <td>Name:</td>\n                    <td class=\"file-details-name\" title=\"${Utils.escapeHtml(this.fileDetails?.name)}\">\n                        ${Utils.escapeHtml(this.fileDetails?.name)}\n                    </td>\n                </tr>\n                <tr>\n                    <td>Size:</td>\n                    <td class=\"file-details-size\" title=\"${Utils.escapeHtml(this.fileDetails?.size)} bytes\">\n                        ${Utils.escapeHtml(this.fileDetails?.size)} bytes\n                    </td>\n                </tr>\n                <tr>\n                    <td>Type:</td>\n                    <td class=\"file-details-type\" title=\"${Utils.escapeHtml(this.fileDetails?.type)}\">\n                        ${Utils.escapeHtml(this.fileDetails?.type)}\n                    </td>\n                </tr>\n                <tr>\n                    <td>Loaded:</td>\n                    <td class=\"file-details-${this.status === \"error\" ? \"error\" : \"loaded\"}\">\n                        ${this.status === \"error\" ? \"Error\" : this.progress + \"%\"}\n                    </td>\n                </tr>\n            </table>\n        `;\n\n        dom.querySelector(\".file-details-toggle-shown,.file-details-toggle-hidden\")\n            .addEventListener(\"click\", this.toggleHandler, false);\n\n        return dom;\n    }\n\n    /**\n     * Render the file thumbnail\n     */\n    renderFileThumb() {\n        if (!this.renderPreview) {\n            this.resetFileThumb();\n            return;\n        }\n        const fileThumb = this.dom.querySelector(\".file-details-thumbnail\");\n        const fileType = this.dom.querySelector(\".file-details-type\");\n        const fileBuffer = new Uint8Array(this.buffer);\n        const type = isImage(fileBuffer);\n\n        if (type && type !== \"image/tiff\" && fileBuffer.byteLength <= 512000) {\n            // Most browsers don't support displaying TIFFs, so ignore them\n            // Don't render images over 512,000 bytes\n            const blob = new Blob([fileBuffer], {type: type}),\n                url = URL.createObjectURL(blob);\n            fileThumb.src = url;\n        } else {\n            this.resetFileThumb();\n        }\n        fileType.textContent = type ? type : detectFileType(fileBuffer)[0]?.mime ?? \"unknown\";\n    }\n\n    /**\n     * Reset the file thumbnail to the default icon\n     */\n    resetFileThumb() {\n        const fileThumb = this.dom.querySelector(\".file-details-thumbnail\");\n        fileThumb.src = require(\"../static/images/file-128x128.png\");\n    }\n\n}\n\n/**\n * A panel constructor factory building a panel that displays file details\n * @param {Object} opts\n * @returns {Function<PanelConstructor>}\n */\nfunction makePanel(opts) {\n    const fdPanel = new FileDetailsPanel(opts);\n\n    return (view) => {\n        return {\n            dom: fdPanel.dom,\n            width: opts?.hidden ? 1 : 200,\n            update(update) {\n            },\n            mount() {\n                $(\"[data-toggle='tooltip']\").tooltip();\n            }\n        };\n    };\n}\n\n/**\n * A function that build the extension that enables the panel in an editor.\n * @param {Object} opts\n * @returns {Extension}\n */\nexport function fileDetailsPanel(opts) {\n    const panelMaker = makePanel(opts);\n    return showSidePanel.of(panelMaker);\n}\n"
  },
  {
    "path": "src/web/utils/htmlWidget.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport {WidgetType, Decoration, ViewPlugin} from \"@codemirror/view\";\nimport {escapeControlChars} from \"./editorUtils.mjs\";\nimport {htmlCopyOverride} from \"./copyOverride.mjs\";\nimport Utils from \"../../core/Utils.mjs\";\n\n\n/**\n * Adds an HTML widget to the Code Mirror editor\n */\nclass HTMLWidget extends WidgetType {\n\n    /**\n     * HTMLWidget consructor\n     */\n    constructor(html, view) {\n        super();\n        this.html = html;\n        this.view = view;\n    }\n\n    /**\n     * Builds the DOM node\n     * @returns {DOMNode}\n     */\n    toDOM() {\n        const wrap = document.createElement(\"span\");\n        wrap.setAttribute(\"id\", \"output-html\");\n        wrap.innerHTML = this.html;\n\n        // Find text nodes and replace unprintable chars with control codes\n        this.walkTextNodes(wrap);\n\n        // Add a handler for copy events to ensure the control codes are copied correctly\n        wrap.addEventListener(\"copy\", htmlCopyOverride);\n        return wrap;\n    }\n\n    /**\n     * Walks all text nodes in a given element\n     * @param {DOMNode} el\n     */\n    walkTextNodes(el) {\n        for (const node of el.childNodes) {\n            switch (node.nodeType) {\n                case Node.TEXT_NODE:\n                    this.replaceControlChars(node);\n                    break;\n                default:\n                    if (node.nodeName !== \"SCRIPT\" &&\n                        node.nodeName !== \"STYLE\")\n                        this.walkTextNodes(node);\n                    break;\n            }\n        }\n    }\n\n    /**\n     * Renders control characters in text nodes\n     * @param {DOMNode} textNode\n     */\n    replaceControlChars(textNode) {\n        // .nodeValue unencodes HTML encoding such as &lt; to \"<\"\n        // We must remember to escape any potential HTML in TextNodes as we do not\n        // want to render it.\n        const textValue = Utils.escapeHtml(textNode.nodeValue);\n        const val = escapeControlChars(textValue, true, this.view.state.lineBreak);\n        if (val.length !== textNode.nodeValue.length) {\n            const node = document.createElement(\"span\");\n            node.innerHTML = val;\n            textNode.parentNode.replaceChild(node, textNode);\n        }\n    }\n\n}\n\n/**\n * Decorator function to provide a set of widgets for the editor DOM\n * @param {EditorView} view\n * @param {string} html\n * @returns {DecorationSet}\n */\nfunction decorateHTML(view, html) {\n    const widgets = [];\n    if (html.length) {\n        const deco = Decoration.widget({\n            widget: new HTMLWidget(html, view),\n            side: 1\n        });\n        widgets.push(deco.range(0));\n    }\n    return Decoration.set(widgets);\n}\n\n\n/**\n * An HTML Plugin builder\n * @param {Object} htmlOutput\n * @returns {ViewPlugin}\n */\nexport function htmlPlugin(htmlOutput) {\n    const plugin = ViewPlugin.fromClass(\n        class {\n            /**\n             * Plugin constructor\n             * @param {EditorView} view\n             */\n            constructor(view) {\n                this.htmlOutput = htmlOutput;\n                this.decorations = decorateHTML(view, this.htmlOutput.html);\n            }\n\n            /**\n             * Editor update listener\n             * @param {ViewUpdate} update\n             */\n            update(update) {\n                if (this.htmlOutput.changed) {\n                    this.decorations = decorateHTML(update.view, this.htmlOutput.html);\n                    this.htmlOutput.changed = false;\n                }\n            }\n        }, {\n            decorations: v => v.decorations\n        }\n    );\n\n    return plugin;\n}\n"
  },
  {
    "path": "src/web/utils/sidePanel.mjs",
    "content": "/**\n * A modification of the CodeMirror Panel extension to enable panels to the\n * left and right of the editor.\n * Based on code here: https://github.com/codemirror/view/blob/main/src/panel.ts\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport {EditorView, ViewPlugin} from \"@codemirror/view\";\nimport {Facet} from \"@codemirror/state\";\n\nconst panelConfig = Facet.define({\n    combine(configs) {\n        let leftContainer, rightContainer;\n        for (const c of configs) {\n            leftContainer = leftContainer || c.leftContainer;\n            rightContainer = rightContainer || c.rightContainer;\n        }\n        return {leftContainer, rightContainer};\n    }\n});\n\n/**\n * Configures the panel-managing extension.\n * @param {PanelConfig} config\n * @returns Extension\n */\nexport function panels(config) {\n    return config ? [panelConfig.of(config)] : [];\n}\n\n/**\n * Get the active panel created by the given constructor, if any.\n * This can be useful when you need access to your panels' DOM\n * structure.\n * @param {EditorView} view\n * @param {PanelConstructor} panel\n * @returns {Panel}\n */\nexport function getPanel(view, panel) {\n    const plugin = view.plugin(panelPlugin);\n    const index = plugin ? plugin.specs.indexOf(panel) : -1;\n    return index > -1 ? plugin.panels[index] : null;\n}\n\nconst panelPlugin = ViewPlugin.fromClass(class {\n\n    /**\n     * @param {EditorView} view\n     */\n    constructor(view) {\n        this.input = view.state.facet(showSidePanel);\n        this.specs = this.input.filter(s => s);\n        this.panels = this.specs.map(spec => spec(view));\n        const conf = view.state.facet(panelConfig);\n        this.left = new PanelGroup(view, true, conf.leftContainer);\n        this.right = new PanelGroup(view, false, conf.rightContainer);\n        this.left.sync(this.panels.filter(p => p.left));\n        this.right.sync(this.panels.filter(p => !p.left));\n        for (const p of this.panels) {\n            p.dom.classList.add(\"cm-panel\");\n            if (p.mount) p.mount();\n        }\n    }\n\n    /**\n     * @param {ViewUpdate} update\n     */\n    update(update) {\n        const conf = update.state.facet(panelConfig);\n        if (this.left.container !== conf.leftContainer) {\n            this.left.sync([]);\n            this.left = new PanelGroup(update.view, true, conf.leftContainer);\n        }\n        if (this.right.container !== conf.rightContainer) {\n            this.right.sync([]);\n            this.right = new PanelGroup(update.view, false, conf.rightContainer);\n        }\n        this.left.syncClasses();\n        this.right.syncClasses();\n        const input = update.state.facet(showSidePanel);\n        if (input !== this.input) {\n            const specs = input.filter(x => x);\n            const panels = [], left = [], right = [], mount = [];\n            for (const spec of specs) {\n                const known = this.specs.indexOf(spec);\n                let panel;\n                if (known < 0) {\n                    panel = spec(update.view);\n                    mount.push(panel);\n                } else {\n                    panel = this.panels[known];\n                    if (panel.update) panel.update(update);\n                }\n                panels.push(panel)\n                ;(panel.left ? left : right).push(panel);\n            }\n            this.specs = specs;\n            this.panels = panels;\n            this.left.sync(left);\n            this.right.sync(right);\n            for (const p of mount) {\n                p.dom.classList.add(\"cm-panel\");\n                if (p.mount) p.mount();\n            }\n        } else {\n            for (const p of this.panels) if (p.update) p.update(update);\n        }\n    }\n\n    /**\n     * Destroy panel\n     */\n    destroy() {\n        this.left.sync([]);\n        this.right.sync([]);\n    }\n}, {\n    // provide: PluginField.scrollMargins.from(value => ({left: value.left.scrollMargin(), right: value.right.scrollMargin()}))\n});\n\n/**\n * PanelGroup\n */\nclass PanelGroup {\n\n    /**\n     * @param {EditorView} view\n     * @param {boolean} left\n     * @param {HTMLElement} container\n     */\n    constructor(view, left, container) {\n        this.view = view;\n        this.left = left;\n        this.container = container;\n        this.dom = undefined;\n        this.classes = \"\";\n        this.panels = [];\n        this.syncClasses();\n    }\n\n    /**\n     * @param {Panel[]} panels\n     */\n    sync(panels) {\n        for (const p of this.panels) if (p.destroy && panels.indexOf(p) < 0) p.destroy();\n        this.panels = panels;\n        this.syncDOM();\n    }\n\n    /**\n     * Synchronise the DOM\n     */\n    syncDOM() {\n        if (this.panels.length === 0) {\n            if (this.dom) {\n                this.dom.remove();\n                this.dom = undefined;\n                this.setScrollerMargin(0);\n            }\n            return;\n        }\n\n        const parent = this.container || this.view.dom;\n        if (!this.dom) {\n            this.dom = document.createElement(\"div\");\n            this.dom.className = this.left ? \"cm-side-panels cm-panels-left\" : \"cm-side-panels cm-panels-right\";\n            parent.insertBefore(this.dom, parent.firstChild);\n        }\n\n        let curDOM = this.dom.firstChild;\n        let bufferWidth = 0;\n        for (const panel of this.panels) {\n            bufferWidth += panel.width;\n            if (panel.dom.parentNode === this.dom) {\n                while (curDOM !== panel.dom) curDOM = rm(curDOM);\n                curDOM = curDOM.nextSibling;\n            } else {\n                this.dom.insertBefore(panel.dom, curDOM);\n                panel.dom.style.width = panel.width + \"px\";\n                this.dom.style.width = bufferWidth + \"px\";\n            }\n        }\n        while (curDOM) curDOM = rm(curDOM);\n\n        this.setScrollerMargin(bufferWidth);\n    }\n\n    /**\n     * Sets the margin of the cm-scroller element to make room for the panel\n     */\n    setScrollerMargin(width) {\n        const parent = this.container || this.view.dom;\n        const margin = this.left ? \"marginLeft\" : \"marginRight\";\n        parent.querySelector(\".cm-scroller\").style[margin] = width + \"px\";\n    }\n\n    /**\n     *\n     */\n    scrollMargin() {\n        return !this.dom || this.container ? 0 :\n            Math.max(0, this.left ?\n                this.dom.getBoundingClientRect().right - Math.max(0, this.view.scrollDOM.getBoundingClientRect().left) :\n                Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().right) - this.dom.getBoundingClientRect().left);\n    }\n\n    /**\n     *\n     */\n    syncClasses() {\n        if (!this.container || this.classes === this.view.themeClasses) return;\n        for (const cls of this.classes.split(\" \")) if (cls) this.container.classList.remove(cls);\n        for (const cls of (this.classes = this.view.themeClasses).split(\" \")) if (cls) this.container.classList.add(cls);\n    }\n}\n\n/**\n * @param {ChildNode} node\n * @returns HTMLElement\n */\nfunction rm(node) {\n    const next = node.nextSibling;\n    node.remove();\n    return next;\n}\n\nconst baseTheme = EditorView.baseTheme({\n    \".cm-side-panels\": {\n        boxSizing: \"border-box\",\n        position: \"absolute\",\n        height: \"100%\",\n        top: 0,\n        bottom: 0\n    },\n    \"&light .cm-side-panels\": {\n        backgroundColor: \"#f5f5f5\",\n        color: \"black\"\n    },\n    \"&light .cm-panels-left\": {\n        borderRight: \"1px solid #ddd\",\n        left: 0\n    },\n    \"&light .cm-panels-right\": {\n        borderLeft: \"1px solid #ddd\",\n        right: 0\n    },\n    \"&dark .cm-side-panels\": {\n        backgroundColor: \"#333338\",\n        color: \"white\"\n    }\n});\n\n/**\n * Opening a panel is done by providing a constructor function for\n * the panel through this facet. (The panel is closed again when its\n * constructor is no longer provided.) Values of `null` are ignored.\n */\nexport const showSidePanel = Facet.define({\n    enables: [panelPlugin, baseTheme]\n});\n"
  },
  {
    "path": "src/web/utils/statusBar.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport {showPanel} from \"@codemirror/view\";\nimport {CHR_ENC_SIMPLE_LOOKUP, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from \"../../core/lib/ChrEnc.mjs\";\nimport { eolCodeToName, eolSeqToCode } from \"./editorUtils.mjs\";\n\n/**\n * A Status bar extension for CodeMirror\n */\nclass StatusBarPanel {\n\n    /**\n     * StatusBarPanel constructor\n     * @param {Object} opts\n     */\n    constructor(opts) {\n        this.label = opts.label;\n        this.timing = opts.timing;\n        this.tabNumGetter = opts.tabNumGetter;\n        this.eolHandler = opts.eolHandler;\n        this.chrEncHandler = opts.chrEncHandler;\n        this.chrEncGetter = opts.chrEncGetter;\n        this.getEncodingState = opts.getEncodingState;\n        this.getEOLState = opts.getEOLState;\n        this.htmlOutput = opts.htmlOutput;\n\n        this.eolVal = null;\n        this.chrEncVal = null;\n\n        this.dom = this.buildDOM();\n    }\n\n    /**\n     * Builds the status bar DOM tree\n     * @returns {DOMNode}\n     */\n    buildDOM() {\n        const dom = document.createElement(\"div\");\n        const lhs = document.createElement(\"div\");\n        const rhs = document.createElement(\"div\");\n\n        dom.className = \"cm-status-bar\";\n        dom.setAttribute(\"data-help-title\", `${this.label} status bar`);\n        dom.setAttribute(\"data-help\", `This status bar provides information about data in the ${this.label}. Help topics are available for each of the components by activating help when hovering over them.`);\n        lhs.innerHTML = this.constructLHS();\n        rhs.innerHTML = this.constructRHS();\n\n        dom.appendChild(lhs);\n        dom.appendChild(rhs);\n\n        // Event listeners\n        dom.querySelectorAll(\".cm-status-bar-select-btn\").forEach(\n            el => el.addEventListener(\"click\", this.showDropUp.bind(this), false)\n        );\n        dom.querySelector(\".eol-select\").addEventListener(\"click\", this.eolSelectClick.bind(this), false);\n        dom.querySelector(\".chr-enc-select\").addEventListener(\"click\", this.chrEncSelectClick.bind(this), false);\n        dom.querySelector(\".cm-status-bar-filter-input\").addEventListener(\"keyup\", this.chrEncFilter.bind(this), false);\n\n        return dom;\n    }\n\n    /**\n     * Handler for dropup clicks\n     * Shows/Hides the dropup\n     * @param {Event} e\n     */\n    showDropUp(e) {\n        const el = e.target\n            .closest(\".cm-status-bar-select\")\n            .querySelector(\".cm-status-bar-select-content\");\n        const btn = e.target.closest(\".cm-status-bar-select-btn\");\n\n        if (btn.classList.contains(\"disabled\")) return;\n\n        el.classList.add(\"show\");\n\n        // Focus the filter input if present\n        const filter = el.querySelector(\".cm-status-bar-filter-input\");\n        if (filter) filter.focus();\n\n        // Set up a listener to close the menu if the user clicks outside of it\n        hideOnClickOutside(el, e);\n    }\n\n    /**\n     * Handler for EOL Select clicks\n     * Sets the line separator\n     * @param {Event} e\n     */\n    eolSelectClick(e) {\n        // preventDefault is required to stop the URL being modified and popState being triggered\n        e.preventDefault();\n\n        const eolCode = e.target.getAttribute(\"data-val\");\n        if (!eolCode) return;\n\n        // Call relevant EOL change handler\n        this.eolHandler(e.target.getAttribute(\"data-val\"), true);\n\n        hideElement(e.target.closest(\".cm-status-bar-select-content\"));\n    }\n\n    /**\n     * Handler for Chr Enc Select clicks\n     * Sets the character encoding\n     * @param {Event} e\n     */\n    chrEncSelectClick(e) {\n        // preventDefault is required to stop the URL being modified and popState being triggered\n        e.preventDefault();\n\n        const chrEncVal = parseInt(e.target.getAttribute(\"data-val\"), 10);\n\n        if (isNaN(chrEncVal)) return;\n\n        this.chrEncHandler(chrEncVal, true);\n        this.updateCharEnc(chrEncVal);\n        hideElement(e.target.closest(\".cm-status-bar-select-content\"));\n    }\n\n    /**\n     * Handler for Chr Enc keyup events\n     * Filters the list of selectable character encodings\n     * @param {Event} e\n     */\n    chrEncFilter(e) {\n        const input = e.target;\n        const filter = input.value.toLowerCase();\n        const div = input.closest(\".cm-status-bar-select-content\");\n        const a = div.getElementsByTagName(\"a\");\n        for (let i = 0; i < a.length; i++) {\n            const txtValue = a[i].textContent || a[i].innerText;\n            if (txtValue.toLowerCase().includes(filter)) {\n                a[i].style.display = \"block\";\n            } else {\n                a[i].style.display = \"none\";\n            }\n        }\n    }\n\n    /**\n     * Counts the stats of a document\n     * @param {EditorState} state\n     */\n    updateStats(state) {\n        const length = this.dom.querySelector(\".stats-length-value\"),\n            lines = this.dom.querySelector(\".stats-lines-value\");\n\n        let docLength = state.doc.length;\n        // CodeMirror always counts line breaks as one character.\n        // We want to show an accurate reading of how many bytes there are.\n        if (state.lineBreak.length !== 1) {\n            docLength += (state.lineBreak.length * state.doc.lines) - state.doc.lines - 1;\n        }\n        length.textContent = docLength;\n        lines.textContent = state.doc.lines;\n    }\n\n    /**\n     * Gets the current selection info\n     * @param {EditorState} state\n     * @param {boolean} selectionSet\n     */\n    updateSelection(state, selectionSet) {\n        const selLen = state?.selection?.main ?\n            state.selection.main.to - state.selection.main.from :\n            0;\n\n        const selInfo = this.dom.querySelector(\".sel-info\"),\n            curOffsetInfo = this.dom.querySelector(\".cur-offset-info\");\n\n        if (!selectionSet) {\n            selInfo.style.display = \"none\";\n            curOffsetInfo.style.display = \"none\";\n            return;\n        }\n\n        // CodeMirror always counts line breaks as one character.\n        // We want to show an accurate reading of how many bytes there are.\n        let from = state.selection.main.from,\n            to = state.selection.main.to;\n        if (state.lineBreak.length !== 1) {\n            const fromLine = state.doc.lineAt(from).number;\n            const toLine = state.doc.lineAt(to).number;\n            from += (state.lineBreak.length * fromLine) - fromLine - 1;\n            to += (state.lineBreak.length * toLine) - toLine - 1;\n        }\n\n        if (selLen > 0) { // Range\n            const start = this.dom.querySelector(\".sel-start-value\"),\n                end = this.dom.querySelector(\".sel-end-value\"),\n                length = this.dom.querySelector(\".sel-length-value\");\n\n            selInfo.style.display = \"inline-block\";\n            curOffsetInfo.style.display = \"none\";\n            start.textContent = from;\n            end.textContent = to;\n            length.textContent = to - from;\n        } else { // Position\n            const offset = this.dom.querySelector(\".cur-offset-value\");\n\n            selInfo.style.display = \"none\";\n            curOffsetInfo.style.display = \"inline-block\";\n            offset.textContent = from;\n        }\n    }\n\n    /**\n     * Sets the current EOL separator in the status bar\n     * @param {EditorState} state\n     */\n    updateEOL(state) {\n        if (this.getEOLState() < 2 && state.lineBreak === this.eolVal) return;\n\n        const val = this.dom.querySelector(\".eol-value\");\n        const button = val.closest(\".cm-status-bar-select-btn\");\n        let eolCode = eolSeqToCode[state.lineBreak];\n        let eolName = eolCodeToName[eolCode];\n\n        switch (this.getEOLState()) {\n            case 1: // Detected\n                val.classList.add(\"font-italic\");\n                eolCode += \" (detected)\";\n                eolName += \" (detected)\";\n                // Pulse\n                val.classList.add(\"pulse\");\n                setTimeout(() => {\n                    val.classList.remove(\"pulse\");\n                }, 2000);\n                break;\n            case 0: // Unset\n            case 2: // Manually set\n            default:\n                val.classList.remove(\"font-italic\");\n                break;\n        }\n\n        val.textContent = eolCode;\n        button.setAttribute(\"title\", `End of line sequence:<br>${eolName}`);\n        button.setAttribute(\"data-original-title\", `End of line sequence:<br>${eolName}`);\n        this.eolVal = state.lineBreak;\n    }\n\n\n    /**\n     * Sets the current character encoding of the document\n     */\n    updateCharEnc() {\n        const chrEncVal = this.chrEncGetter();\n        if (this.getEncodingState() < 2 && chrEncVal === this.chrEncVal) return;\n\n        let name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] : \"Raw Bytes\";\n\n        const val = this.dom.querySelector(\".chr-enc-value\");\n        const button = val.closest(\".cm-status-bar-select-btn\");\n\n        switch (this.getEncodingState()) {\n            case 1: // Detected\n                val.classList.add(\"font-italic\");\n                name += \" (detected)\";\n                // Pulse\n                val.classList.add(\"pulse\");\n                setTimeout(() => {\n                    val.classList.remove(\"pulse\");\n                }, 2000);\n                break;\n            case 0: // Unset\n            case 2: // Manually set\n            default:\n                val.classList.remove(\"font-italic\");\n                break;\n        }\n\n        val.textContent = name;\n        button.setAttribute(\"title\", `${this.label} character encoding:<br>${name}`);\n        button.setAttribute(\"data-original-title\", `${this.label} character encoding:<br>${name}`);\n        this.chrEncVal = chrEncVal;\n    }\n\n    /**\n     * Sets the latest timing info\n     */\n    updateTiming() {\n        if (!this.timing) return;\n\n        const bakingTime = this.dom.querySelector(\".baking-time-value\");\n        const bakingTimeInfo = this.dom.querySelector(\".baking-time-info\");\n\n        if (this.label === \"Output\" && this.timing) {\n            bakingTimeInfo.style.display = \"inline-block\";\n            bakingTime.textContent = this.timing.duration(this.tabNumGetter());\n\n            const info = this.timing.printStages(this.tabNumGetter()).replace(/\\n/g, \"<br>\");\n            bakingTimeInfo.setAttribute(\"data-original-title\", info);\n        } else {\n            bakingTimeInfo.style.display = \"none\";\n        }\n    }\n\n    /**\n     * Updates the sizing of elements that need to fit correctly\n     * @param {EditorView} view\n     */\n    updateSizing(view) {\n        const viewHeight = view.contentDOM.parentNode.clientHeight;\n        this.dom.querySelectorAll(\".cm-status-bar-select-scroll\").forEach(\n            el => {\n                el.style.maxHeight = (viewHeight - 50) + \"px\";\n            }\n        );\n    }\n\n    /**\n     * Checks whether there is HTML output requiring some widgets to be disabled\n     */\n    monitorHTMLOutput() {\n        if (!this.htmlOutput?.changed) return;\n\n        if (this.htmlOutput?.html === \"\") {\n            // Enable all controls\n            this.dom.querySelectorAll(\".disabled\").forEach(el => {\n                el.classList.remove(\"disabled\");\n            });\n        } else {\n            // Disable chrenc, length, selection etc.\n            this.dom.querySelectorAll(\".cm-status-bar-select-btn\").forEach(el => {\n                el.classList.add(\"disabled\");\n            });\n\n            this.dom.querySelector(\".stats-length-value\").parentNode.classList.add(\"disabled\");\n            this.dom.querySelector(\".stats-lines-value\").parentNode.classList.add(\"disabled\");\n            this.dom.querySelector(\".sel-info\").classList.add(\"disabled\");\n            this.dom.querySelector(\".cur-offset-info\").classList.add(\"disabled\");\n        }\n    }\n\n    /**\n     * Builds the Left-hand-side widgets\n     * @returns {string}\n     */\n    constructLHS() {\n        return `\n            <span data-toggle=\"tooltip\" title=\"${this.label} length\" data-help-title=\"${this.label} length\" data-help=\"This number represents the number of characters in the ${this.label}.<br><br>The CRLF end of line separator is counted as two characters which impacts this value.\">\n                <i class=\"material-icons\">abc</i>\n                <span class=\"stats-length-value\"></span>\n            </span>\n            <span data-toggle=\"tooltip\" title=\"Number of lines\"  data-help-title=\"Number of lines\" data-help=\"This number represents the number of lines in the ${this.label}. Lines are separated by the End of Line Sequence which can be changed using the EOL selector at the far right of this status bar.\">\n                <i class=\"material-icons\">sort</i>\n                <span class=\"stats-lines-value\"></span>\n            </span>\n\n            <span class=\"sel-info\" data-toggle=\"tooltip\" title=\"Main selection\" data-help-title=\"Main selection\" data-help=\"These numbers show which offsets have been selected and how many characters are in the current selection. If multiple selections are made, these numbers refer to the latest one. \">\n                <i class=\"material-icons\">highlight_alt</i>\n                <span class=\"sel-start-value\"></span>\\u279E<span class=\"sel-end-value\"></span>\n                (<span class=\"sel-length-value\"></span> selected)\n            </span>\n            <span class=\"cur-offset-info\" data-toggle=\"tooltip\" title=\"Cursor offset\" data-help-title=\"Cursor offset\" data-help=\"This number indicates what the current offset of the cursor is from the beginning of the ${this.label}.<br><br>The CRLF end of line separator is counted as two characters which impacts this value.\">\n                <i class=\"material-icons\">location_on</i>\n                <span class=\"cur-offset-value\"></span>\n            </span>`;\n    }\n\n    /**\n     * Builds the Right-hand-side widgets\n     * Event listener set up in Manager\n     *\n     * @returns {string}\n     */\n    constructRHS() {\n        const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP).map(name =>\n            `<a href=\"#\" draggable=\"false\" data-val=\"${CHR_ENC_SIMPLE_LOOKUP[name]}\">${name}</a>`\n        ).join(\"\");\n\n        let chrEncHelpText = \"\",\n            eolHelpText = \"\";\n        if (this.label === \"Input\") {\n            chrEncHelpText = \"The input character encoding defines how the input text is encoded into bytes which are then processed by the Recipe.<br><br>The 'Raw bytes' option attempts to treat the input as individual bytes in the range 0-255. If it detects any characters with Unicode values above 255, it will treat the entire input as UTF-8. 'Raw bytes' is usually the best option if you are inputting binary data, such as a file.\";\n            eolHelpText = \"The End of Line Sequence defines which bytes are considered EOL terminators. Pressing the return key will enter this value into the input and create a new line.<br><br>Changing the EOL sequence will not modify any existing data in the input but may change how previously entered line breaks are displayed. Lines added while a different EOL terminator was set may not now result in a new line, but may be displayed as control characters instead.\";\n        } else {\n            chrEncHelpText = \"The output character encoding defines how the output bytes are decoded into text which can be displayed to you.<br><br>The 'Raw bytes' option treats the output data as individual bytes in the range 0-255.\";\n            eolHelpText = \"The End of Line Sequence defines which bytes are considered EOL terminators.<br><br>Changing this value will not modify the value of the output, but may change how certain bytes are displayed and whether they result in a new line being created.\";\n        }\n\n        return `\n            <span class=\"baking-time-info\" style=\"display: none\" data-toggle=\"tooltip\" data-html=\"true\" title=\"Baking time\" data-help-title=\"Baking time\" data-help=\"The baking time is the total time between data being read from the input, processed, and then displayed in the output.<br><br>The 'Threading overhead' value accounts for the transfer of data between different processing threads, as well as some garbage collection. It is not included in the overall bake time displayed in the status bar as it is largely influenced by background operating system and browser activity which can fluctuate significantly.\">\n                <i class=\"material-icons\">schedule</i>\n                <span class=\"baking-time-value\"></span>ms\n            </span>\n\n            <div class=\"cm-status-bar-select chr-enc-select\" data-help-title=\"${this.label} character encoding\" data-help=\"${chrEncHelpText}\">\n                <span class=\"cm-status-bar-select-btn\" data-toggle=\"tooltip\" data-html=\"true\" data-placement=\"left\" title=\"${this.label} character encoding\">\n                    <i class=\"material-icons\">text_fields</i> <span class=\"chr-enc-value\">Raw Bytes</span>\n                </span>\n                <div class=\"cm-status-bar-select-content\">\n                    <div class=\"cm-status-bar-select-scroll no-select\">\n                        <a href=\"#\" draggable=\"false\" data-val=\"0\">Raw Bytes</a>\n                        ${chrEncOptions}\n                    </div>\n                    <div class=\"input-group cm-status-bar-filter-search\">\n                        <div class=\"input-group-prepend\">\n                            <span class=\"input-group-text\">\n                                <i class=\"material-icons\">search</i>\n                            </span>\n                        </div>\n                        <input type=\"text\" class=\"form-control cm-status-bar-filter-input\" placeholder=\"Filter...\">\n                    </div>\n                </div>\n            </div>\n\n            <div class=\"cm-status-bar-select eol-select\" data-help-title=\"${this.label} EOL sequence\" data-help=\"${eolHelpText}\">\n                <span class=\"cm-status-bar-select-btn\" data-toggle=\"tooltip\" data-html=\"true\" data-placement=\"left\" title=\"End of line sequence\">\n                    <i class=\"material-icons\">keyboard_return</i> <span class=\"eol-value\"></span>\n                </span>\n                <div class=\"cm-status-bar-select-content no-select\">\n                    <a href=\"#\" draggable=\"false\" data-val=\"LF\">Line Feed, U+000A</a>\n                    <a href=\"#\" draggable=\"false\" data-val=\"VT\">Vertical Tab, U+000B</a>\n                    <a href=\"#\" draggable=\"false\" data-val=\"FF\">Form Feed, U+000C</a>\n                    <a href=\"#\" draggable=\"false\" data-val=\"CR\">Carriage Return, U+000D</a>\n                    <a href=\"#\" draggable=\"false\" data-val=\"CRLF\">CR+LF, U+000D U+000A</a>\n                    <!-- <a href=\"#\" draggable=\"false\" data-val=\"NL\">Next Line, U+0085</a> This causes problems. -->\n                    <a href=\"#\" draggable=\"false\" data-val=\"LS\">Line Separator, U+2028</a>\n                    <a href=\"#\" draggable=\"false\" data-val=\"PS\">Paragraph Separator, U+2029</a>\n                </div>\n            </div>`;\n    }\n\n}\n\nconst elementsWithListeners = {};\n\n/**\n * Hides the provided element when a click is made outside of it\n * @param {Element} element\n * @param {Event} instantiatingEvent\n */\nfunction hideOnClickOutside(element, instantiatingEvent) {\n    /**\n     * Handler for document click events\n     * Closes element if click is outside it.\n     * @param {Event} event\n     */\n    const outsideClickListener = event => {\n        // Don't trigger if we're clicking inside the element, or if the element\n        // is not visible, or if this is the same click event that opened it.\n        if (!element.contains(event.target) &&\n            event.timeStamp !== instantiatingEvent.timeStamp) {\n            hideElement(element);\n        }\n    };\n\n    if (!Object.prototype.hasOwnProperty.call(elementsWithListeners, element)) {\n        elementsWithListeners[element] = outsideClickListener;\n        document.addEventListener(\"click\", elementsWithListeners[element], false);\n    }\n}\n\n/**\n * Hides the specified element and removes the click listener for it\n * @param {Element} element\n */\nfunction hideElement(element) {\n    element.classList.remove(\"show\");\n    document.removeEventListener(\"click\", elementsWithListeners[element], false);\n    delete elementsWithListeners[element];\n}\n\n\n/**\n * A panel constructor factory building a panel that re-counts the stats every time the document changes.\n * @param {Object} opts\n * @returns {Function<PanelConstructor>}\n */\nfunction makePanel(opts) {\n    const sbPanel = new StatusBarPanel(opts);\n\n    return (view) => {\n        sbPanel.updateEOL(view.state);\n        sbPanel.updateCharEnc();\n        sbPanel.updateTiming();\n        sbPanel.updateStats(view.state);\n        sbPanel.updateSelection(view.state, false);\n        sbPanel.monitorHTMLOutput();\n\n        return {\n            \"dom\": sbPanel.dom,\n            update(update) {\n                sbPanel.updateEOL(update.state);\n                sbPanel.updateCharEnc();\n                sbPanel.updateSelection(update.state, update.selectionSet);\n                sbPanel.updateTiming();\n                sbPanel.monitorHTMLOutput();\n                if (update.geometryChanged) {\n                    sbPanel.updateSizing(update.view);\n                }\n                if (update.docChanged) {\n                    sbPanel.updateStats(update.state);\n                }\n            }\n        };\n    };\n}\n\n/**\n * A function that build the extension that enables the panel in an editor.\n * @param {Object} opts\n * @returns {Extension}\n */\nexport function statusBar(opts) {\n    const panelMaker = makePanel(opts);\n    return showPanel.of(panelMaker);\n}\n"
  },
  {
    "path": "src/web/waiters/BackgroundWorkerWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport ChefWorker from \"worker-loader?inline=no-fallback!../../core/ChefWorker.js\";\n\n/**\n * Waiter to handle conversations with a ChefWorker in the background.\n */\nclass BackgroundWorkerWaiter {\n\n    /**\n     * BackgroundWorkerWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.callbacks = {};\n        this.callbackID = 0;\n        this.completedCallback = -1;\n        this.timeout = null;\n    }\n\n\n    /**\n     * Sets up the ChefWorker and associated listeners.\n     */\n    registerChefWorker() {\n        log.debug(\"Registering new background ChefWorker\");\n        this.chefWorker = new ChefWorker();\n        this.chefWorker.addEventListener(\"message\", this.handleChefMessage.bind(this));\n        this.chefWorker.postMessage({\n            action: \"setLogPrefix\",\n            data: \"BGChefWorker\"\n        });\n        this.chefWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n\n        let docURL = document.location.href.split(/[#?]/)[0];\n        const index = docURL.lastIndexOf(\"/\");\n        if (index > 0) {\n            docURL = docURL.substring(0, index);\n        }\n        this.chefWorker.postMessage({\"action\": \"docURL\", \"data\": docURL});\n    }\n\n\n    /**\n     * Handler for messages sent back by the ChefWorker.\n     *\n     * @param {MessageEvent} e\n     */\n    handleChefMessage(e) {\n        const r = e.data;\n        log.debug(`Receiving '${r.action}' from BGChefWorker`);\n\n        switch (r.action) {\n            case \"bakeComplete\":\n            case \"bakeError\":\n                if (typeof r.data.id !== \"undefined\") {\n                    clearTimeout(this.timeout);\n                    this.callbacks[r.data.id].bind(this)(r.data);\n                    this.completedCallback = r.data.id;\n                }\n                break;\n            case \"workerLoaded\":\n                log.debug(\"Background ChefWorker loaded\");\n                break;\n            case \"optionUpdate\":\n            case \"statusMessage\":\n            case \"progressMessage\":\n                // Ignore these messages\n                break;\n            default:\n                log.error(\"Unrecognised message from background ChefWorker\", e);\n                break;\n        }\n    }\n\n\n    /**\n     * Cancels the current bake by terminating the ChefWorker and creating a new one.\n     */\n    cancelBake() {\n        if (this.chefWorker)\n            this.chefWorker.terminate();\n        this.registerChefWorker();\n    }\n\n\n    /**\n     * Asks the ChefWorker to bake the input using the specified recipe.\n     *\n     * @param {string} input\n     * @param {Object[]} recipeConfig\n     * @param {Object} options\n     * @param {number} progress\n     * @param {boolean} step\n     * @param {Function} callback\n     */\n    bake(input, recipeConfig, options, progress, step, callback) {\n        const id = this.callbackID++;\n        this.callbacks[id] = callback;\n\n        this.chefWorker.postMessage({\n            action: \"bake\",\n            data: {\n                input: input,\n                recipeConfig: recipeConfig,\n                options: options,\n                progress: progress,\n                step: step,\n                id: id\n            }\n        });\n    }\n\n\n    /**\n     * Asks the Magic operation what it can do with the input data.\n     *\n     * @param {string|ArrayBuffer} input\n     */\n    magic(input) {\n        // If we're still working on the previous bake, cancel it before starting a new one.\n        if (this.completedCallback + 1 < this.callbackID) {\n            clearTimeout(this.timeout);\n            this.cancelBake();\n        }\n\n        this.bake(input, [\n            {\n                \"op\": \"Magic\",\n                \"args\": [3, false, false]\n            }\n        ], {}, 0, false, this.magicComplete);\n\n        // Cancel this bake if it takes too long.\n        this.timeout = setTimeout(this.cancelBake.bind(this), 3000);\n    }\n\n\n    /**\n     * Handler for completed Magic bakes.\n     *\n     * @param {Object} response\n     */\n    magicComplete(response) {\n        log.debug(\"--- Background Magic Bake complete ---\");\n        if (!response || response.error) return;\n\n        this.manager.output.backgroundMagicResult(response.dish.value);\n    }\n\n\n    /**\n     * Sets the console log level in the workers.\n     */\n    setLogLevel() {\n        if (!this.chefWorker) return;\n        this.chefWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n    }\n\n}\n\n\nexport default BackgroundWorkerWaiter;\n"
  },
  {
    "path": "src/web/waiters/BindingsWaiter.mjs",
    "content": "/**\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n/**\n * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.)\n */\nclass BindingsWaiter {\n\n    /**\n     * BindingsWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n    }\n\n\n    /**\n     * Handler for all keydown events\n     * Checks whether valid keyboard shortcut has been instated\n     *\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    parseInput(e) {\n        const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey;\n\n        if (e.ctrlKey && modKey) {\n            let elem;\n            switch (e.code) {\n                case \"KeyF\": // Focus search\n                    e.preventDefault();\n                    document.getElementById(\"search\").focus();\n                    break;\n                case \"KeyI\": // Focus input\n                    e.preventDefault();\n                    this.manager.input.inputEditorView.focus();\n                    break;\n                case \"KeyO\": // Focus output\n                    e.preventDefault();\n                    this.manager.output.outputEditorView.focus();\n                    break;\n                case \"Period\": // Focus next operation\n                    e.preventDefault();\n                    try {\n                        elem = document.activeElement.closest(\".operation\") || document.querySelector(\"#rec-list .operation\");\n                        if (elem.parentNode.lastChild === elem) {\n                            // If operation is last in recipe, loop around to the top operation's first argument\n                            elem.parentNode.firstChild.querySelectorAll(\".arg\")[0].focus();\n                        } else {\n                            // Focus first argument of next operation\n                            elem.nextSibling.querySelectorAll(\".arg\")[0].focus();\n                        }\n                    } catch (e) {\n                        // do nothing, just don't throw an error\n                    }\n                    break;\n                case \"KeyB\": // Set breakpoint\n                    e.preventDefault();\n                    try {\n                        elem = document.activeElement.closest(\".operation\").querySelectorAll(\".breakpoint\")[0];\n                        if (elem.getAttribute(\"break\") === \"false\") {\n                            elem.setAttribute(\"break\", \"true\"); // add break point if not already enabled\n                            elem.classList.add(\"breakpoint-selected\");\n                        } else {\n                            elem.setAttribute(\"break\", \"false\"); // remove break point if already enabled\n                            elem.classList.remove(\"breakpoint-selected\");\n                        }\n                        window.dispatchEvent(this.manager.statechange);\n                    } catch (e) {\n                        // do nothing, just don't throw an error\n                    }\n                    break;\n                case \"KeyD\": // Disable operation\n                    e.preventDefault();\n                    try {\n                        elem = document.activeElement.closest(\".operation\").querySelectorAll(\".disable-icon\")[0];\n                        if (elem.getAttribute(\"disabled\") === \"false\") {\n                            elem.setAttribute(\"disabled\", \"true\"); // disable operation if enabled\n                            elem.classList.add(\"disable-elem-selected\");\n                            elem.parentNode.parentNode.classList.add(\"disabled\");\n                        } else {\n                            elem.setAttribute(\"disabled\", \"false\"); // enable operation if disabled\n                            elem.classList.remove(\"disable-elem-selected\");\n                            elem.parentNode.parentNode.classList.remove(\"disabled\");\n                        }\n                        this.app.progress = 0;\n                        window.dispatchEvent(this.manager.statechange);\n                    } catch (e) {\n                        // do nothing, just don't throw an error\n                    }\n                    break;\n                case \"Space\": // Bake\n                    e.preventDefault();\n                    this.manager.controls.bakeClick();\n                    break;\n                case \"Quote\": // Step through\n                    e.preventDefault();\n                    this.manager.controls.stepClick();\n                    break;\n                case \"KeyC\": // Clear recipe\n                    e.preventDefault();\n                    this.manager.recipe.clearRecipe();\n                    break;\n                case \"KeyS\": // Save output to file\n                    e.preventDefault();\n                    this.manager.output.saveClick();\n                    break;\n                case \"KeyL\": // Load recipe\n                    e.preventDefault();\n                    this.manager.controls.loadClick();\n                    break;\n                case \"KeyM\": // Switch input and output\n                    e.preventDefault();\n                    this.manager.output.switchClick();\n                    break;\n                case \"KeyT\": // New tab\n                    e.preventDefault();\n                    this.manager.input.addInputClick();\n                    break;\n                case \"KeyW\": // Close tab\n                    e.preventDefault();\n                    this.manager.input.removeInput(this.manager.tabs.getActiveTab(\"input\"));\n                    break;\n                case \"ArrowLeft\": // Go to previous tab\n                    e.preventDefault();\n                    this.manager.input.changeTabLeft();\n                    break;\n                case \"ArrowRight\": // Go to next tab\n                    e.preventDefault();\n                    this.manager.input.changeTabRight();\n                    break;\n                default:\n                    if (e.code.match(/Digit[0-9]/g)) { // Select nth operation\n                        e.preventDefault();\n                        try {\n                            // Select the first argument of the operation corresponding to the number pressed\n                            document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus();\n                        } catch (e) {\n                            // do nothing, just don't throw an error\n                        }\n                    }\n                    break;\n            }\n        } else {\n            switch (e.code) {\n                case \"F1\":\n                    e.preventDefault();\n                    this.contextualHelp();\n                    break;\n            }\n        }\n    }\n\n\n    /**\n     * Updates keybinding list when metaKey option is toggled\n     */\n    updateKeybList() {\n        let modWinLin = \"Alt\";\n        let modMac = \"Opt\";\n        if (this.app.options.useMetaKey) {\n            modWinLin = \"Win\";\n            modMac = \"Cmd\";\n        }\n        document.getElementById(\"keybList\").innerHTML = `\n        <tr>\n            <th>Command</th>\n            <th>Shortcut (Win/Linux)</th>\n            <th>Shortcut (Mac)</th>\n        </tr>\n        <tr>\n            <td>Activate contextual help</td>\n            <td>F1</td>\n            <td>F1</td>\n        </tr>\n        <tr>\n            <td>Place cursor in search field</td>\n            <td>Ctrl+${modWinLin}+f</td>\n            <td>Ctrl+${modMac}+f</td>\n        <tr>\n            <td>Place cursor in input box</td>\n            <td>Ctrl+${modWinLin}+i</td>\n            <td>Ctrl+${modMac}+i</td>\n        </tr>\n        <tr>\n            <td>Place cursor in output box</td>\n            <td>Ctrl+${modWinLin}+o</td>\n            <td>Ctrl+${modMac}+o</td>\n        </tr>\n        <tr>\n            <td>Place cursor in first argument field of the next operation in the recipe</td>\n            <td>Ctrl+${modWinLin}+.</td>\n            <td>Ctrl+${modMac}+.</td>\n        </tr>\n        <tr>\n            <td>Place cursor in first argument field of the nth operation in the recipe</td>\n            <td>Ctrl+${modWinLin}+[1-9]</td>\n            <td>Ctrl+${modMac}+[1-9]</td>\n        </tr>\n        <tr>\n            <td>Disable current operation</td>\n            <td>Ctrl+${modWinLin}+d</td>\n            <td>Ctrl+${modMac}+d</td>\n        </tr>\n        <tr>\n            <td>Set/clear breakpoint</td>\n            <td>Ctrl+${modWinLin}+b</td>\n            <td>Ctrl+${modMac}+b</td>\n        </tr>\n        <tr>\n            <td>Bake</td>\n            <td>Ctrl+${modWinLin}+Space</td>\n            <td>Ctrl+${modMac}+Space</td>\n        </tr>\n        <tr>\n            <td>Step</td>\n            <td>Ctrl+${modWinLin}+'</td>\n            <td>Ctrl+${modMac}+'</td>\n        </tr>\n        <tr>\n            <td>Clear recipe</td>\n            <td>Ctrl+${modWinLin}+c</td>\n            <td>Ctrl+${modMac}+c</td>\n        </tr>\n        <tr>\n            <td>Save to file</td>\n            <td>Ctrl+${modWinLin}+s</td>\n            <td>Ctrl+${modMac}+s</td>\n        </tr>\n        <tr>\n            <td>Load recipe</td>\n            <td>Ctrl+${modWinLin}+l</td>\n            <td>Ctrl+${modMac}+l</td>\n        </tr>\n        <tr>\n            <td>Move output to input</td>\n            <td>Ctrl+${modWinLin}+m</td>\n            <td>Ctrl+${modMac}+m</td>\n        </tr>\n        <tr>\n            <td>Create a new tab</td>\n            <td>Ctrl+${modWinLin}+t</td>\n            <td>Ctrl+${modMac}+t</td>\n        </tr>\n        <tr>\n            <td>Close the current tab</td>\n            <td>Ctrl+${modWinLin}+w</td>\n            <td>Ctrl+${modMac}+w</td>\n        </tr>\n        <tr>\n            <td>Go to next tab</td>\n            <td>Ctrl+${modWinLin}+RightArrow</td>\n            <td>Ctrl+${modMac}+RightArrow</td>\n        </tr>\n        <tr>\n            <td>Go to previous tab</td>\n            <td>Ctrl+${modWinLin}+LeftArrow</td>\n            <td>Ctrl+${modMac}+LeftArrow</td>\n        </tr>\n        `;\n    }\n\n    /**\n     * Shows contextual help message based on where the mouse pointer is\n     */\n    contextualHelp() {\n        const hoveredHelpEls = document.querySelectorAll(\":hover[data-help],:hover[data-help-proxy]\");\n        if (!hoveredHelpEls.length) return;\n\n        let helpEl = hoveredHelpEls[hoveredHelpEls.length - 1];\n        const helpElSelector = helpEl.getAttribute(\"data-help-proxy\");\n        if (helpElSelector) {\n            // A hovered element is directing us to another element for its help text\n            helpEl = document.querySelector(helpElSelector);\n        }\n        this.displayHelp(helpEl);\n    }\n\n    /**\n     * Displays the help pane populated with help text associated with the given element\n     *\n     * @param {Element} el\n     */\n    displayHelp(el) {\n        const helpText = el.getAttribute(\"data-help\");\n        let helpTitle = el.getAttribute(\"data-help-title\");\n\n        if (helpTitle)\n            helpTitle = \"<span class='text-muted'>Help topic:</span> \" + helpTitle;\n        else\n            helpTitle = \"<span class='text-muted'>Help topic</span>\";\n\n        document.querySelector(\"#help-modal .modal-body\").innerHTML = helpText;\n        document.querySelector(\"#help-modal #help-title\").innerHTML = helpTitle;\n\n        $(\"#help-modal\").modal();\n    }\n\n}\n\nexport default BindingsWaiter;\n"
  },
  {
    "path": "src/web/waiters/ControlsWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils from \"../../core/Utils.mjs\";\nimport { eolSeqToCode } from \"../utils/editorUtils.mjs\";\n\n\n/**\n * Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.)\n */\nclass ControlsWaiter {\n\n    /**\n     * ControlsWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n    }\n\n\n    /**\n     * Initialise Bootstrap components\n     */\n    initComponents() {\n        $(\"body\").bootstrapMaterialDesign();\n        $(\"[data-toggle=tooltip]\").tooltip({\n            animation: false,\n            container: \"body\",\n            boundary: \"viewport\",\n            trigger: \"hover\"\n        });\n\n        // Set number of operations in various places in the DOM\n        document.querySelectorAll(\".num-ops\").forEach(el => {\n            el.innerHTML = Object.keys(this.app.operations).length;\n        });\n    }\n\n\n    /**\n     * Checks or unchecks the Auto Bake checkbox based on the given value.\n     *\n     * @param {boolean} value - The new value for Auto Bake.\n     */\n    setAutoBake(value) {\n        const autoBakeCheckbox = document.getElementById(\"auto-bake\");\n\n        if (autoBakeCheckbox.checked !== value) {\n            autoBakeCheckbox.click();\n        }\n    }\n\n\n    /**\n     * Handler to trigger baking.\n     */\n    bakeClick() {\n        const btnBake = document.getElementById(\"bake\");\n        if (btnBake.textContent.indexOf(\"Bake\") > 0) {\n            this.app.manager.input.bakeAll();\n        } else if (btnBake.textContent.indexOf(\"Cancel\") > 0) {\n            this.manager.worker.cancelBake(false, true);\n        }\n    }\n\n\n    /**\n     * Handler for the 'Step through' command. Executes the next step of the recipe.\n     */\n    stepClick() {\n        this.app.step();\n    }\n\n\n    /**\n     * Handler for changes made to the Auto Bake checkbox.\n     */\n    autoBakeChange() {\n        this.app.autoBake_ = document.getElementById(\"auto-bake\").checked;\n    }\n\n\n    /**\n     * Handler for the 'Clear recipe' command. Removes all operations from the recipe.\n     */\n    clearRecipeClick() {\n        this.manager.recipe.clearRecipe();\n    }\n\n\n    /**\n     * Populates the save dialog box with a URL incorporating the recipe and input.\n     *\n     * @param {Object[]} [recipeConfig] - The recipe configuration object array.\n     */\n    initialiseSaveLink(recipeConfig) {\n        recipeConfig = recipeConfig || this.app.getRecipeConfig();\n\n        const includeRecipe = document.getElementById(\"save-link-recipe-checkbox\").checked;\n        const includeInput = document.getElementById(\"save-link-input-checkbox\").checked;\n        const saveLinkEl = document.getElementById(\"save-link\");\n        const saveLink = this.generateStateUrl(includeRecipe, includeInput, null, recipeConfig);\n\n        saveLinkEl.innerHTML = Utils.escapeHtml(Utils.truncate(saveLink, 120));\n        saveLinkEl.setAttribute(\"href\", saveLink);\n    }\n\n\n    /**\n     * Generates a URL containing the current recipe and input state.\n     *\n     * @param {boolean} includeRecipe - Whether to include the recipe in the URL.\n     * @param {boolean} includeInput - Whether to include the input in the URL.\n     * @param {string} input\n     * @param {Object[]} [recipeConfig] - The recipe configuration object array.\n     * @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included\n     * @returns {string}\n     */\n    generateStateUrl(includeRecipe, includeInput, input, recipeConfig, baseURL) {\n        recipeConfig = recipeConfig || this.app.getRecipeConfig();\n\n        const link = baseURL || window.location.protocol + \"//\" +\n            window.location.host +\n            window.location.pathname;\n        const recipeStr = Utils.generatePrettyRecipe(recipeConfig);\n\n        includeRecipe = includeRecipe && (recipeConfig.length > 0);\n\n        // If we don't get passed an input, get it from the current URI\n        if (input === null && includeInput) {\n            const params = this.app.getURIParams();\n            if (params.input) {\n                includeInput = true;\n                input = params.input;\n            } else {\n                includeInput = false;\n            }\n        }\n\n        const inputChrEnc = this.manager.input.getChrEnc();\n        const outputChrEnc = this.manager.output.getChrEnc();\n        const inputEOL = eolSeqToCode[this.manager.input.getEOLSeq()];\n        const outputEOL = eolSeqToCode[this.manager.output.getEOLSeq()];\n\n        const params = [\n            includeRecipe ? [\"recipe\", recipeStr] : undefined,\n            includeInput && input.length ? [\"input\", Utils.escapeHtml(input)] : undefined,\n            inputChrEnc !== 0 ? [\"ienc\", inputChrEnc] : undefined,\n            outputChrEnc !== 0 ? [\"oenc\", outputChrEnc] : undefined,\n            inputEOL !== \"LF\" ? [\"ieol\", inputEOL] : undefined,\n            outputEOL !== \"LF\" ? [\"oeol\", outputEOL] : undefined\n        ];\n\n        const hash = params\n            .filter(v => v)\n            .map(([key, value]) => `${key}=${Utils.encodeURIFragment(value)}`)\n            .join(\"&\");\n\n        if (hash) {\n            return `${link}#${hash}`;\n        }\n\n        return link;\n    }\n\n\n    /**\n     * Handler for changes made to the save dialog text area. Re-initialises the save link.\n     */\n    saveTextChange(e) {\n        try {\n            const recipeConfig = Utils.parseRecipeConfig(e.target.value);\n            this.initialiseSaveLink(recipeConfig);\n        } catch (err) {}\n    }\n\n\n    /**\n     * Handler for the 'Save' command. Pops up the save dialog box.\n     */\n    saveClick() {\n        const recipeConfig = this.app.getRecipeConfig();\n        const recipeStr = JSON.stringify(recipeConfig);\n\n        document.getElementById(\"save-text-chef\").value = Utils.generatePrettyRecipe(recipeConfig, true);\n        document.getElementById(\"save-text-clean\").value = JSON.stringify(recipeConfig, null, 2)\n            .replace(/{\\n\\s+\"/g, \"{ \\\"\")\n            .replace(/\\[\\n\\s{3,}/g, \"[\")\n            .replace(/\\n\\s{3,}]/g, \"]\")\n            .replace(/\\s*\\n\\s*}/g, \" }\")\n            .replace(/\\n\\s{6,}/g, \" \");\n        document.getElementById(\"save-text-compact\").value = recipeStr;\n\n        this.initialiseSaveLink(recipeConfig);\n        $(\"#save-modal\").modal();\n    }\n\n\n    /**\n     * Handler for the save link recipe checkbox change event.\n     */\n    slrCheckChange() {\n        this.initialiseSaveLink();\n    }\n\n\n    /**\n     * Handler for the save link input checkbox change event.\n     */\n    sliCheckChange() {\n        this.initialiseSaveLink();\n    }\n\n\n    /**\n     * Handler for the 'Load' command. Pops up the load dialog box.\n     */\n    loadClick() {\n        this.populateLoadRecipesList();\n        $(\"#load-modal\").modal();\n    }\n\n\n    /**\n     * Saves the recipe specified in the save textarea to local storage.\n     */\n    saveButtonClick() {\n        if (!this.app.isLocalStorageAvailable()) {\n            this.app.alert(\n                \"Your security settings do not allow access to local storage so your recipe cannot be saved.\",\n                5000\n            );\n            return false;\n        }\n\n        const recipeName = Utils.escapeHtml(document.getElementById(\"save-name\").value);\n        const recipeStr  = document.querySelector(\"#save-texts .tab-pane.active textarea\").value;\n\n        if (!recipeName) {\n            this.app.alert(\"Please enter a recipe name\", 3000);\n            return;\n        }\n\n        const savedRecipes = localStorage.savedRecipes ?\n            JSON.parse(localStorage.savedRecipes) : [];\n        let recipeId = localStorage.recipeId || 0;\n\n        savedRecipes.push({\n            id: ++recipeId,\n            name: recipeName,\n            recipe: recipeStr\n        });\n\n        localStorage.savedRecipes = JSON.stringify(savedRecipes);\n        localStorage.recipeId = recipeId;\n\n        this.app.alert(`Recipe saved as \"${recipeName}\".`, 3000);\n    }\n\n\n    /**\n     * Populates the list of saved recipes in the load dialog box from local storage.\n     */\n    populateLoadRecipesList() {\n        if (!this.app.isLocalStorageAvailable()) return false;\n\n        const loadNameEl = document.getElementById(\"load-name\");\n\n        // Remove current recipes from select\n        let i = loadNameEl.options.length;\n        while (i--) {\n            loadNameEl.remove(i);\n        }\n\n        // Add recipes to select\n        const savedRecipes = localStorage.savedRecipes ?\n            JSON.parse(localStorage.savedRecipes) : [];\n\n        for (i = 0; i < savedRecipes.length; i++) {\n            const opt = document.createElement(\"option\");\n            opt.value = savedRecipes[i].id;\n            // Unescape then re-escape in case localStorage has been corrupted\n            opt.innerHTML = Utils.escapeHtml(Utils.unescapeHtml(savedRecipes[i].name));\n\n            loadNameEl.appendChild(opt);\n        }\n\n        // Populate textarea with first recipe\n        const loadText = document.getElementById(\"load-text\");\n        const evt = new Event(\"change\");\n        loadText.value = savedRecipes.length ? savedRecipes[0].recipe : \"\";\n        loadText.dispatchEvent(evt);\n    }\n\n\n    /**\n     * Removes the currently selected recipe from local storage.\n     */\n    loadDeleteClick() {\n        if (!this.app.isLocalStorageAvailable()) return false;\n\n        const id = parseInt(document.getElementById(\"load-name\").value, 10);\n        const rawSavedRecipes = localStorage.savedRecipes ?\n            JSON.parse(localStorage.savedRecipes) : [];\n\n        const savedRecipes = rawSavedRecipes.filter(r => r.id !== id);\n\n        localStorage.savedRecipes = JSON.stringify(savedRecipes);\n        this.populateLoadRecipesList();\n    }\n\n\n    /**\n     * Displays the selected recipe in the load text box.\n     */\n    loadNameChange(e) {\n        if (!this.app.isLocalStorageAvailable()) return false;\n\n        const el = e.target;\n        const savedRecipes = localStorage.savedRecipes ?\n            JSON.parse(localStorage.savedRecipes) : [];\n        const id = parseInt(el.value, 10);\n\n        const recipe = savedRecipes.find(r => r.id === id);\n\n        document.getElementById(\"load-text\").value = recipe.recipe;\n    }\n\n\n    /**\n     * Loads the selected recipe and populates the Recipe with its operations.\n     */\n    loadButtonClick() {\n        try {\n            const recipeConfig = Utils.parseRecipeConfig(document.getElementById(\"load-text\").value);\n            this.app.setRecipeConfig(recipeConfig);\n            this.app.autoBake();\n\n            $(\"#rec-list [data-toggle=popover]\").popover();\n        } catch (e) {\n            this.app.alert(\"Invalid recipe\", 2000);\n        }\n    }\n\n\n    /**\n     * Hides the arguments for all the operations in the current recipe.\n     */\n    hideRecipeArgsClick() {\n        const icon = document.getElementById(\"hide-icon\");\n\n        if (icon.getAttribute(\"hide-args\") === \"false\") {\n            icon.setAttribute(\"hide-args\", \"true\");\n            icon.setAttribute(\"data-original-title\", \"Show arguments\");\n            icon.children[0].innerText = \"keyboard_arrow_down\";\n            Array.from(document.getElementsByClassName(\"hide-args-icon\")).forEach(function(item) {\n                item.setAttribute(\"hide-args\", \"true\");\n                item.innerText = \"keyboard_arrow_down\";\n                item.classList.add(\"hide-args-selected\");\n                item.parentNode.previousElementSibling.style.display = \"none\";\n            });\n        } else {\n            icon.setAttribute(\"hide-args\", \"false\");\n            icon.setAttribute(\"data-original-title\", \"Hide arguments\");\n            icon.children[0].innerText = \"keyboard_arrow_up\";\n            Array.from(document.getElementsByClassName(\"hide-args-icon\")).forEach(function(item) {\n                item.setAttribute(\"hide-args\", \"false\");\n                item.innerText = \"keyboard_arrow_up\";\n                item.classList.remove(\"hide-args-selected\");\n                item.parentNode.previousElementSibling.style.display = \"grid\";\n            });\n        }\n    }\n\n\n    /**\n     * Populates the bug report information box with useful technical info.\n     *\n     * @param {event} e\n     */\n    supportButtonClick(e) {\n        e.preventDefault();\n\n        const reportBugInfo = document.getElementById(\"report-bug-info\");\n        const saveLink = this.generateStateUrl(true, true, null, null, \"https://gchq.github.io/CyberChef/\");\n\n        if (reportBugInfo) {\n            reportBugInfo.innerHTML = `* Version: ${PKG_VERSION}\n* Compile time: ${COMPILE_TIME}\n* User-Agent:\n${navigator.userAgent}\n* [Link to reproduce](${saveLink})\n\n`;\n        }\n    }\n\n\n    /**\n     * Shows the stale indicator to show that the input or recipe has changed\n     * since the last bake.\n     */\n    showStaleIndicator() {\n        const staleIndicator = document.getElementById(\"stale-indicator\");\n        staleIndicator.classList.remove(\"hidden\");\n    }\n\n\n    /**\n     * Hides the stale indicator to show that the input or recipe has not changed\n     * since the last bake.\n     */\n    hideStaleIndicator() {\n        const staleIndicator = document.getElementById(\"stale-indicator\");\n        staleIndicator.classList.add(\"hidden\");\n    }\n\n\n    /**\n     * Switches the Bake button between 'Bake', 'Cancel' and 'Loading' functions.\n     *\n     * @param {string} func - The function to change to. Either \"cancel\", \"loading\" or \"bake\"\n     */\n    toggleBakeButtonFunction(func) {\n        const bakeButton = document.getElementById(\"bake\"),\n            btnText = bakeButton.querySelector(\"span\");\n\n        switch (func) {\n            case \"cancel\":\n                btnText.innerText = \"Cancel\";\n                bakeButton.classList.remove(\"btn-success\");\n                bakeButton.classList.remove(\"btn-warning\");\n                bakeButton.classList.add(\"btn-danger\");\n                break;\n            case \"loading\":\n                bakeButton.style.background = \"\";\n                btnText.innerText = \"Loading...\";\n                bakeButton.classList.remove(\"btn-success\");\n                bakeButton.classList.remove(\"btn-danger\");\n                bakeButton.classList.add(\"btn-warning\");\n                break;\n            default:\n                bakeButton.style.background = \"\";\n                btnText.innerText = \"Bake!\";\n                bakeButton.classList.remove(\"btn-danger\");\n                bakeButton.classList.remove(\"btn-warning\");\n                bakeButton.classList.add(\"btn-success\");\n        }\n    }\n\n    /**\n     * Calculates the height of the controls area and adjusts the recipe\n     * height accordingly.\n     */\n    calcControlsHeight() {\n        const controls = document.getElementById(\"controls\"),\n            recList = document.getElementById(\"rec-list\");\n\n        recList.style.bottom = controls.clientHeight + \"px\";\n    }\n\n}\n\nexport default ControlsWaiter;\n"
  },
  {
    "path": "src/web/waiters/HighlighterWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport {EditorSelection} from \"@codemirror/state\";\nimport {chrEncWidth} from \"../../core/lib/ChrEnc.mjs\";\n\n/**\n * Waiter to handle events related to highlighting in CyberChef.\n */\nclass HighlighterWaiter {\n\n    /**\n     * HighlighterWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.currentSelectionRanges = [];\n    }\n\n    /**\n     * Handler for selection change events in the input and output\n     *\n     * Highlights the given offsets in the input or output.\n     * We will only highlight if:\n     *     - input hasn't changed since last bake\n     *     - last bake was a full bake\n     *     - all operations in the recipe support highlighting\n     *\n     * @param {string} io\n     * @param {ViewUpdate} e\n     */\n    selectionChange(io, e) {\n        // Confirm we are not currently baking\n        if (!this.app.autoBake_ || this.app.baking) return false;\n\n        // Confirm this was a user-generated event to prevent looping\n        // from setting the selection in this class\n        if (!e.transactions[0].isUserEvent(\"select\")) return false;\n\n        this.currentSelectionRanges = [];\n\n        // Confirm some non-empty ranges are set\n        const selectionRanges = e.state.selection.ranges;\n\n        // Adjust offsets based on the width of the character set\n        const inputCharacterWidth = chrEncWidth(this.manager.input.getChrEnc());\n        const outputCharacterWidth = chrEncWidth(this.manager.output.getChrEnc());\n        let ratio = 1;\n        if (inputCharacterWidth !== outputCharacterWidth &&\n            inputCharacterWidth !== 0 && outputCharacterWidth !== 0) {\n            ratio = io === \"input\" ?\n                inputCharacterWidth / outputCharacterWidth :\n                outputCharacterWidth / inputCharacterWidth;\n        }\n\n        // Loop through ranges and send request for output offsets for each one\n        const direction = io === \"input\" ? \"forward\" : \"reverse\";\n        for (const range of selectionRanges) {\n            const pos = [{\n                start: Math.floor(range.from * ratio),\n                end: Math.floor(range.to * ratio)\n            }];\n            this.manager.worker.highlight(this.app.getRecipeConfig(), direction, pos);\n        }\n    }\n\n    /**\n     * Displays highlight offsets sent back from the Chef.\n     *\n     * @param {Object[]} pos - The position object for the highlight.\n     * @param {number} pos.start - The start offset.\n     * @param {number} pos.end - The end offset.\n     * @param {string} direction\n     */\n    displayHighlights(pos, direction) {\n        if (!pos) return;\n        if (this.manager.tabs.getActiveTab(\"input\") !== this.manager.tabs.getActiveTab(\"output\")) return;\n\n        const io = direction === \"forward\" ? \"output\" : \"input\";\n        this.highlight(io, pos);\n    }\n\n    /**\n     * Sends selection updates to the relevant EditorView.\n     *\n     * @param {string} io - The input or output\n     * @param {Object[]} ranges - An array of position objects to highlight\n     * @param {number} ranges.start - The start offset\n     * @param {number} ranges.end - The end offset\n     */\n    async highlight(io, ranges) {\n        if (!this.app.options.showHighlighter) return false;\n        if (!this.app.options.attemptHighlight) return false;\n        if (!ranges || !ranges.length) return false;\n\n        const view = io === \"input\" ?\n            this.manager.input.inputEditorView :\n            this.manager.output.outputEditorView;\n\n        // Add new SelectionRanges to existing ones\n        for (const range of ranges) {\n            if (typeof range.start !== \"number\" ||\n                typeof range.end !== \"number\")\n                continue;\n            const selection = range.end <= range.start ?\n                EditorSelection.cursor(range.start) :\n                EditorSelection.range(range.start, range.end);\n\n            this.currentSelectionRanges.push(selection);\n        }\n\n        // Set selection\n        if (this.currentSelectionRanges.length) {\n            try {\n                view.dispatch({\n                    selection: EditorSelection.create(this.currentSelectionRanges),\n                    scrollIntoView: true\n                });\n            } catch (err) {\n                // Ignore Range Errors\n                if (!err.toString().startsWith(\"RangeError\")) {\n                    log.error(err);\n                }\n            }\n        }\n    }\n\n}\n\nexport default HighlighterWaiter;\n"
  },
  {
    "path": "src/web/waiters/InputWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport LoaderWorker from \"worker-loader?inline=no-fallback!../workers/LoaderWorker.js\";\nimport InputWorker from \"worker-loader?inline=no-fallback!../workers/InputWorker.mjs\";\nimport Utils, {debounce} from \"../../core/Utils.mjs\";\nimport {toBase64} from \"../../core/lib/Base64.mjs\";\nimport cptable from \"codepage\";\n\nimport {\n    EditorView,\n    keymap,\n    highlightSpecialChars,\n    drawSelection,\n    rectangularSelection,\n    crosshairCursor,\n    dropCursor\n} from \"@codemirror/view\";\nimport {\n    EditorState,\n    Compartment\n} from \"@codemirror/state\";\nimport {\n    defaultKeymap,\n    insertTab,\n    insertNewline,\n    history,\n    historyKeymap\n} from \"@codemirror/commands\";\nimport {\n    bracketMatching\n} from \"@codemirror/language\";\nimport {\n    search,\n    searchKeymap,\n    highlightSelectionMatches\n} from \"@codemirror/search\";\n\nimport {statusBar} from \"../utils/statusBar.mjs\";\nimport {fileDetailsPanel} from \"../utils/fileDetails.mjs\";\nimport {eolCodeToSeq, eolCodeToName, renderSpecialChar} from \"../utils/editorUtils.mjs\";\n\n\n/**\n * Waiter to handle events related to the input.\n */\nclass InputWaiter {\n\n    /**\n     * InputWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.inputTextEl = document.getElementById(\"input-text\");\n        this.inputChrEnc = 0;\n        this.eolState = 0; // 0 = unset, 1 = detected, 2 = manual\n        this.encodingState = 0; // 0 = unset, 1 = detected, 2 = manual\n        this.initEditor();\n\n        this.inputWorker = null;\n        this.loaderWorkers = [];\n        this.workerId = 0;\n        this.maxTabs = this.manager.tabs.calcMaxTabs();\n        this.callbacks = {};\n        this.callbackID = 0;\n        this.fileDetails = {};\n\n        this.maxWorkers = 1;\n        if (navigator.hardwareConcurrency !== undefined &&\n            navigator.hardwareConcurrency > 1) {\n            // Subtract 1 from hardwareConcurrency value to avoid using\n            // the entire available resources\n            this.maxWorkers = navigator.hardwareConcurrency - 1;\n        }\n    }\n\n    /**\n     * Sets up the CodeMirror Editor\n     */\n    initEditor() {\n        // Mutable extensions\n        this.inputEditorConf = {\n            eol: new Compartment,\n            lineWrapping: new Compartment,\n            fileDetailsPanel: new Compartment\n        };\n\n        const self = this;\n        const initialState = EditorState.create({\n            doc: null,\n            extensions: [\n                // Editor extensions\n                history(),\n                highlightSpecialChars({\n                    render: renderSpecialChar // Custom character renderer to handle special cases\n                }),\n                drawSelection(),\n                rectangularSelection(),\n                crosshairCursor(),\n                dropCursor(),\n                bracketMatching(),\n                highlightSelectionMatches(),\n                search({top: true}),\n                EditorState.allowMultipleSelections.of(true),\n\n                // Custom extensions\n                statusBar({\n                    label: \"Input\",\n                    eolHandler: this.eolChange.bind(this),\n                    chrEncHandler: this.chrEncChange.bind(this),\n                    chrEncGetter: this.getChrEnc.bind(this),\n                    getEncodingState: this.getEncodingState.bind(this),\n                    getEOLState: this.getEOLState.bind(this)\n                }),\n\n                // Mutable state\n                this.inputEditorConf.fileDetailsPanel.of([]),\n                this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping),\n                this.inputEditorConf.eol.of(EditorState.lineSeparator.of(\"\\n\")),\n\n                // Keymap\n                keymap.of([\n                    // Explicitly insert a tab rather than indenting the line\n                    { key: \"Tab\", run: insertTab },\n                    // Explicitly insert a new line (using the current EOL char) rather\n                    // than messing around with indenting, which does not respect EOL chars\n                    { key: \"Enter\", run: insertNewline },\n                    ...historyKeymap,\n                    ...defaultKeymap,\n                    ...searchKeymap\n                ]),\n\n                // Event listeners\n                EditorView.updateListener.of(e => {\n                    if (e.selectionSet)\n                        this.manager.highlighter.selectionChange(\"input\", e);\n                    if (e.docChanged && !this.silentInputChange)\n                        this.inputChange(e);\n                    this.silentInputChange = false;\n                }),\n\n                // Event handlers\n                EditorView.domEventHandlers({\n                    paste(event, view) {\n                        const clipboardData = event.clipboardData;\n                        const items = clipboardData.items;\n                        let files = [];\n                        for (let i = 0; i < items.length; i++) {\n                            const item = items[i];\n                            if (item.kind === \"string\") {\n                                // If there are any string items they should be preferred over\n                                // files.\n                                files = [];\n                                break;\n                            } else if (item.kind === \"file\") {\n                                files.push(item.getAsFile());\n                            }\n                        }\n                        if (files.length > 0) {\n                            // Prevent the default paste behavior, afterPaste will load the files instead\n                            event.preventDefault();\n                        }\n                        setTimeout(() => {\n                            self.afterPaste(files);\n                        });\n                    }\n                })\n            ]\n        });\n\n\n        if (this.inputEditorView) this.inputEditorView.destroy();\n        this.inputEditorView = new EditorView({\n            state: initialState,\n            parent: this.inputTextEl\n        });\n    }\n\n    /**\n     * Handler for EOL change events\n     * Sets the line separator\n     * @param {string} eol\n     * @param {boolean} [manual=false]\n     */\n    eolChange(eol, manual=false) {\n        const eolVal = eolCodeToSeq[eol];\n        if (eolVal === undefined) return;\n\n        this.eolState = manual ? 2 : this.eolState;\n        if (this.eolState < 2 && eolVal === this.getEOLSeq()) return;\n\n        if (this.eolState === 1) {\n            // Alert\n            this.app.alert(`Input end of line separator has been detected and changed to ${eolCodeToName[eol]}`, 5000);\n        }\n\n        // Update the EOL value\n        const oldInputVal = this.getInput();\n        this.inputEditorView.dispatch({\n            effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal))\n        });\n\n        // Reset the input so that lines are recalculated, preserving the old EOL values\n        this.setInput(oldInputVal);\n    }\n\n    /**\n     * Getter for the input EOL sequence\n     * @returns {string}\n     */\n    getEOLSeq() {\n        return this.inputEditorView.state.lineBreak;\n    }\n\n    /**\n     * Returns whether the input EOL sequence was set manually or has been detected automatically\n     * @returns {number} - 0 = unset, 1 = detected, 2 = manual\n     */\n    getEOLState() {\n        return this.eolState;\n    }\n\n    /**\n     * Handler for Chr Enc change events\n     * Sets the input character encoding\n     * @param {number} chrEncVal\n     * @param {boolean} [manual=false] - Flag to indicate the encoding was set by the user\n     * @param {boolean} [internal=false] - Flag to indicate this was set internally, i.e. by loading from URI\n     */\n    chrEncChange(chrEncVal, manual=false, internal=false) {\n        if (typeof chrEncVal !== \"number\") return;\n        this.inputChrEnc = chrEncVal;\n        this.encodingState = manual ? 2 : this.encodingState;\n        if (!internal) {\n            this.inputChange();\n        }\n    }\n\n    /**\n     * Getter for the input character encoding\n     * @returns {number}\n     */\n    getChrEnc() {\n        return this.inputChrEnc;\n    }\n\n    /**\n     * Returns whether the input character encoding was set manually or has been detected automatically\n     * @returns {number} - 0 = unset, 1 = detected, 2 = manual\n     */\n    getEncodingState() {\n        return this.encodingState;\n    }\n\n    /**\n     * Sets word wrap on the input editor\n     * @param {boolean} wrap\n     */\n    setWordWrap(wrap) {\n        this.inputEditorView.dispatch({\n            effects: this.inputEditorConf.lineWrapping.reconfigure(\n                wrap ? EditorView.lineWrapping : []\n            )\n        });\n    }\n\n    /**\n     * Gets the value of the current input\n     * @returns {string}\n     */\n    getInput() {\n        const doc = this.inputEditorView.state.doc;\n        const eol = this.getEOLSeq();\n        return doc.sliceString(0, doc.length, eol);\n    }\n\n    /**\n     * Sets the value of the current input\n     * @param {string} data\n     * @param {boolean} [silent=false]\n     */\n    setInput(data, silent=false) {\n        const lineLengthThreshold = 131072; // 128KB\n        let wrap = this.app.options.wordWrap;\n        if (data.length > lineLengthThreshold) {\n            const lines = data.split(this.getEOLSeq());\n            const longest = lines.reduce((a, b) =>\n                a > b.length ? a : b.length, 0\n            );\n            if (longest > lineLengthThreshold) {\n                // If we are exceeding the max line length, turn off word wrap\n                wrap = false;\n                this.app.alert(\"Maximum line length exceeded. Word wrap will be temporarily disabled to improve performance.\", 20000);\n            }\n        }\n\n        // If turning word wrap off, do it before we populate the editor for performance reasons\n        if (!wrap) this.setWordWrap(wrap);\n\n        // We use setTimeout here to delay the editor dispatch until the next event cycle,\n        // ensuring all async actions have completed before attempting to set the contents\n        // of the editor. This is mainly with the above call to setWordWrap() in mind.\n        setTimeout(() => {\n            // Insert data into editor, overwriting any previous contents\n            this.silentInputChange = silent;\n            this.inputEditorView.dispatch({\n                changes: {\n                    from: 0,\n                    to: this.inputEditorView.state.doc.length,\n                    insert: data\n                }\n            });\n\n            // If turning word wrap on, do it after we populate the editor\n            if (wrap)\n                setTimeout(() => {\n                    this.setWordWrap(wrap);\n                });\n        });\n    }\n\n    /**\n     * Calculates the maximum number of tabs to display\n     */\n    calcMaxTabs() {\n        const numTabs = this.manager.tabs.calcMaxTabs();\n        if (this.inputWorker && this.maxTabs !== numTabs) {\n            this.maxTabs = numTabs;\n            this.inputWorker.postMessage({\n                action: \"updateMaxTabs\",\n                data: {\n                    maxTabs: numTabs,\n                    activeTab: this.manager.tabs.getActiveTab(\"input\")\n                }\n            });\n        }\n    }\n\n    /**\n     * Terminates any existing workers and sets up a new InputWorker and LoaderWorker\n     */\n    setupInputWorker() {\n        if (this.inputWorker !== null) {\n            this.inputWorker.terminate();\n            this.inputWorker = null;\n        }\n\n        for (let i = this.loaderWorkers.length - 1; i >= 0; i--) {\n            this.removeLoaderWorker(this.loaderWorkers[i]);\n        }\n\n        log.debug(\"Adding new InputWorker\");\n        this.inputWorker = new InputWorker();\n        this.inputWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n        this.inputWorker.postMessage({\n            action: \"updateMaxWorkers\",\n            data: this.maxWorkers\n        });\n        this.inputWorker.postMessage({\n            action: \"updateMaxTabs\",\n            data: {\n                maxTabs: this.maxTabs,\n                activeTab: this.manager.tabs.getActiveTab(\"input\")\n            }\n        });\n\n        this.inputWorker.addEventListener(\"message\", this.handleInputWorkerMessage.bind(this));\n    }\n\n    /**\n     * Activates a loaderWorker and sends it to the InputWorker\n     */\n    activateLoaderWorker() {\n        const workerIdx = this.addLoaderWorker();\n        if (workerIdx === -1) return;\n\n        const workerObj = this.loaderWorkers[workerIdx];\n        this.inputWorker.postMessage({\n            action: \"loaderWorkerReady\",\n            data: {\n                id: workerObj.id\n            }\n        });\n    }\n\n    /**\n     * Adds a new loaderWorker\n     *\n     * @returns {number} - The index of the created worker\n     */\n    addLoaderWorker() {\n        if (this.loaderWorkers.length === this.maxWorkers) {\n            return -1;\n        }\n        log.debug(`Adding new LoaderWorker (${this.loaderWorkers.length + 1}/${this.maxWorkers}).`);\n        const newWorker = new LoaderWorker();\n        const workerId = this.workerId++;\n        newWorker.addEventListener(\"message\", this.handleLoaderMessage.bind(this));\n        newWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n        newWorker.postMessage({\n            action: \"setID\",\n            data: {\n                id: workerId\n            }\n        });\n        const newWorkerObj = {\n            worker: newWorker,\n            id: workerId\n        };\n        this.loaderWorkers.push(newWorkerObj);\n        return this.loaderWorkers.indexOf(newWorkerObj);\n    }\n\n    /**\n     * Removes a loaderworker\n     *\n     * @param {Object} workerObj - Object containing the loaderWorker and its id\n     * @param {LoaderWorker} workerObj.worker - The actual loaderWorker\n     * @param {number} workerObj.id - The ID of the loaderWorker\n     */\n    removeLoaderWorker(workerObj) {\n        const idx = this.loaderWorkers.indexOf(workerObj);\n        if (idx === -1) {\n            return;\n        }\n        log.debug(`Terminating worker ${this.loaderWorkers[idx].id}`);\n        this.loaderWorkers[idx].worker.terminate();\n        this.loaderWorkers.splice(idx, 1);\n    }\n\n    /**\n     * Finds and returns the object for the loaderWorker of a given id\n     *\n     * @param {number} id - The ID of the loaderWorker to find\n     * @returns {object}\n     */\n    getLoaderWorker(id) {\n        const idx = this.getLoaderWorkerIndex(id);\n        if (idx === -1) return;\n        return this.loaderWorkers[idx];\n    }\n\n    /**\n     * Gets the index for the loaderWorker of a given id\n     *\n     * @param {number} id - The ID of hte loaderWorker to find\n     * @returns {number} The current index of the loaderWorker in the array\n     */\n    getLoaderWorkerIndex(id) {\n        for (let i = 0; i < this.loaderWorkers.length; i++) {\n            if (this.loaderWorkers[i].id === id) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    /**\n     * Sends an input to be loaded to the loaderWorker\n     *\n     * @param {object} inputData - Object containing the input to be loaded\n     * @param {File} inputData.file - The actual file object to load\n     * @param {number} inputData.inputNum - The inputNum for the file object\n     * @param {number} inputData.workerId - The ID of the loaderWorker that will load it\n     */\n    loadInput(inputData) {\n        const idx = this.getLoaderWorkerIndex(inputData.workerId);\n        if (idx === -1) return;\n        this.loaderWorkers[idx].worker.postMessage({\n            action: \"loadFile\",\n            data: {\n                file: inputData.file,\n                inputNum: inputData.inputNum\n            }\n        });\n    }\n\n    /**\n     * Handler for messages sent back by the loaderWorker\n     * Sends the message straight to the inputWorker to be handled there.\n     *\n     * @param {MessageEvent} e\n     */\n    handleLoaderMessage(e) {\n        const r = e.data;\n\n        if (Object.prototype.hasOwnProperty.call(r, \"progress\") &&\n            Object.prototype.hasOwnProperty.call(r, \"inputNum\")) {\n            this.manager.tabs.updateTabProgress(r.inputNum, r.progress, 100, \"input\");\n        }\n\n        const transferable = Object.prototype.hasOwnProperty.call(r, \"fileBuffer\") ? [r.fileBuffer] : undefined;\n        this.inputWorker.postMessage({\n            action: \"loaderWorkerMessage\",\n            data: r\n        }, transferable);\n    }\n\n\n    /**\n     * Handler for messages sent back by the InputWorker\n     *\n     * @param {MessageEvent} e\n     */\n    handleInputWorkerMessage(e) {\n        const r = e.data;\n\n        if (!(\"action\" in r)) {\n            log.error(\"A message was received from the InputWorker with no action property. Ignoring message.\");\n            return;\n        }\n\n        log.debug(`Receiving '${r.action}' from InputWorker.`);\n\n        switch (r.action) {\n            case \"activateLoaderWorker\":\n                this.activateLoaderWorker();\n                break;\n            case \"loadInput\":\n                this.loadInput(r.data);\n                break;\n            case \"terminateLoaderWorker\":\n                this.removeLoaderWorker(this.getLoaderWorker(r.data));\n                break;\n            case \"refreshTabs\":\n                this.refreshTabs(r.data.nums, r.data.activeTab, r.data.tabsLeft, r.data.tabsRight);\n                break;\n            case \"changeTab\":\n                this.changeTab(r.data, this.app.options.syncTabs);\n                break;\n            case \"updateTabHeader\":\n                this.manager.tabs.updateTabHeader(r.data.inputNum, r.data.input, \"input\");\n                break;\n            case \"loadingInfo\":\n                this.showLoadingInfo(r.data, true);\n                break;\n            case \"setInput\":\n                this.set(r.data.inputNum, r.data.inputObj, r.data.silent);\n                break;\n            case \"inputAdded\":\n                this.inputAdded(r.data.changeTab, r.data.inputNum);\n                break;\n            case \"queueInput\":\n                this.manager.worker.queueInput(r.data);\n                break;\n            case \"queueInputError\":\n                this.manager.worker.queueInputError(r.data);\n                break;\n            case \"bakeInputs\":\n                this.manager.worker.bakeInputs(r.data);\n                break;\n            case \"displayTabSearchResults\":\n                this.displayTabSearchResults(r.data);\n                break;\n            case \"filterTabError\":\n                this.app.handleError(r.data);\n                break;\n            case \"setUrl\":\n                this.app.updateURL(r.data.includeInput, r.data.input);\n                break;\n            case \"getInput\":\n            case \"getInputNums\":\n                this.callbacks[r.data.id](r.data);\n                break;\n            case \"removeChefWorker\":\n                this.removeChefWorker();\n                break;\n            case \"fileLoaded\":\n                this.fileLoaded(r.data.inputNum);\n                break;\n            default:\n                log.error(`Unknown action ${r.action}.`);\n        }\n    }\n\n    /**\n     * Sends a message to the inputWorker to bake all inputs\n     */\n    bakeAll() {\n        this.app.progress = 0;\n        debounce(this.manager.controls.toggleBakeButtonFunction, 20, \"toggleBakeButton\", this, [\"loading\"]);\n        this.inputWorker.postMessage({\n            action: \"bakeAll\"\n        });\n    }\n\n    /**\n     * Sets the input in the input area\n     *\n     * @param {number} inputNum\n     * @param {Object} inputData - Object containing the input and its metadata\n     *     @param {string} type\n     *     @param {ArrayBuffer} buffer\n     *     @param {string} stringSample\n     *     @param {Object} file\n     *         @param {string} file.name\n     *         @param {number} file.size\n     *         @param {string} file.type\n     *     @param {string} status\n     *     @param {number} progress\n     *     @param {number} encoding\n     *     @param {string} eolSequence\n     * @param {boolean} [silent=false] - If false, fires the manager statechange event\n     */\n    async set(inputNum, inputData, silent=false) {\n        return new Promise(function(resolve, reject) {\n            const activeTab = this.manager.tabs.getActiveTab(\"input\");\n            if (inputNum !== activeTab) {\n                this.changeTab(inputNum, this.app.options.syncTabs);\n                return;\n            }\n\n            // Update current character encoding\n            this.inputChrEnc = inputData.encoding;\n\n            // Update current eol sequence\n            this.inputEditorView.dispatch({\n                effects: this.inputEditorConf.eol.reconfigure(\n                    EditorState.lineSeparator.of(inputData.eolSequence)\n                )\n            });\n\n            // Handle file previews\n            if (inputData.file) {\n                this.setFile(inputNum, inputData);\n            } else {\n                this.clearFile(inputNum);\n            }\n\n            // Decode the data to a string\n            this.manager.timing.recordTime(\"inputEncodingStart\", inputNum);\n            let inputVal;\n            if (this.getChrEnc() > 0) {\n                inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer));\n            } else {\n                inputVal = Utils.arrayBufferToStr(inputData.buffer);\n            }\n            this.manager.timing.recordTime(\"inputEncodingEnd\", inputNum);\n\n            // Populate the input editor\n            this.setInput(inputVal, silent);\n\n            // Set URL to current input\n            if (inputVal.length >= 0 && inputVal.length <= 51200) {\n                const inputStr = toBase64(inputVal, \"A-Za-z0-9+/\");\n                this.app.updateURL(true, inputStr);\n            }\n        }.bind(this));\n    }\n\n    /**\n     * Displays file details\n     *\n     * @param {number} inputNum\n     * @param {Object} inputData - Object containing the input and its metadata\n     *     @param {string} type\n     *     @param {ArrayBuffer} buffer\n     *     @param {string} stringSample\n     *     @param {Object} file\n     *         @param {string} file.name\n     *         @param {number} file.size\n     *         @param {string} file.type\n     *     @param {string} status\n     *     @param {number} progress\n     */\n    setFile(inputNum, inputData) {\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        if (inputNum !== activeTab) return;\n\n        // Create file details panel\n        this.fileDetails = {\n            fileDetails: inputData.file,\n            progress: inputData.progress,\n            status: inputData.status,\n            buffer: inputData.buffer,\n            renderPreview: this.app.options.imagePreview,\n            toggleHandler: this.toggleFileDetails.bind(this),\n            hidden: false\n        };\n        this.inputEditorView.dispatch({\n            effects: this.inputEditorConf.fileDetailsPanel.reconfigure(\n                fileDetailsPanel(this.fileDetails)\n            )\n        });\n    }\n\n    /**\n     * Clears the file details panel\n     *\n     * @param {number} inputNum\n     */\n    clearFile(inputNum) {\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        if (inputNum !== activeTab) return;\n\n        // Clear file details panel\n        this.inputEditorView.dispatch({\n            effects: this.inputEditorConf.fileDetailsPanel.reconfigure([])\n        });\n    }\n\n    /**\n     * Handler for file details toggle clicks\n     * @param {event} e\n     */\n    toggleFileDetails(e) {\n        $(\"[data-toggle='tooltip']\").tooltip(\"hide\");\n        this.fileDetails.hidden = !this.fileDetails.hidden;\n        this.inputEditorView.dispatch({\n            effects: this.inputEditorConf.fileDetailsPanel.reconfigure(\n                fileDetailsPanel(this.fileDetails)\n            )\n        });\n    }\n\n    /**\n     * Update file details when a file completes loading\n     *\n     * @param {number} inputNum - The inputNum of the input which has finished loading\n     */\n    fileLoaded(inputNum) {\n        this.manager.tabs.updateTabProgress(inputNum, 100, 100, \"input\");\n\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        if (activeTab !== inputNum) return;\n\n        this.inputWorker.postMessage({\n            action: \"setInput\",\n            data: {\n                inputNum: inputNum,\n                silent: false\n            }\n        });\n\n        this.updateFileProgress(inputNum, 100);\n    }\n\n    /**\n     * Updates the displayed load progress for a file\n     *\n     * @param {number} inputNum\n     * @param {number | string} progress - Either a number or \"error\"\n     */\n    updateFileProgress(inputNum, progress) {\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        if (inputNum !== activeTab) return;\n\n        this.fileDetails.progress = progress;\n        if (progress === \"error\") this.fileDetails.status = \"error\";\n        this.inputEditorView.dispatch({\n            effects: this.inputEditorConf.fileDetailsPanel.reconfigure(\n                fileDetailsPanel(this.fileDetails)\n            )\n        });\n    }\n\n    /**\n     * Updates the stored value for the specified inputNum\n     *\n     * @param {number} inputNum\n     * @param {string | ArrayBuffer} value\n     */\n    updateInputValue(inputNum, value, force=false) {\n        // Prepare the value as a buffer (full value) and a string sample (up to 4096 bytes)\n        let buffer;\n        let stringSample = \"\";\n\n        // If value is a string, interpret it using the specified character encoding\n        const tabNum = this.manager.tabs.getActiveTab(\"input\");\n        this.manager.timing.recordTime(\"inputEncodingStart\", tabNum);\n        if (typeof value === \"string\") {\n            stringSample = value.slice(0, 4096);\n            if (this.getChrEnc() > 0) {\n                buffer = cptable.utils.encode(this.getChrEnc(), value);\n                buffer = new Uint8Array(buffer).buffer;\n            } else {\n                buffer = Utils.strToArrayBuffer(value);\n            }\n        } else {\n            buffer = value;\n            stringSample = Utils.arrayBufferToStr(value.slice(0, 4096));\n        }\n        this.manager.timing.recordTime(\"inputEncodingEnd\", tabNum);\n\n        // Update the deep link\n        const recipeStr = buffer.byteLength < 51200 ? toBase64(buffer, \"A-Za-z0-9+/\") : \"\"; // B64 alphabet with no padding\n        const includeInput = recipeStr.length > 0 && buffer.byteLength < 51200;\n        this.app.updateURL(includeInput, recipeStr);\n\n        // Post new value to the InputWorker\n        const transferable = [buffer];\n        this.inputWorker.postMessage({\n            action: \"updateInputValue\",\n            data: {\n                inputNum: inputNum,\n                buffer: buffer,\n                stringSample: stringSample,\n                encoding: this.getChrEnc(),\n                eolSequence: this.getEOLSeq()\n            }\n        }, transferable);\n    }\n\n    /**\n     * Get the input value for the specified input\n     *\n     * @param {number} inputNum - The inputNum of the input to retrieve from the inputWorker\n     * @returns {ArrayBuffer | string}\n     */\n    async getInputValue(inputNum) {\n        return await new Promise(resolve => {\n            this.getInputFromWorker(inputNum, false, r => {\n                resolve(r.data);\n            });\n        });\n    }\n\n    /**\n     * Get the input object for the specified input\n     *\n     * @param {number} inputNum - The inputNum of the input to retrieve from the inputWorker\n     * @returns {object}\n     */\n    async getInputObj(inputNum) {\n        return await new Promise(resolve => {\n            this.getInputFromWorker(inputNum, true, r => {\n                resolve(r.data);\n            });\n        });\n    }\n\n    /**\n     * Gets the specified input from the inputWorker\n     *\n     * @param {number} inputNum - The inputNum of the data to get\n     * @param {boolean} getObj - If true, get the actual data object of the input instead of just the value\n     * @param {Function} callback - The callback to execute when the input is returned\n     * @returns {ArrayBuffer | string | object}\n     */\n    getInputFromWorker(inputNum, getObj, callback) {\n        const id = this.callbackID++;\n\n        this.callbacks[id] = callback;\n\n        this.inputWorker.postMessage({\n            action: \"getInput\",\n            data: {\n                inputNum: inputNum,\n                getObj: getObj,\n                id: id\n            }\n        });\n    }\n\n    /**\n     * Gets the number of inputs from the inputWorker\n     *\n     * @returns {object}\n     */\n    async getInputNums() {\n        return await new Promise(resolve => {\n            this.getNums(r => {\n                resolve(r);\n            });\n        });\n    }\n\n    /**\n     * Gets a list of inputNums from the inputWorker, and sends\n     * them back to the specified callback\n     */\n    getNums(callback) {\n        const id = this.callbackID++;\n\n        this.callbacks[id] = callback;\n\n        this.inputWorker.postMessage({\n            action: \"getInputNums\",\n            data: id\n        });\n    }\n\n    /**\n     * Handler for input change events.\n     * Updates the value stored in the inputWorker\n     * Debounces the input so we don't call autobake too often.\n     *\n     * @param {event} e\n     *\n     * @fires Manager#statechange\n     */\n    inputChange(e) {\n        // Change debounce delay based on input length\n        const inputLength = this.inputEditorView.state.doc.length;\n        let delay;\n        if (inputLength < 10000) delay = 20;\n        else if (inputLength < 100000) delay = 50;\n        else if (inputLength < 1000000) delay = 200;\n        else delay = 500;\n\n        debounce(function(e) {\n            const value = this.getInput();\n            const activeTab = this.manager.tabs.getActiveTab(\"input\");\n\n            this.updateInputValue(activeTab, value);\n            this.inputWorker.postMessage({\n                action: \"updateTabHeader\",\n                data: activeTab\n            });\n\n            // Fire the statechange event as the input has been modified\n            window.dispatchEvent(this.manager.statechange);\n        }, delay, \"inputChange\", this, [e])();\n    }\n\n    /**\n     * Handler that fires just after input paste events.\n     * Checks whether the EOL separator or character encoding should be updated.\n     *\n     * @param {File[]} files - An array of any files that were included in the paste event\n     */\n    afterPaste(files) {\n        if (files.length > 0) {\n            this.loadUIFiles(files);\n        }\n        // If EOL has been fixed, skip this.\n        if (this.eolState > 1) return;\n\n        const inputText = this.getInput();\n\n        // Detect most likely EOL sequence\n        const eolCharCounts = {\n            \"LF\": inputText.count(\"\\u000a\"),\n            \"VT\": inputText.count(\"\\u000b\"),\n            \"FF\": inputText.count(\"\\u000c\"),\n            \"CR\": inputText.count(\"\\u000d\"),\n            \"CRLF\": inputText.count(\"\\u000d\\u000a\"),\n            \"NEL\": inputText.count(\"\\u0085\"),\n            \"LS\": inputText.count(\"\\u2028\"),\n            \"PS\": inputText.count(\"\\u2029\")\n        };\n\n        // If all zero, leave alone\n        const total = Object.values(eolCharCounts).reduce((acc, curr) => {\n            return acc + curr;\n        }, 0);\n        if (total === 0) return;\n\n        // Find most prevalent line ending sequence\n        const highest = Object.entries(eolCharCounts).reduce((acc, curr) => {\n            return curr[1] > acc[1] ? curr : acc;\n        }, [\"LF\", 0]);\n        let choice = highest[0];\n\n        // If CRLF not zero and more than half the highest alternative, choose CRLF\n        if ((eolCharCounts.CRLF * 2) > highest[1]) {\n            choice = \"CRLF\";\n        }\n\n        const eolVal = eolCodeToSeq[choice];\n        if (eolVal === this.getEOLSeq()) return;\n\n        // Setting automatically\n        this.eolState = 1;\n        this.eolChange(choice);\n    }\n\n    /**\n     * Handler for input dragover events.\n     * Gives the user a visual cue to show that items can be dropped here.\n     *\n     * @param {event} e\n     */\n    inputDragover(e) {\n        // This will be set if we're dragging an operation\n        if (e.dataTransfer.effectAllowed === \"move\")\n            return false;\n\n        e.stopPropagation();\n        e.preventDefault();\n        e.target.closest(\"#input-text\").classList.add(\"dropping-file\");\n    }\n\n    /**\n     * Handler for input dragleave events.\n     * Removes the visual cue.\n     *\n     * @param {event} e\n     */\n    inputDragleave(e) {\n        e.stopPropagation();\n        e.preventDefault();\n\n        // Dragleave often fires when moving between lines in the editor.\n        // If the from element is within the input-text element, we are still on target.\n        if (!this.inputTextEl.contains(e.fromElement)) {\n            e.target.closest(\"#input-text\").classList.remove(\"dropping-file\");\n        }\n    }\n\n    /**\n     * Handler for input drop events.\n     * Loads the dragged data.\n     *\n     * @param {event} e\n     */\n    async inputDrop(e) {\n        // This will be set if we're dragging an operation\n        if (e.dataTransfer.effectAllowed === \"move\")\n            return false;\n\n        e.stopPropagation();\n        e.preventDefault();\n        e.target.closest(\"#input-text\").classList.remove(\"dropping-file\");\n\n        // Dropped text is handled by the editor itself\n        if (e.dataTransfer.getData(\"Text\")) return;\n\n        // Dropped files\n        if (e?.dataTransfer?.files?.length > 0) {\n            let files = [];\n\n            // Handling the files as FileSystemEntry objects allows us to open directories,\n            // but relies on a function that may be deprecated in future.\n            if (Object.prototype.hasOwnProperty.call(DataTransferItem.prototype, \"webkitGetAsEntry\")) {\n                const fileEntries = await this.getAllFileEntries(e.dataTransfer.items);\n                // Read all FileEntry objects into File objects\n                files = await Promise.all(fileEntries.map(async fe => await this.getFile(fe)));\n            } else {\n                files = e.dataTransfer.files;\n            }\n\n            this.loadUIFiles(files);\n        }\n    }\n\n    /**\n     *\n     * @param {DataTransferItemList} dataTransferItemList\n     * @returns {FileSystemEntry[]}\n     */\n    async getAllFileEntries(dataTransferItemList) {\n        const fileEntries = [];\n        // Use BFS to traverse entire directory/file structure\n        const queue = [];\n        // Unfortunately dataTransferItemList is not iterable i.e. no forEach\n        for (let i = 0; i < dataTransferItemList.length; i++) {\n            // Note webkitGetAsEntry a non-standard feature and may change\n            // Usage is necessary for handling directories\n            queue.push(dataTransferItemList[i].webkitGetAsEntry());\n        }\n        while (queue.length > 0) {\n            const entry = queue.shift();\n            if (entry.isFile) {\n                fileEntries.push(entry);\n            } else if (entry.isDirectory) {\n                queue.push(...await this.readAllDirectoryEntries(entry.createReader()));\n            }\n        }\n        return fileEntries;\n    }\n\n    /**\n     * Get all the entries (files or sub-directories) in a directory by calling\n     * readEntries until it returns empty array\n     *\n     * @param {FileSystemDirectoryReader} directoryReader\n     * @returns {FileSystemEntry[]}\n     */\n    async readAllDirectoryEntries(directoryReader) {\n        const entries = [];\n        let readEntries = await this.readEntriesPromise(directoryReader);\n        while (readEntries.length > 0) {\n            entries.push(...readEntries);\n            readEntries = await this.readEntriesPromise(directoryReader);\n        }\n        return entries;\n    }\n\n    /**\n     * Wrap readEntries in a promise to make working with readEntries easier.\n     * readEntries will return only some of the entries in a directory\n     * e.g. Chrome returns at most 100 entries at a time\n     *\n     * @param {FileSystemDirectoryReader} directoryReader\n     * @returns {Promise}\n     */\n    async readEntriesPromise(directoryReader) {\n        try {\n            return await new Promise((resolve, reject) => {\n                directoryReader.readEntries(resolve, reject);\n            });\n        } catch (err) {\n            log.error(err);\n        }\n    }\n\n    /**\n     * Reads a FileEntry and returns it as a File object\n     * @param {FileEntry} fileEntry\n     * @returns {File}\n     */\n    async getFile(fileEntry) {\n        try {\n            return new Promise((resolve, reject) => fileEntry.file(resolve, reject));\n        } catch (err) {\n            log.error(err);\n        }\n    }\n\n    /**\n     * Handler for open input button events\n     * Loads the opened data into the input textarea\n     *\n     * @param {event} e\n     */\n    inputOpen(e) {\n        e.preventDefault();\n\n        if (e.target.files.length > 0) {\n            this.loadUIFiles(e.target.files);\n            e.target.value = \"\";\n        }\n    }\n\n    /**\n     * Handler for open input button click.\n     * Opens the open file dialog.\n     */\n    inputOpenClick() {\n        document.getElementById(\"open-file\").click();\n    }\n\n    /**\n     * Handler for open folder button click\n     * Opens the open folder dialog.\n     */\n    folderOpenClick() {\n        document.getElementById(\"open-folder\").click();\n    }\n\n    /**\n     * Load files from the UI into the inputWorker\n     *\n     * @param {FileList} files - The list of files to be loaded\n     */\n    loadUIFiles(files) {\n        const numFiles = files.length;\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        log.debug(`Loading ${numFiles} files.`);\n\n        // Display the number of files as pending so the user\n        // knows that we've received the files.\n        this.showLoadingInfo({\n            pending: numFiles,\n            loading: 0,\n            loaded: 0,\n            total: numFiles,\n            activeProgress: {\n                inputNum: activeTab,\n                progress: 0\n            }\n        }, false);\n\n        this.inputWorker.postMessage({\n            action: \"loadUIFiles\",\n            data: {\n                files: files,\n                activeTab: activeTab\n            }\n        });\n    }\n\n    /**\n     * Display the loaded files information in the input header.\n     * Also, sets the background of the Input header to be a progress bar\n     * @param {object} loadedData - Object containing the loading information\n     * @param {number} loadedData.pending - How many files are pending (not loading / loaded)\n     * @param {number} loadedData.loading - How many files are being loaded\n     * @param {number} loadedData.loaded - How many files have been loaded\n     * @param {number} loadedData.total - The total number of files\n     * @param {object} loadedData.activeProgress - Object containing data about the active tab\n     * @param {number} loadedData.activeProgress.inputNum - The inputNum of the input the progress is for\n     * @param {number} loadedData.activeProgress.progress - The loading progress of the active input\n     * @param {boolean} autoRefresh - If true, automatically refreshes the loading info by sending a message to the inputWorker after 100ms\n     */\n    showLoadingInfo(loadedData, autoRefresh) {\n        const pending = loadedData.pending;\n        const loading = loadedData.loading;\n        const loaded = loadedData.loaded;\n        const total = loadedData.total;\n\n        let width = total.toLocaleString().length;\n        width = width < 2 ? 2 : width;\n\n        const totalStr = total.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n        let msg = \"total: \" + totalStr;\n\n        const loadedStr = loaded.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n        msg += \"<br>loaded: \" + loadedStr;\n\n        if (pending > 0) {\n            const pendingStr = pending.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n            msg += \"<br>pending: \" + pendingStr;\n        } else if (loading > 0) {\n            const loadingStr = loading.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n            msg += \"<br>loading: \" + loadingStr;\n        }\n\n        const inFiles = document.getElementById(\"input-files-info\");\n        if (total > 1) {\n            inFiles.innerHTML = msg;\n            inFiles.style.display = \"\";\n        } else {\n            inFiles.style.display = \"none\";\n        }\n\n        this.updateFileProgress(loadedData.activeProgress.inputNum, loadedData.activeProgress.progress);\n\n        const inputTitle = document.getElementById(\"input\").firstElementChild;\n        if (loaded < total) {\n            const percentComplete = loaded / total * 100;\n            inputTitle.style.background = `linear-gradient(to right, var(--title-background-colour) ${percentComplete}%, var(--primary-background-colour) ${percentComplete}%)`;\n        } else {\n            inputTitle.style.background = \"\";\n        }\n\n        if (loaded < total && autoRefresh) {\n            setTimeout(function() {\n                this.inputWorker.postMessage({\n                    action: \"getLoadProgress\",\n                    data: this.manager.tabs.getActiveTab(\"input\")\n                });\n            }.bind(this), 100);\n        }\n    }\n\n    /**\n     * Change to a different tab.\n     *\n     * @param {number} inputNum - The inputNum of the tab to change to\n     * @param {boolean} [changeOutput=false] - If true, also changes the output\n     */\n    changeTab(inputNum, changeOutput=false) {\n        if (this.manager.tabs.getTabItem(inputNum, \"input\") !== null) {\n            this.manager.tabs.changeTab(inputNum, \"input\");\n            this.inputWorker.postMessage({\n                action: \"setInput\",\n                data: {\n                    inputNum: inputNum,\n                    silent: true\n                }\n            });\n        } else {\n            const minNum = Math.min(...this.manager.tabs.getTabList(\"input\"));\n            let direction = \"right\";\n            if (inputNum < minNum) {\n                direction = \"left\";\n            }\n            this.inputWorker.postMessage({\n                action: \"refreshTabs\",\n                data: {\n                    inputNum: inputNum,\n                    direction: direction\n                }\n            });\n        }\n\n        if (changeOutput) {\n            this.manager.output.changeTab(inputNum, false);\n        }\n\n        // Set cursor focus to current tab\n        this.inputEditorView.focus();\n    }\n\n    /**\n     * Handler for clicking on a tab\n     *\n     * @param {event} mouseEvent\n     */\n    changeTabClick(mouseEvent) {\n        if (!mouseEvent.target) return;\n\n        const tabNum = mouseEvent.target.parentElement.getAttribute(\"inputNum\");\n        if (tabNum >= 0) {\n            this.changeTab(parseInt(tabNum, 10), this.app.options.syncTabs);\n        }\n    }\n\n    /**\n     * Handler for clear all IO events.\n     * Resets the input, output and info areas, and creates a new inputWorker\n     */\n    clearAllIoClick() {\n        this.manager.worker.cancelBake(true, true);\n        this.manager.worker.loaded = false;\n        this.manager.output.removeAllOutputs();\n        this.manager.output.terminateZipWorker();\n\n        this.eolState = 0;\n        this.encodingState = 0;\n        this.manager.output.eolState = 0;\n        this.manager.output.encodingState = 0;\n\n        this.initEditor();\n        this.manager.output.initEditor();\n\n        const tabsList = document.getElementById(\"input-tabs\");\n        const tabsListChildren = tabsList.children;\n\n        tabsList.classList.remove(\"tabs-left\");\n        tabsList.classList.remove(\"tabs-right\");\n        for (let i = tabsListChildren.length - 1; i >= 0; i--) {\n            tabsListChildren.item(i).remove();\n        }\n\n        this.showLoadingInfo({\n            pending: 0,\n            loading: 0,\n            loaded: 1,\n            total: 1,\n            activeProgress: {\n                inputNum: 1,\n                progress: 100\n            }\n        });\n\n        this.setupInputWorker();\n        this.manager.worker.setupChefWorker();\n        this.addInput(true);\n    }\n\n    /**\n     * Sets the console log level in the workers.\n     */\n    setLogLevel() {\n        this.loaderWorkers.forEach(w => {\n            w.postMessage({\n                action: \"setLogLevel\",\n                data: log.getLevel()\n            });\n        });\n\n        if (!this.inputWorker) return;\n        this.inputWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n    }\n\n    /**\n     * Sends a message to the inputWorker to add a new input.\n     * @param {boolean} [changeTab=false] - If true, changes the tab to the new input\n     */\n    addInput(changeTab=false) {\n        if (!this.inputWorker) return;\n        this.inputWorker.postMessage({\n            action: \"addInput\",\n            data: changeTab\n        });\n    }\n\n    /**\n     * Handler for add input button clicked.\n     */\n    addInputClick() {\n        this.addInput(true);\n    }\n\n    /**\n     * Handler for when the inputWorker adds a new input\n     *\n     * @param {boolean} changeTab - Whether or not to change to the new input tab\n     * @param {number} inputNum - The new inputNum\n     */\n    inputAdded(changeTab, inputNum) {\n        this.addTab(inputNum, changeTab);\n\n        this.manager.output.addOutput(inputNum, changeTab);\n        this.manager.worker.addChefWorker();\n    }\n\n    /**\n     * Remove a chefWorker from the workerWaiter if we remove an input\n     */\n    removeChefWorker() {\n        const workerIdx = this.manager.worker.getInactiveChefWorker(true);\n        const worker = this.manager.worker.chefWorkers[workerIdx];\n        this.manager.worker.removeChefWorker(worker);\n    }\n\n    /**\n     * Adds a new input tab.\n     *\n     * @param {number} inputNum - The inputNum of the new tab\n     * @param {boolean} [changeTab=true] - If true, changes to the new tab once it's been added\n     */\n    addTab(inputNum, changeTab=true) {\n        const tabsWrapper = document.getElementById(\"input-tabs\"),\n            numTabs = tabsWrapper.children.length;\n\n        if (!this.manager.tabs.getTabItem(inputNum, \"input\") && numTabs < this.maxTabs) {\n            const newTab = this.manager.tabs.createTabElement(inputNum, changeTab, \"input\");\n            tabsWrapper.appendChild(newTab);\n\n            if (numTabs > 0) {\n                this.manager.tabs.showTabBar();\n            } else {\n                this.manager.tabs.hideTabBar();\n            }\n\n            this.inputWorker.postMessage({\n                action: \"updateTabHeader\",\n                data: inputNum\n            });\n        } else if (numTabs === this.maxTabs) {\n            // Can't create a new tab\n            document.getElementById(\"input-tabs\").lastElementChild.classList.add(\"tabs-right\");\n        }\n\n        if (changeTab) this.changeTab(inputNum, false);\n    }\n\n    /**\n     * Refreshes the input tabs, and changes to activeTab\n     *\n     * @param {number[]} nums - The inputNums to be displayed as tabs\n     * @param {number} activeTab - The tab to change to\n     * @param {boolean} tabsLeft - True if there are input tabs to the left of the displayed tabs\n     * @param {boolean} tabsRight - True if there are input tabs to the right of the displayed tabs\n     */\n    refreshTabs(nums, activeTab, tabsLeft, tabsRight) {\n        this.manager.tabs.refreshTabs(nums, activeTab, tabsLeft, tabsRight, \"input\");\n\n        this.inputWorker.postMessage({\n            action: \"setInput\",\n            data: {\n                inputNum: activeTab,\n                silent: true\n            }\n        });\n    }\n\n    /**\n     * Sends a message to the inputWorker to remove an input.\n     * If the input tab is on the screen, refreshes the tabs\n     *\n     * @param {number} inputNum - The inputNum of the tab to be removed\n     */\n    removeInput(inputNum) {\n        let refresh = false;\n        if (this.manager.tabs.getTabItem(inputNum, \"input\") !== null) {\n            refresh = true;\n        }\n        this.inputWorker.postMessage({\n            action: \"removeInput\",\n            data: {\n                inputNum: inputNum,\n                refreshTabs: refresh,\n                removeChefWorker: true\n            }\n        });\n\n        this.manager.output.removeTab(inputNum);\n    }\n\n    /**\n     * Handler for clicking on a remove tab button\n     *\n     * @param {event} mouseEvent\n     */\n    removeTabClick(mouseEvent) {\n        if (!mouseEvent.target) {\n            return;\n        }\n        const tabNum = mouseEvent.target.closest(\"button\").parentElement.getAttribute(\"inputNum\");\n        if (tabNum) {\n            this.removeInput(parseInt(tabNum, 10));\n        }\n    }\n\n    /**\n     * Handler for scrolling on the input tabs area\n     *\n     * @param {event} wheelEvent\n     */\n    scrollTab(wheelEvent) {\n        wheelEvent.preventDefault();\n\n        if (wheelEvent.deltaY > 0) {\n            this.changeTabLeft();\n        } else if (wheelEvent.deltaY < 0) {\n            this.changeTabRight();\n        }\n    }\n\n    /**\n     * Handler for mouse down on the next tab button\n     */\n    nextTabClick() {\n        this.mousedown = true;\n        this.changeTabRight();\n        const time = 200;\n        const func = function(time) {\n            if (this.mousedown) {\n                this.changeTabRight();\n                const newTime = (time > 50) ? time - 10 : 50;\n                setTimeout(func.bind(this, [newTime]), newTime);\n            }\n        };\n        this.tabTimeout = setTimeout(func.bind(this, [time]), time);\n    }\n\n    /**\n     * Handler for mouse down on the previous tab button\n     */\n    previousTabClick() {\n        this.mousedown = true;\n        this.changeTabLeft();\n        const time = 200;\n        const func = function(time) {\n            if (this.mousedown) {\n                this.changeTabLeft();\n                const newTime = (time > 50) ? time - 10 : 50;\n                setTimeout(func.bind(this, [newTime]), newTime);\n            }\n        };\n        this.tabTimeout = setTimeout(func.bind(this, [time]), time);\n    }\n\n    /**\n     * Handler for mouse up event on the tab buttons\n     */\n    tabMouseUp() {\n        this.mousedown = false;\n\n        clearTimeout(this.tabTimeout);\n        this.tabTimeout = null;\n    }\n\n    /**\n     * Changes to the next (right) tab\n     */\n    changeTabRight() {\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        if (activeTab === -1) return;\n        this.inputWorker.postMessage({\n            action: \"changeTabRight\",\n            data: {\n                activeTab: activeTab\n            }\n        });\n    }\n\n    /**\n     * Changes to the previous (left) tab\n     */\n    changeTabLeft() {\n        const activeTab = this.manager.tabs.getActiveTab(\"input\");\n        if (activeTab === -1) return;\n        this.inputWorker.postMessage({\n            action: \"changeTabLeft\",\n            data: {\n                activeTab: activeTab\n            }\n        });\n    }\n\n    /**\n     * Handler for go to tab button clicked\n     */\n    async goToTab() {\n        const inputNums = await this.getInputNums();\n        let tabNum = window.prompt(`Enter tab number (${inputNums.min} - ${inputNums.max}):`, this.manager.tabs.getActiveTab(\"input\").toString());\n\n        if (tabNum === null) return;\n        tabNum = parseInt(tabNum, 10);\n\n        this.changeTab(tabNum, this.app.options.syncTabs);\n    }\n\n    /**\n     * Handler for find tab button clicked\n     */\n    findTab() {\n        this.filterTabSearch();\n        $(\"#input-tab-modal\").modal();\n    }\n\n    /**\n     * Sends a message to the inputWorker to search the inputs\n     */\n    filterTabSearch() {\n        const showPending = document.getElementById(\"input-show-pending\").checked;\n        const showLoading = document.getElementById(\"input-show-loading\").checked;\n        const showLoaded = document.getElementById(\"input-show-loaded\").checked;\n\n        const filter = document.getElementById(\"input-filter\").value;\n        const filterType = document.getElementById(\"input-filter-button\").innerText;\n        const numResults = parseInt(document.getElementById(\"input-num-results\").value, 10);\n\n        this.inputWorker.postMessage({\n            action: \"filterTabs\",\n            data: {\n                showPending: showPending,\n                showLoading: showLoading,\n                showLoaded: showLoaded,\n                filter: filter,\n                filterType: filterType,\n                numResults: numResults\n            }\n        });\n    }\n\n    /**\n     * Handle when an option in the filter drop down box is clicked\n     *\n     * @param {event} mouseEvent\n     */\n    filterOptionClick(mouseEvent) {\n        document.getElementById(\"input-filter-button\").innerText = mouseEvent.target.innerText;\n        this.filterTabSearch();\n    }\n\n    /**\n     * Displays the results of a tab search in the find tab box\n     *\n     * @param {object[]} results - List of results objects\n     *\n     */\n    displayTabSearchResults(results) {\n        const resultsList = document.getElementById(\"input-search-results\");\n\n        for (let i = resultsList.children.length - 1; i >= 0; i--) {\n            resultsList.children.item(i).remove();\n        }\n\n        for (let i = 0; i < results.length; i++) {\n            const newListItem = document.createElement(\"li\");\n            newListItem.classList.add(\"input-filter-result\");\n            newListItem.setAttribute(\"inputNum\", results[i].inputNum);\n            newListItem.innerText = `${results[i].inputNum}: ${results[i].textDisplay}`;\n\n            resultsList.appendChild(newListItem);\n        }\n    }\n\n    /**\n     * Handler for clicking on a filter result\n     *\n     * @param {event} e\n     */\n    filterItemClick(e) {\n        if (!e.target) return;\n        const inputNum = parseInt(e.target.getAttribute(\"inputNum\"), 10);\n        if (inputNum <= 0) return;\n\n        $(\"#input-tab-modal\").modal(\"hide\");\n        this.changeTab(inputNum, this.app.options.syncTabs);\n    }\n\n    /**\n     * Handler for incoming postMessages\n     * If the events data has a `type` property set to `dataSubmit`\n     * the value property is set to the current input\n     * @param {event} e\n     * @param {object} e.data\n     * @param {string} e.data.type - the type of request, currently the only value is \"dataSubmit\"\n     * @param {string} e.data.value - the value of the message\n     */\n    handlePostMessage(e) {\n        log.debug(e);\n        if (\"data\" in e && \"id\" in e.data && \"value\" in e.data) {\n            if (e.data.id === \"setInput\") {\n                this.setInput(e.data.value);\n            }\n        }\n    }\n}\n\nexport default InputWaiter;\n"
  },
  {
    "path": "src/web/waiters/OperationsWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport HTMLOperation from \"../HTMLOperation.mjs\";\nimport Sortable from \"sortablejs\";\nimport {fuzzyMatch, calcMatchRanges} from \"../../core/lib/FuzzyMatch.mjs\";\n\n\n/**\n * Waiter to handle events related to the operations.\n */\nclass OperationsWaiter {\n\n    /**\n     * OperationsWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.options = {};\n        this.removeIntent = false;\n    }\n\n\n    /**\n     * Handler for search events.\n     * Finds operations which match the given search term and displays them under the search box.\n     *\n     * @param {event} e\n     */\n    searchOperations(e) {\n        let ops, selected;\n\n        if (e.type === \"search\" || e.keyCode === 13) { // Search or Return\n            e.preventDefault();\n            ops = document.querySelectorAll(\"#search-results li\");\n            if (ops.length) {\n                selected = this.getSelectedOp(ops);\n                if (selected > -1) {\n                    this.manager.recipe.addOperation(ops[selected].innerHTML);\n                }\n            }\n        }\n\n        if (e.keyCode === 40) { // Down\n            e.preventDefault();\n            ops = document.querySelectorAll(\"#search-results li\");\n            if (ops.length) {\n                selected = this.getSelectedOp(ops);\n                if (selected > -1) {\n                    ops[selected].classList.remove(\"selected-op\");\n                }\n                if (selected === ops.length-1) selected = -1;\n                ops[selected+1].classList.add(\"selected-op\");\n            }\n        } else if (e.keyCode === 38) { // Up\n            e.preventDefault();\n            ops = document.querySelectorAll(\"#search-results li\");\n            if (ops.length) {\n                selected = this.getSelectedOp(ops);\n                if (selected > -1) {\n                    ops[selected].classList.remove(\"selected-op\");\n                }\n                if (selected === 0) selected = ops.length;\n                ops[selected-1].classList.add(\"selected-op\");\n            }\n        } else {\n            const searchResultsEl = document.getElementById(\"search-results\");\n            const el = e.target;\n            const str = el.value;\n\n            while (searchResultsEl.firstChild) {\n                try {\n                    $(searchResultsEl.firstChild).popover(\"dispose\");\n                } catch (err) {}\n                searchResultsEl.removeChild(searchResultsEl.firstChild);\n            }\n\n            $(\"#categories .show\").collapse(\"hide\");\n            if (str) {\n                const matchedOps = this.filterOperations(str, true);\n                const matchedOpsHtml = matchedOps\n                    .map(v => v.toStubHtml())\n                    .join(\"\");\n\n                searchResultsEl.innerHTML = matchedOpsHtml;\n                searchResultsEl.dispatchEvent(this.manager.oplistcreate);\n            }\n        }\n    }\n\n\n    /**\n     * Filters operations based on the search string and returns the matching ones.\n     *\n     * @param {string} searchStr\n     * @param {boolean} highlight - Whether or not to highlight the matching string in the operation\n     *   name and description\n     * @returns {string[]}\n     */\n    filterOperations(inStr, highlight) {\n        const matchedOps = [];\n        const matchedDescs = [];\n\n        // Create version with no whitespace for the fuzzy match\n        // Helps avoid missing matches e.g. query \"TCP \" would not find \"Parse TCP\"\n        const inStrNWS = inStr.replace(/\\s/g, \"\");\n\n        for (const opName in this.app.operations) {\n            const op = this.app.operations[opName];\n\n            // Match op name using fuzzy match\n            const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);\n\n            // Match description based on exact match\n            const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());\n\n            if (nameMatch || descPos >= 0) {\n                const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);\n                if (highlight) {\n                    operation.highlightSearchStrings(calcMatchRanges(idxs), [[descPos, inStr.length]]);\n                }\n\n                if (nameMatch) {\n                    matchedOps.push([operation, score]);\n                } else {\n                    matchedDescs.push(operation);\n                }\n            }\n        }\n\n        // Sort matched operations based on fuzzy score\n        matchedOps.sort((a, b) => b[1] - a[1]);\n\n        return matchedOps.map(a => a[0]).concat(matchedDescs);\n    }\n\n\n    /**\n     * Finds the operation which has been selected using keyboard shortcuts. This will have the class\n     * 'selected-op' set. Returns the index of the operation within the given list.\n     *\n     * @param {element[]} ops\n     * @returns {number}\n     */\n    getSelectedOp(ops) {\n        for (let i = 0; i < ops.length; i++) {\n            if (ops[i].classList.contains(\"selected-op\")) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n\n    /**\n     * Handler for oplistcreate events.\n     *\n     * @listens Manager#oplistcreate\n     * @param {event} e\n     */\n    opListCreate(e) {\n        this.manager.recipe.createSortableSeedList(e.target);\n\n        // Populate ops total\n        document.querySelector(\"#operations .title .op-count\").innerText = Object.keys(this.app.operations).length;\n\n        this.enableOpsListPopovers(e.target);\n    }\n\n\n    /**\n     * Sets up popovers, allowing the popover itself to gain focus which enables scrolling\n     * and other interactions.\n     *\n     * @param {Element} el - The element to start selecting from\n     */\n    enableOpsListPopovers(el) {\n        $(el).find(\"[data-toggle=popover]\").addBack(\"[data-toggle=popover]\")\n            .popover({trigger: \"manual\"})\n            .on(\"mouseenter\", function(e) {\n                if (e.buttons > 0) return; // Mouse button held down - likely dragging an operation\n                const _this = this;\n                $(this).popover(\"show\");\n                $(\".popover\").on(\"mouseleave\", function () {\n                    $(_this).popover(\"hide\");\n                });\n            }).on(\"mouseleave\", function () {\n                const _this = this;\n                setTimeout(function() {\n                    // Determine if the popover associated with this element is being hovered over\n                    if ($(_this).data(\"bs.popover\") &&\n                        ($(_this).data(\"bs.popover\").tip && !$($(_this).data(\"bs.popover\").tip).is(\":hover\"))) {\n                        $(_this).popover(\"hide\");\n                    }\n                }, 50);\n            });\n    }\n\n\n    /**\n     * Handler for operation doubleclick events.\n     * Adds the operation to the recipe and auto bakes.\n     *\n     * @param {event} e\n     */\n    operationDblclick(e) {\n        const li = e.target;\n\n        this.manager.recipe.addOperation(li.textContent);\n    }\n\n\n    /**\n     * Handler for edit favourites click events.\n     * Sets up the 'Edit favourites' pane and displays it.\n     *\n     * @param {event} e\n     */\n    editFavouritesClick(e) {\n        e.preventDefault();\n        e.stopPropagation();\n\n        // Add favourites to modal\n        const favCat = this.app.categories.filter(function(c) {\n            return c.name === \"Favourites\";\n        })[0];\n\n        let html = \"\";\n        for (let i = 0; i < favCat.ops.length; i++) {\n            const opName = favCat.ops[i];\n            const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);\n            html += operation.toStubHtml(true);\n        }\n\n        const editFavouritesList = document.getElementById(\"edit-favourites-list\");\n        editFavouritesList.innerHTML = html;\n        this.removeIntent = false;\n\n        const editableList = Sortable.create(editFavouritesList, {\n            filter: \".remove-icon\",\n            onFilter: function (evt) {\n                const el = editableList.closest(evt.item);\n                if (el && el.parentNode) {\n                    $(el).popover(\"dispose\");\n                    el.parentNode.removeChild(el);\n                }\n            },\n            onEnd: function(evt) {\n                if (this.removeIntent) {\n                    $(evt.item).popover(\"dispose\");\n                    evt.item.remove();\n                }\n            }.bind(this),\n        });\n\n        Sortable.utils.on(editFavouritesList, \"dragleave\", function() {\n            this.removeIntent = true;\n        }.bind(this));\n\n        Sortable.utils.on(editFavouritesList, \"dragover\", function() {\n            this.removeIntent = false;\n        }.bind(this));\n\n        $(\"#edit-favourites-list [data-toggle=popover]\").popover();\n        $(\"#favourites-modal\").modal();\n    }\n\n\n    /**\n     * Handler for save favourites click events.\n     * Saves the selected favourites and reloads them.\n     */\n    saveFavouritesClick() {\n        const favs = document.querySelectorAll(\"#edit-favourites-list li\");\n        const favouritesList = Array.from(favs, e => e.childNodes[0].textContent);\n\n        this.app.saveFavourites(favouritesList);\n        this.app.loadFavourites();\n        this.app.populateOperationsList();\n        this.manager.recipe.initialiseOperationDragNDrop();\n    }\n\n\n    /**\n     * Handler for reset favourites click events.\n     * Resets favourites to their defaults.\n     */\n    resetFavouritesClick() {\n        this.app.resetFavourites();\n    }\n\n\n    /**\n     * Sets whether operation counts are displayed next to a category title\n     */\n    setCatCount() {\n        if (this.app.options.showCatCount) {\n            document.querySelectorAll(\".category-title .op-count\").forEach(el => el.classList.remove(\"hidden\"));\n        } else {\n            document.querySelectorAll(\".category-title .op-count\").forEach(el => el.classList.add(\"hidden\"));\n        }\n    }\n\n}\n\nexport default OperationsWaiter;\n"
  },
  {
    "path": "src/web/waiters/OptionsWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n/**\n * Waiter to handle events related to the CyberChef options.\n */\nclass OptionsWaiter {\n\n    /**\n     * OptionsWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n    }\n\n    /**\n     * Loads options and sets values of switches and inputs to match them.\n     *\n     * @param {Object} options\n     */\n    load(options) {\n        Object.assign(this.app.options, options);\n\n        // Set options to match object\n        document.querySelectorAll(\"#options-body input[type=checkbox]\").forEach(cbox => {\n            cbox.checked = this.app.options[cbox.getAttribute(\"option\")];\n        });\n\n        document.querySelectorAll(\"#options-body input[type=number]\").forEach(nbox => {\n            nbox.value = this.app.options[nbox.getAttribute(\"option\")];\n            nbox.dispatchEvent(new CustomEvent(\"change\", {bubbles: true}));\n        });\n\n        document.querySelectorAll(\"#options-body select\").forEach(select => {\n            const val = this.app.options[select.getAttribute(\"option\")];\n            if (val) {\n                select.value = val;\n                select.dispatchEvent(new CustomEvent(\"change\", {bubbles: true}));\n            } else {\n                select.selectedIndex = 0;\n            }\n        });\n\n        // Initialise options\n        this.setWordWrap();\n        this.manager.ops.setCatCount();\n    }\n\n\n    /**\n     * Handler for options click events.\n     * Displays the options pane.\n     *\n     * @param {event} e\n     */\n    optionsClick(e) {\n        e.preventDefault();\n        $(\"#options-modal\").modal();\n    }\n\n\n    /**\n     * Handler for reset options click events.\n     * Resets options back to their default values.\n     */\n    resetOptionsClick() {\n        this.load(this.app.doptions);\n    }\n\n\n    /**\n     * Handler for switch change events.\n     *\n     * @param {event} e\n     */\n    switchChange(e) {\n        const el = e.target;\n        const option = el.getAttribute(\"option\");\n        const state = el.checked;\n\n        this.updateOption(option, state);\n    }\n\n\n    /**\n     * Handler for number change events.\n     *\n     * @param {event} e\n     */\n    numberChange(e) {\n        const el = e.target;\n        const option = el.getAttribute(\"option\");\n        const val = parseInt(el.value, 10);\n\n        this.updateOption(option, val);\n    }\n\n\n    /**\n     * Handler for select change events.\n     *\n     * @param {event} e\n     */\n    selectChange(e) {\n        const el = e.target;\n        const option = el.getAttribute(\"option\");\n\n        this.updateOption(option, el.value);\n    }\n\n    /**\n     * Modifies an option value and saves it to local storage.\n     *\n     * @param {string} option - The option to be updated\n     * @param {string|number|boolean} value - The new value of the option\n     */\n    updateOption(option, value) {\n        log.debug(`Setting ${option} to ${value}`);\n        this.app.options[option] = value;\n\n        if (this.app.isLocalStorageAvailable())\n            localStorage.setItem(\"options\", JSON.stringify(this.app.options));\n    }\n\n\n    /**\n     * Sets or unsets word wrap on the input and output depending on the wordWrap option value.\n     */\n    setWordWrap() {\n        this.manager.input.setWordWrap(this.app.options.wordWrap);\n        this.manager.output.setWordWrap(this.app.options.wordWrap);\n    }\n\n\n    /**\n     * Theme change event listener\n     *\n     * @param {Event} e\n     */\n    themeChange(e) {\n        const themeClass = e.target.value;\n        this.changeTheme(themeClass);\n    }\n\n\n    /**\n     * Changes the theme by setting the class of the <html> element.\n     *\n     * @param (string} theme\n     */\n    changeTheme(theme) {\n        document.querySelector(\":root\").className = theme;\n\n        // Update theme selection\n        const themeSelect = document.getElementById(\"theme\");\n        let themeOption = themeSelect.querySelector(`option[value=\"${theme}\"]`);\n\n        if (!themeOption) {\n            const preferredColorScheme = this.getPreferredColorScheme();\n            document.querySelector(\":root\").className = preferredColorScheme;\n            themeOption = themeSelect.querySelector(`option[value=\"${preferredColorScheme}\"]`);\n        }\n\n        themeSelect.selectedIndex = themeOption.index;\n    }\n\n    /**\n     * Applies the user's preferred color scheme using the `prefers-color-scheme` media query.\n     */\n    applyPreferredColorScheme() {\n        const themeFromStorage = this.app?.options?.theme;\n        let theme = themeFromStorage;\n        if (!theme) {\n            theme = this.getPreferredColorScheme();\n        }\n        this.changeTheme(theme);\n    }\n\n    /**\n     * Get the user's preferred color scheme using the `prefers-color-scheme` media query.\n     */\n    getPreferredColorScheme() {\n        const prefersDarkScheme = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n        return prefersDarkScheme ? \"dark\" : \"classic\";\n    }\n\n    /**\n     * Changes the console logging level.\n     *\n     * @param {Event} e\n     */\n    logLevelChange(e) {\n        const level = e.target.value;\n        log.setLevel(level, false);\n        this.manager.worker.setLogLevel();\n        this.manager.input.setLogLevel();\n        this.manager.output.setLogLevel();\n        this.manager.background.setLogLevel();\n    }\n}\n\nexport default OptionsWaiter;\n"
  },
  {
    "path": "src/web/waiters/OutputWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport Utils, {debounce} from \"../../core/Utils.mjs\";\nimport Dish from \"../../core/Dish.mjs\";\nimport {isUTF8, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from \"../../core/lib/ChrEnc.mjs\";\nimport {detectFileType} from \"../../core/lib/FileType.mjs\";\nimport FileSaver from \"file-saver\";\nimport ZipWorker from \"worker-loader?inline=no-fallback!../workers/ZipWorker.mjs\";\n\nimport {\n    EditorView,\n    keymap,\n    highlightSpecialChars,\n    drawSelection,\n    rectangularSelection,\n    crosshairCursor\n} from \"@codemirror/view\";\nimport {\n    EditorState,\n    Compartment\n} from \"@codemirror/state\";\nimport {\n    defaultKeymap\n} from \"@codemirror/commands\";\nimport {\n    bracketMatching\n} from \"@codemirror/language\";\nimport {\n    search,\n    searchKeymap,\n    highlightSelectionMatches\n} from \"@codemirror/search\";\n\nimport {statusBar} from \"../utils/statusBar.mjs\";\nimport {htmlPlugin} from \"../utils/htmlWidget.mjs\";\nimport {copyOverride} from \"../utils/copyOverride.mjs\";\nimport {eolCodeToSeq, eolCodeToName, renderSpecialChar} from \"../utils/editorUtils.mjs\";\n\n\n/**\n  * Waiter to handle events related to the output\n  */\nclass OutputWaiter {\n\n    /**\n     * OutputWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.outputTextEl = document.getElementById(\"output-text\");\n        // Object to handle output HTML state - used by htmlWidget extension\n        this.htmlOutput = {\n            html: \"\",\n            changed: false\n        };\n        // Hold a copy of the currently displayed output so that we don't have to update it unnecessarily\n        this.currentOutputCache = null;\n        this.initEditor();\n\n        this.outputs = {};\n        this.zipWorker = null;\n        this.maxTabs = this.manager.tabs.calcMaxTabs();\n        this.tabTimeout = null;\n        this.eolState = 0; // 0 = unset, 1 = detected, 2 = manual\n        this.encodingState = 0; // 0 = unset, 1 = detected, 2 = manual\n    }\n\n    /**\n     * Sets up the CodeMirror Editor\n     */\n    initEditor() {\n        // Mutable extensions\n        this.outputEditorConf = {\n            eol: new Compartment,\n            lineWrapping: new Compartment,\n            drawSelection: new Compartment\n        };\n\n        const initialState = EditorState.create({\n            doc: null,\n            extensions: [\n                // Editor extensions\n                EditorState.readOnly.of(true),\n                highlightSpecialChars({\n                    render: renderSpecialChar, // Custom character renderer to handle special cases\n                    addSpecialChars: /[\\ue000-\\uf8ff]/g // Add the Unicode Private Use Area which we use for some whitespace chars\n                }),\n                rectangularSelection(),\n                crosshairCursor(),\n                bracketMatching(),\n                highlightSelectionMatches(),\n                search({top: true}),\n                EditorState.allowMultipleSelections.of(true),\n\n                // Custom extensions\n                statusBar({\n                    label: \"Output\",\n                    timing: this.manager.timing,\n                    tabNumGetter: function() {\n                        return this.manager.tabs.getActiveTab(\"output\");\n                    }.bind(this),\n                    eolHandler: this.eolChange.bind(this),\n                    chrEncHandler: this.chrEncChange.bind(this),\n                    chrEncGetter: this.getChrEnc.bind(this),\n                    getEncodingState: this.getEncodingState.bind(this),\n                    getEOLState: this.getEOLState.bind(this),\n                    htmlOutput: this.htmlOutput\n                }),\n                htmlPlugin(this.htmlOutput),\n                copyOverride(),\n\n                // Mutable state\n                this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping),\n                this.outputEditorConf.eol.of(EditorState.lineSeparator.of(\"\\n\")),\n                this.outputEditorConf.drawSelection.of(drawSelection()),\n\n                // Keymap\n                keymap.of([\n                    ...defaultKeymap,\n                    ...searchKeymap\n                ]),\n\n                // Event listeners\n                EditorView.updateListener.of(e => {\n                    if (e.selectionSet)\n                        this.manager.highlighter.selectionChange(\"output\", e);\n                    if (e.docChanged || this.docChanging) {\n                        this.docChanging = false;\n                        this.toggleLoader(false);\n                    }\n                })\n            ]\n        });\n\n        if (this.outputEditorView) this.outputEditorView.destroy();\n        this.outputEditorView = new EditorView({\n            state: initialState,\n            parent: this.outputTextEl\n        });\n    }\n\n    /**\n     * Handler for EOL change events\n     * Sets the line separator\n     * @param {string} eol\n     * @param {boolean} [manual=false]\n     */\n    async eolChange(eol, manual=false) {\n        const eolVal = eolCodeToSeq[eol];\n        if (eolVal === undefined) return;\n\n        this.eolState = manual ? 2 : this.eolState;\n        if (this.eolState < 2 && eolVal === this.getEOLSeq()) return;\n\n        if (this.eolState === 1) {\n            // Alert\n            this.app.alert(`Output end of line separator has been detected and changed to ${eolCodeToName[eol]}`, 5000);\n        }\n\n        const currentTabNum = this.manager.tabs.getActiveTab(\"output\");\n        if (currentTabNum >= 0) {\n            this.outputs[currentTabNum].eolSequence = eolVal;\n        } else {\n            throw new Error(`Cannot change output ${currentTabNum} EOL sequence to ${eolVal}`);\n        }\n\n        // Update the EOL value\n        this.outputEditorView.dispatch({\n            effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal))\n        });\n\n        // Reset the output so that lines are recalculated, preserving the old EOL values\n        await this.setOutput(this.currentOutputCache, true);\n        // Update the URL manually since we aren't firing a statechange event\n        this.app.updateURL(true);\n    }\n\n    /**\n     * Getter for the output EOL sequence\n     * Prefer reading value from `this.outputs` since the editor may not have updated yet.\n     * @returns {string}\n     */\n    getEOLSeq() {\n        const currentTabNum = this.manager.tabs.getActiveTab(\"output\");\n        if (currentTabNum < 0) {\n            return this.outputEditorConf.state?.lineBreak || \"\\n\";\n        }\n        return this.outputs[currentTabNum].eolSequence;\n    }\n\n    /**\n     * Returns whether the output EOL sequence was set manually or has been detected automatically\n     * @returns {number} - 0 = unset, 1 = detected, 2 = manual\n     */\n    getEOLState() {\n        return this.eolState;\n    }\n\n    /**\n     * Handler for Chr Enc change events\n     * Sets the output character encoding\n     * @param {number} chrEncVal\n     * @param {boolean} [manual=false]\n     */\n    async chrEncChange(chrEncVal, manual=false) {\n        if (typeof chrEncVal !== \"number\") return;\n        const currentEnc = this.getChrEnc();\n\n        const currentTabNum = this.manager.tabs.getActiveTab(\"output\");\n        if (currentTabNum >= 0) {\n            this.outputs[currentTabNum].encoding = chrEncVal;\n        } else {\n            throw new Error(`Cannot change output ${currentTabNum} chrEnc to ${chrEncVal}`);\n        }\n\n        this.encodingState = manual ? 2 : this.encodingState;\n\n        if (this.encodingState > 1) {\n            // Reset the output, forcing it to re-decode the data with the new character encoding\n            await this.setOutput(this.currentOutputCache, true);\n            // Update the URL manually since we aren't firing a statechange event\n            this.app.updateURL(true);\n        } else if (currentEnc !== chrEncVal) {\n            // Alert\n            this.app.alert(`Output character encoding has been detected and changed to ${CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] || \"Raw Bytes\"}`, 5000);\n        }\n    }\n\n    /**\n     * Getter for the output character encoding\n     * @returns {number}\n     */\n    getChrEnc() {\n        const currentTabNum = this.manager.tabs.getActiveTab(\"output\");\n        if (currentTabNum < 0) {\n            return 0;\n        }\n        return this.outputs[currentTabNum].encoding;\n    }\n\n    /**\n     * Returns whether the output character encoding was set manually or has been detected automatically\n     * @returns {number} - 0 = unset, 1 = detected, 2 = manual\n     */\n    getEncodingState() {\n        return this.encodingState;\n    }\n\n    /**\n     * Sets word wrap on the output editor\n     * @param {boolean} wrap\n     */\n    setWordWrap(wrap) {\n        this.outputEditorView.dispatch({\n            effects: this.outputEditorConf.lineWrapping.reconfigure(\n                wrap ? EditorView.lineWrapping : []\n            )\n        });\n    }\n\n    /**\n     * Sets the value of the current output\n     * @param {string|ArrayBuffer} data\n     * @param {boolean} [force=false]\n     */\n    async setOutput(data, force=false) {\n        // Don't do anything if the output hasn't changed\n        if (!force && data === this.currentOutputCache) {\n            this.manager.controls.hideStaleIndicator();\n            this.toggleLoader(false);\n            return;\n        }\n\n        this.currentOutputCache = data;\n        this.toggleLoader(true);\n\n        // Remove class to #output-text to change display settings\n        this.outputTextEl.classList.remove(\"html-output\");\n\n        // If data is an ArrayBuffer, convert to a string in the correct character encoding\n        const tabNum = this.manager.tabs.getActiveTab(\"output\");\n        this.manager.timing.recordTime(\"outputDecodingStart\", tabNum);\n        if (data instanceof ArrayBuffer) {\n            await this.detectEncoding(data);\n            data = await this.bufferToStr(data);\n        }\n        this.manager.timing.recordTime(\"outputDecodingEnd\", tabNum);\n\n        // Turn drawSelection back on\n        this.outputEditorView.dispatch({\n            effects: this.outputEditorConf.drawSelection.reconfigure(\n                drawSelection()\n            )\n        });\n\n        // Ensure we're not exceeding the maximum line length\n        let wrap = this.app.options.wordWrap;\n        const lineLengthThreshold = 131072; // 128KB\n        if (data.length > lineLengthThreshold) {\n            const lines = data.split(this.getEOLSeq());\n            const longest = lines.reduce((a, b) =>\n                a > b.length ? a : b.length, 0\n            );\n            if (longest > lineLengthThreshold) {\n                // If we are exceeding the max line length, turn off word wrap\n                wrap = false;\n            }\n        }\n\n        // If turning word wrap off, do it before we populate the editor for performance reasons\n        if (!wrap) this.setWordWrap(wrap);\n\n        // Detect suitable EOL sequence\n        this.detectEOLSequence(data);\n\n        // We use setTimeout here to delay the editor dispatch until the next event cycle,\n        // ensuring all async actions have completed before attempting to set the contents\n        // of the editor. This is mainly with the above call to setWordWrap() in mind.\n        setTimeout(() => {\n            this.docChanging = true;\n            // Insert data into editor, overwriting any previous contents\n            this.outputEditorView.dispatch({\n                changes: {\n                    from: 0,\n                    to: this.outputEditorView.state.doc.length,\n                    insert: data\n                }\n            });\n\n            // If turning word wrap on, do it after we populate the editor\n            if (wrap)\n                setTimeout(() => {\n                    this.setWordWrap(wrap);\n                });\n        });\n    }\n\n    /**\n     * Sets the value of the current output to a rendered HTML value\n     * @param {string} html\n     */\n    async setHTMLOutput(html) {\n        this.htmlOutput.html = html;\n        this.htmlOutput.changed = true;\n        // This clears the text output, but also fires a View update which\n        // triggers the htmlWidget to render the HTML. We set the force flag\n        // to ensure the loader gets removed and HTML is rendered.\n        await this.setOutput(\"\", true);\n\n        // Turn off drawSelection\n        this.outputEditorView.dispatch({\n            effects: this.outputEditorConf.drawSelection.reconfigure([])\n        });\n\n        // Add class to #output-text to change display settings\n        this.outputTextEl.classList.add(\"html-output\");\n\n        // Execute script sections\n        const outputHTML = document.getElementById(\"output-html\");\n        const scriptElements = outputHTML ? outputHTML.querySelectorAll(\"script\") : [];\n        for (let i = 0; i < scriptElements.length; i++) {\n            try {\n                eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval\n            } catch (err) {\n                log.error(err);\n            }\n        }\n    }\n\n    /**\n     * Clears the HTML output\n     */\n    clearHTMLOutput() {\n        this.htmlOutput.html = \"\";\n        this.htmlOutput.changed = true;\n        // Fire a blank change to force the htmlWidget to update and remove any HTML\n        this.outputEditorView.dispatch({\n            changes: {\n                from: 0,\n                insert: \"\"\n            }\n        });\n    }\n\n    /**\n     * Checks whether the EOL separator should be updated\n     *\n     * @param {string} data\n     */\n    detectEOLSequence(data) {\n        // If EOL has been fixed, skip this.\n        if (this.eolState > 1) return;\n        // If data is too long, skip this.\n        if (data.length > 1000000) return;\n\n        // Detect most likely EOL sequence\n        const eolCharCounts = {\n            \"LF\": data.count(\"\\u000a\"),\n            \"VT\": data.count(\"\\u000b\"),\n            \"FF\": data.count(\"\\u000c\"),\n            \"CR\": data.count(\"\\u000d\"),\n            \"CRLF\": data.count(\"\\u000d\\u000a\"),\n            \"NEL\": data.count(\"\\u0085\"),\n            \"LS\": data.count(\"\\u2028\"),\n            \"PS\": data.count(\"\\u2029\")\n        };\n\n        // If all zero, leave alone\n        const total = Object.values(eolCharCounts).reduce((acc, curr) => {\n            return acc + curr;\n        }, 0);\n        if (total === 0) return;\n\n        // Find most prevalent line ending sequence\n        const highest = Object.entries(eolCharCounts).reduce((acc, curr) => {\n            return curr[1] > acc[1] ? curr : acc;\n        }, [\"LF\", 0]);\n        let choice = highest[0];\n\n        // If CRLF not zero and more than half the highest alternative, choose CRLF\n        if ((eolCharCounts.CRLF * 2) > highest[1]) {\n            choice = \"CRLF\";\n        }\n\n        const eolVal = eolCodeToSeq[choice];\n        if (eolVal === this.getEOLSeq()) return;\n\n        // Setting automatically\n        this.eolState = 1;\n        this.eolChange(choice);\n    }\n\n    /**\n     * Checks whether the character encoding should be updated.\n     *\n     * @param {ArrayBuffer} data\n     */\n    async detectEncoding(data) {\n        // If encoding has been fixed, skip this.\n        if (this.encodingState > 1) return;\n        // If data is too long, skip this.\n        if (data.byteLength > 1000000) return;\n\n        const enc = isUTF8(data); // 0 = not UTF8, 1 = ASCII, 2 = UTF8\n\n        switch (enc) {\n            case 0: // not UTF8\n                // Set to Raw Bytes\n                this.encodingState = 1;\n                await this.chrEncChange(0, false);\n                break;\n            case 2: // UTF8\n                // Set to UTF8\n                this.encodingState = 1;\n                await this.chrEncChange(65001, false);\n                break;\n            case 1: // ASCII\n            default:\n                // Ignore\n                break;\n        }\n    }\n\n    /**\n     * Calculates the maximum number of tabs to display\n     */\n    calcMaxTabs() {\n        const numTabs = this.manager.tabs.calcMaxTabs();\n        if (numTabs !== this.maxTabs) {\n            this.maxTabs = numTabs;\n            this.refreshTabs(this.manager.tabs.getActiveTab(\"output\"), \"right\");\n        }\n    }\n\n    /**\n     * Gets the dish object for an output.\n     *\n     * @param inputNum - The inputNum of the output to get the dish of\n     * @returns {Dish}\n     */\n    getOutputDish(inputNum) {\n        if (this.outputExists(inputNum) &&\n            this.outputs[inputNum].data &&\n            this.outputs[inputNum].data.dish) {\n            return this.outputs[inputNum].data.dish;\n        }\n        return null;\n    }\n\n    /**\n     * Checks if an output exists in the output dictionary\n     *\n     * @param {number} inputNum - The number of the output we're looking for\n     * @returns {boolean}\n     */\n    outputExists(inputNum) {\n        if (this.outputs[inputNum] === undefined ||\n            this.outputs[inputNum] === null) {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * Adds a new output to the output array.\n     * Creates a new tab if we have less than maxtabs tabs open\n     *\n     * @param {number} inputNum - The inputNum of the new output\n     * @param {boolean} [changeTab=true] - If true, change to the new output\n     */\n    addOutput(inputNum, changeTab = true) {\n        // Remove the output (will only get removed if it already exists)\n        this.removeOutput(inputNum);\n\n        const newOutput = {\n            data: null,\n            inputNum: inputNum,\n            statusMessage: `Input ${inputNum} has not been baked yet.`,\n            error: null,\n            status: \"inactive\",\n            bakeId: -1,\n            progress: false,\n            encoding: 0,\n            eolSequence: \"\\u000a\"\n        };\n\n        this.outputs[inputNum] = newOutput;\n\n        this.addTab(inputNum, changeTab);\n    }\n\n    /**\n     * Updates the value for the output in the output array.\n     * If this is the active output tab, updates the output textarea\n     *\n     * @param {ArrayBuffer | String} data\n     * @param {number} inputNum\n     * @param {boolean} set\n     */\n    updateOutputValue(data, inputNum, set=true) {\n        if (!this.outputExists(inputNum)) {\n            this.addOutput(inputNum);\n        }\n\n        if (Object.prototype.hasOwnProperty.call(data, \"dish\")) {\n            data.dish = new Dish(data.dish);\n        }\n\n        this.outputs[inputNum].data = data;\n\n        const tabItem = this.manager.tabs.getTabItem(inputNum, \"output\");\n        if (tabItem) tabItem.style.background = \"\";\n\n        if (set) this.set(inputNum);\n    }\n\n    /**\n     * Updates the status message for the output in the output array.\n     * If this is the active output tab, updates the output textarea\n     *\n     * @param {string} statusMessage\n     * @param {number} inputNum\n     * @param {boolean} [set=true]\n     */\n    updateOutputMessage(statusMessage, inputNum, set=true) {\n        if (!this.outputExists(inputNum)) return;\n        this.outputs[inputNum].statusMessage = statusMessage;\n        if (set) this.set(inputNum);\n    }\n\n    /**\n     * Updates the error value for the output in the output array.\n     * If this is the active output tab, calls app.handleError.\n     * Otherwise, the error will be handled when the output is switched to\n     *\n     * @param {Error} error\n     * @param {number} inputNum\n     * @param {number} [progress=0]\n     */\n    updateOutputError(error, inputNum, progress=0) {\n        if (!this.outputExists(inputNum)) return;\n\n        const errorString = error.displayStr || error.toString();\n\n        this.outputs[inputNum].error = errorString;\n        this.outputs[inputNum].progress = progress;\n        this.updateOutputStatus(\"error\", inputNum);\n    }\n\n    /**\n     * Updates the status value for the output in the output array\n     *\n     * @param {string} status\n     * @param {number} inputNum\n     */\n    updateOutputStatus(status, inputNum) {\n        if (!this.outputExists(inputNum)) return;\n        this.outputs[inputNum].status = status;\n\n        if (status !== \"error\") {\n            delete this.outputs[inputNum].error;\n        }\n\n        this.displayTabInfo(inputNum);\n        this.set(inputNum);\n    }\n\n    /**\n     * Updates the stored bake ID for the output in the output array\n     *\n     * @param {number} bakeId\n     * @param {number} inputNum\n     */\n    updateOutputBakeId(bakeId, inputNum) {\n        if (!this.outputExists(inputNum)) return;\n        this.outputs[inputNum].bakeId = bakeId;\n    }\n\n    /**\n     * Updates the stored progress value for the output in the output array\n     *\n     * @param {number} progress\n     * @param {number} total\n     * @param {number} inputNum\n     */\n    updateOutputProgress(progress, total, inputNum) {\n        if (!this.outputExists(inputNum)) return;\n        this.outputs[inputNum].progress = progress;\n\n        if (progress !== false) {\n            this.manager.tabs.updateTabProgress(inputNum, progress, total, \"output\");\n        }\n\n    }\n\n    /**\n     * Removes an output from the output array.\n     *\n     * @param {number} inputNum\n     */\n    removeOutput(inputNum) {\n        if (!this.outputExists(inputNum)) return;\n\n        delete this.outputs[inputNum];\n    }\n\n    /**\n     * Removes all output tabs\n     */\n    removeAllOutputs() {\n        this.outputs = {};\n\n        const tabsList = document.getElementById(\"output-tabs\");\n        const tabsListChildren = tabsList.children;\n\n        tabsList.classList.remove(\"tabs-left\");\n        tabsList.classList.remove(\"tabs-right\");\n        for (let i = tabsListChildren.length - 1; i >= 0; i--) {\n            tabsListChildren.item(i).remove();\n        }\n    }\n\n    /**\n     * Sets the output in the output pane.\n     *\n     * @param {number} inputNum\n     */\n    async set(inputNum) {\n        inputNum = parseInt(inputNum, 10);\n        if (inputNum !== this.manager.tabs.getActiveTab(\"output\") ||\n            !this.outputExists(inputNum)) return;\n        this.toggleLoader(true);\n\n        return new Promise(async function(resolve, reject) {\n            const output = this.outputs[inputNum];\n            this.manager.timing.recordTime(\"settingOutput\", inputNum);\n\n            // Update the EOL value\n            this.outputEditorView.dispatch({\n                effects: this.outputEditorConf.eol.reconfigure(\n                    EditorState.lineSeparator.of(output.eolSequence)\n                )\n            });\n\n            // If pending or baking, show loader and status message\n            // If error, style the tab and handle the error\n            // If done, display the output if it's the active tab\n            // If inactive, show the last bake value (or blank)\n            if (output.status === \"inactive\" ||\n                output.status === \"stale\" ||\n                (output.status === \"baked\" && output.bakeId < this.manager.worker.bakeId)) {\n                this.manager.controls.showStaleIndicator();\n            } else {\n                this.manager.controls.hideStaleIndicator();\n            }\n\n            if (output.progress !== undefined && !this.app.baking) {\n                this.manager.recipe.updateBreakpointIndicator(output.progress);\n            } else {\n                this.manager.recipe.updateBreakpointIndicator(false);\n            }\n\n            if (output.status === \"pending\" || output.status === \"baking\") {\n                // show the loader and the status message if it's being shown\n                // otherwise don't do anything\n                document.querySelector(\"#output-loader .loading-msg\").textContent = output.statusMessage;\n            } else if (output.status === \"error\") {\n                this.clearHTMLOutput();\n\n                if (output.error) {\n                    await this.setOutput(output.error);\n                } else {\n                    await this.setOutput(output.data.result);\n                }\n            } else if (output.status === \"baked\" || output.status === \"inactive\") {\n                document.querySelector(\"#output-loader .loading-msg\").textContent = `Loading output ${inputNum}`;\n\n                if (output.data === null) {\n                    this.clearHTMLOutput();\n                    await this.setOutput(\"\");\n                    return;\n                }\n\n                switch (output.data.type) {\n                    case \"html\":\n                        await this.setHTMLOutput(output.data.result);\n                        break;\n                    case \"ArrayBuffer\":\n                    case \"string\":\n                    default:\n                        this.clearHTMLOutput();\n                        await this.setOutput(output.data.result);\n                        break;\n                }\n                this.manager.timing.recordTime(\"complete\", inputNum);\n\n                // Trigger an update so that the status bar recalculates timings\n                this.outputEditorView.dispatch({\n                    changes: {\n                        from: 0,\n                        to: 0\n                    }\n                });\n\n                debounce(this.backgroundMagic, 50, \"backgroundMagic\", this, [])();\n            }\n        }.bind(this));\n    }\n\n    /**\n     * Retrieves the dish as a string\n     *\n     * @param {Dish} dish\n     * @returns {string}\n     */\n    async getDishStr(dish) {\n        return await new Promise(resolve => {\n            this.manager.worker.getDishAs(dish, \"string\", r => {\n                resolve(r.value);\n            });\n        });\n    }\n\n    /**\n     * Retrieves the dish as an ArrayBuffer\n     *\n     * @param {Dish} dish\n     * @returns {ArrayBuffer}\n     */\n    async getDishBuffer(dish) {\n        return await new Promise(resolve => {\n            this.manager.worker.getDishAs(dish, \"ArrayBuffer\", r => {\n                resolve(r.value);\n            });\n        });\n    }\n\n    /**\n     * Retrieves the title of the Dish as a string\n     *\n     * @param {Dish} dish\n     * @param {number} maxLength\n     * @returns {string}\n     */\n    async getDishTitle(dish, maxLength) {\n        return await new Promise(resolve => {\n            this.manager.worker.getDishTitle(dish, maxLength, r => {\n                resolve(r.value);\n            });\n        });\n    }\n\n    /**\n     * Asks a worker to translate an ArrayBuffer into a certain character encoding\n     *\n     * @param {ArrrayBuffer} buffer\n     * @returns {string}\n     */\n    async bufferToStr(buffer) {\n        const encoding = this.getChrEnc();\n\n        if (buffer.byteLength === 0) return \"\";\n        return await new Promise(resolve => {\n            this.manager.worker.bufferToStr(buffer, encoding, r => {\n                resolve(r.value);\n            });\n        });\n    }\n\n    /**\n     * Save bombe object then remove it from the DOM so that it does not cause performance issues.\n     */\n    saveBombe() {\n        this.bombeEl = document.getElementById(\"bombe\");\n        this.bombeEl.parentNode.removeChild(this.bombeEl);\n    }\n\n    /**\n     * Shows or hides the output loading screen.\n     * The animated Bombe SVG, whilst quite aesthetically pleasing, is reasonably CPU\n     * intensive, so we remove it from the DOM when not in use. We only show it if the\n     * recipe is taking longer than 200ms. We add it to the DOM just before that so that\n     * it is ready to fade in without stuttering.\n     *\n     * @param {boolean} value - If true, show the loader\n     */\n    toggleLoader(value) {\n        const outputLoader = document.getElementById(\"output-loader\"),\n            animation = document.getElementById(\"output-loader-animation\");\n\n        if (value) {\n            this.manager.controls.hideStaleIndicator();\n            // Don't add the bombe if it's already there or scheduled to be loaded\n            if (animation.children.length === 0 && !this.appendBombeTimeout) {\n                // Start a timer to add the Bombe to the DOM just before we make it\n                // visible so that there is no stuttering\n                this.appendBombeTimeout = setTimeout(function() {\n                    this.appendBombeTimeout = null;\n                    animation.appendChild(this.bombeEl);\n                }.bind(this), 150);\n            }\n\n            if (outputLoader.style.visibility !== \"visible\" && !this.outputLoaderTimeout) {\n                // Show the loading screen\n                this.outputLoaderTimeout = setTimeout(function() {\n                    this.outputLoaderTimeout = null;\n                    outputLoader.style.visibility = \"visible\";\n                    outputLoader.style.opacity = 1;\n                }, 200);\n            }\n        } else if (outputLoader.style.visibility !== \"hidden\" || this.appendBombeTimeout || this.outputLoaderTimeout) {\n            clearTimeout(this.appendBombeTimeout);\n            clearTimeout(this.outputLoaderTimeout);\n            this.appendBombeTimeout = null;\n            this.outputLoaderTimeout = null;\n\n            // Remove the Bombe from the DOM to save resources\n            this.outputLoaderTimeout = setTimeout(function () {\n                this.outputLoaderTimeout = null;\n                if (animation.children.length > 0)\n                    animation.removeChild(this.bombeEl);\n            }.bind(this), 500);\n            outputLoader.style.opacity = 0;\n            outputLoader.style.visibility = \"hidden\";\n        }\n    }\n\n    /**\n     * Handler for save click events.\n     * Saves the current output to a file.\n     */\n    saveClick() {\n        this.downloadFile();\n    }\n\n    /**\n     * Handler for file download events.\n     */\n    async downloadFile() {\n        const dish = this.getOutputDish(this.manager.tabs.getActiveTab(\"output\"));\n        if (dish === null) {\n            this.app.alert(\"Could not find any output data to download. Has this output been baked?\", 3000);\n            return;\n        }\n\n        const data = await dish.get(Dish.ARRAY_BUFFER);\n        let ext = \".dat\";\n\n        // Detect file type automatically\n        const types = detectFileType(data);\n        if (types.length) {\n            ext = `.${types[0].extension.split(\",\", 1)[0]}`;\n        }\n\n        const fileName = window.prompt(\"Please enter a filename: \", `download${ext}`);\n\n        // Assume if the user clicks cancel they don't want to download\n        if (fileName === null) return;\n\n        const file = new File([data], fileName);\n        FileSaver.saveAs(file, fileName, {autoBom: false});\n    }\n\n    /**\n     * Handler for save all click event\n     * Saves all outputs to a single archive file\n     */\n    async saveAllClick() {\n        const downloadButton = document.getElementById(\"save-all-to-file\");\n        if (downloadButton.firstElementChild.innerHTML === \"archive\") {\n            this.downloadAllFiles();\n        } else {\n            const cancel = await new Promise(function(resolve, reject) {\n                this.app.confirm(\n                    \"Cancel zipping?\",\n                    \"The outputs are currently being zipped for download.<br>Cancel zipping?\",\n                    \"Continue zipping\",\n                    \"Cancel zipping\",\n                    resolve, this);\n            }.bind(this));\n            if (!cancel) {\n                this.terminateZipWorker();\n            }\n        }\n    }\n\n    /**\n     * Spawns a new ZipWorker and sends it the outputs so that they can\n     * be zipped for download\n     */\n    async downloadAllFiles() {\n        const inputNums = Object.keys(this.outputs);\n        for (let i = 0; i < inputNums.length; i++) {\n            const iNum = inputNums[i];\n            if (this.outputs[iNum].status !== \"baked\" ||\n            this.outputs[iNum].bakeId !== this.manager.worker.bakeId) {\n                const continueDownloading = await new Promise(function(resolve, reject) {\n                    this.app.confirm(\n                        \"Incomplete outputs\",\n                        \"Not all outputs have been baked yet. Continue downloading outputs?\",\n                        \"Download\", \"Cancel\", resolve, this);\n                }.bind(this));\n                if (continueDownloading) {\n                    break;\n                } else {\n                    return;\n                }\n            }\n        }\n\n        let fileName = window.prompt(\"Please enter a filename: \", \"download.zip\");\n\n        if (fileName === null || fileName === \"\") {\n            // Don't zip the files if there isn't a filename\n            this.app.alert(\"No filename was specified.\", 3000);\n            return;\n        }\n\n        if (!fileName.match(/.zip$/)) {\n            fileName += \".zip\";\n        }\n\n        let fileExt = window.prompt(\"Please enter a file extension for the files, or leave blank to detect automatically.\", \"\");\n\n        if (fileExt === null) fileExt = \"\";\n\n        if (this.zipWorker !== null) {\n            this.terminateZipWorker();\n        }\n\n        const downloadButton = document.getElementById(\"save-all-to-file\");\n\n        downloadButton.classList.add(\"spin\");\n        downloadButton.title = `Zipping ${inputNums.length} files...`;\n        downloadButton.setAttribute(\"data-original-title\", `Zipping ${inputNums.length} files...`);\n\n        downloadButton.firstElementChild.innerHTML = \"autorenew\";\n\n        log.debug(\"Creating ZipWorker\");\n        this.zipWorker = new ZipWorker();\n        this.zipWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n        this.zipWorker.postMessage({\n            action: \"zipFiles\",\n            data: {\n                outputs: this.outputs,\n                filename: fileName,\n                fileExtension: fileExt\n            }\n        });\n        this.zipWorker.addEventListener(\"message\", this.handleZipWorkerMessage.bind(this));\n    }\n\n    /**\n     * Terminate the ZipWorker\n     */\n    terminateZipWorker() {\n        if (this.zipWorker === null) return; // Already terminated\n\n        log.debug(\"Terminating ZipWorker.\");\n\n        this.zipWorker.terminate();\n        this.zipWorker = null;\n\n        const downloadButton = document.getElementById(\"save-all-to-file\");\n        downloadButton.classList.remove(\"spin\");\n        downloadButton.title = \"Save all outputs to a zip file\";\n        downloadButton.setAttribute(\"data-original-title\", \"Save all outputs to a zip file\");\n        downloadButton.firstElementChild.innerHTML = \"archive\";\n    }\n\n    /**\n     * Handle messages sent back by the ZipWorker\n     */\n    handleZipWorkerMessage(e) {\n        const r = e.data;\n        if (!(\"zippedFile\" in r)) {\n            log.error(\"No zipped file was sent in the message.\");\n            this.terminateZipWorker();\n            return;\n        }\n        if (!(\"filename\" in r)) {\n            log.error(\"No filename was sent in the message.\");\n            this.terminateZipWorker();\n            return;\n        }\n\n        const file = new File([r.zippedFile], r.filename);\n        FileSaver.saveAs(file, r.filename, {autoBom: false});\n\n        this.terminateZipWorker();\n    }\n\n    /**\n     * Adds a new output tab.\n     *\n     * @param {number} inputNum\n     * @param {boolean} [changeTab=true]\n     */\n    addTab(inputNum, changeTab = true) {\n        const tabsWrapper = document.getElementById(\"output-tabs\");\n        const numTabs = tabsWrapper.children.length;\n\n        if (!this.manager.tabs.getTabItem(inputNum, \"output\") && numTabs < this.maxTabs) {\n            // Create a new tab element\n            const newTab = this.manager.tabs.createTabElement(inputNum, changeTab, \"output\");\n            tabsWrapper.appendChild(newTab);\n        } else if (numTabs === this.maxTabs) {\n            // Can't create a new tab\n            document.getElementById(\"output-tabs\").lastElementChild.classList.add(\"tabs-right\");\n        }\n\n        this.displayTabInfo(inputNum);\n\n        if (changeTab) {\n            this.changeTab(inputNum, false);\n        }\n    }\n\n    /**\n     * Changes the active tab\n     *\n     * @param {number} inputNum\n     * @param {boolean} [changeInput = false]\n     */\n    changeTab(inputNum, changeInput = false) {\n        if (!this.outputExists(inputNum)) return;\n        const currentNum = this.manager.tabs.getActiveTab(\"output\");\n\n        this.hideMagicButton();\n\n        if (!this.manager.tabs.changeTab(inputNum, \"output\")) {\n            let direction = \"right\";\n            if (currentNum > inputNum) {\n                direction = \"left\";\n            }\n            const newOutputs = this.getNearbyNums(inputNum, direction);\n\n            const tabsLeft = (newOutputs[0] !== this.getSmallestInputNum());\n            const tabsRight = (newOutputs[newOutputs.length - 1] !== this.getLargestInputNum());\n\n            this.manager.tabs.refreshTabs(newOutputs, inputNum, tabsLeft, tabsRight, \"output\");\n\n            for (let i = 0; i < newOutputs.length; i++) {\n                this.displayTabInfo(newOutputs[i]);\n            }\n        }\n\n        this.set(inputNum);\n\n        if (changeInput) {\n            this.manager.input.changeTab(inputNum, false);\n        }\n    }\n\n    /**\n     * Handler for changing tabs event\n     *\n     * @param {event} mouseEvent\n     */\n    changeTabClick(mouseEvent) {\n        if (!mouseEvent.target) return;\n\n        const tabNum = mouseEvent.target.parentElement.getAttribute(\"inputNum\");\n        if (tabNum) {\n            this.changeTab(parseInt(tabNum, 10), this.app.options.syncTabs);\n        }\n    }\n\n    /**\n     * Handler for scrolling on the output tabs area\n     *\n     * @param {event} wheelEvent\n     */\n    scrollTab(wheelEvent) {\n        wheelEvent.preventDefault();\n\n        if (wheelEvent.deltaY > 0) {\n            this.changeTabLeft();\n        } else if (wheelEvent.deltaY < 0) {\n            this.changeTabRight();\n        }\n    }\n\n    /**\n     * Handler for mouse down on the next tab button\n     */\n    nextTabClick() {\n        this.mousedown = true;\n        this.changeTabRight();\n        const time = 200;\n        const func = function(time) {\n            if (this.mousedown) {\n                this.changeTabRight();\n                const newTime = (time > 50) ? time - 10 : 50;\n                setTimeout(func.bind(this, [newTime]), newTime);\n            }\n        };\n        this.tabTimeout = setTimeout(func.bind(this, [time]), time);\n    }\n\n    /**\n     * Handler for mouse down on the previous tab button\n     */\n    previousTabClick() {\n        this.mousedown = true;\n        this.changeTabLeft();\n        const time = 200;\n        const func = function(time) {\n            if (this.mousedown) {\n                this.changeTabLeft();\n                const newTime = (time > 50) ? time - 10 : 50;\n                setTimeout(func.bind(this, [newTime]), newTime);\n            }\n        };\n        this.tabTimeout = setTimeout(func.bind(this, [time]), time);\n    }\n\n    /**\n     * Handler for mouse up event on the tab buttons\n     */\n    tabMouseUp() {\n        this.mousedown = false;\n\n        clearTimeout(this.tabTimeout);\n        this.tabTimeout = null;\n    }\n\n    /**\n     * Handler for changing to the left tab\n     */\n    changeTabLeft() {\n        const currentTab = this.manager.tabs.getActiveTab(\"output\");\n        this.changeTab(this.getPreviousInputNum(currentTab), this.app.options.syncTabs);\n    }\n\n    /**\n     * Handler for changing to the right tab\n     */\n    changeTabRight() {\n        const currentTab = this.manager.tabs.getActiveTab(\"output\");\n        this.changeTab(this.getNextInputNum(currentTab), this.app.options.syncTabs);\n    }\n\n    /**\n     * Handler for go to tab button clicked\n     */\n    goToTab() {\n        const min = this.getSmallestInputNum(),\n            max = this.getLargestInputNum();\n\n        let tabNum = window.prompt(`Enter tab number (${min} - ${max}):`, this.manager.tabs.getActiveTab(\"output\").toString());\n        if (tabNum === null) return;\n        tabNum = parseInt(tabNum, 10);\n\n        if (this.outputExists(tabNum)) {\n            this.changeTab(tabNum, this.app.options.syncTabs);\n        }\n    }\n\n    /**\n     * Generates a list of the nearby inputNums\n     * @param inputNum\n     * @param direction\n     */\n    getNearbyNums(inputNum, direction) {\n        const nums = [];\n        for (let i = 0; i < this.maxTabs; i++) {\n            let newNum;\n            if (i === 0 && this.outputs[inputNum] !== undefined) {\n                newNum = inputNum;\n            } else {\n                switch (direction) {\n                    case \"left\":\n                        newNum = this.getNextInputNum(nums[i - 1]);\n                        if (newNum === nums[i - 1]) {\n                            direction = \"right\";\n                            newNum = this.getPreviousInputNum(nums[0]);\n                        }\n                        break;\n                    case \"right\":\n                        newNum = this.getPreviousInputNum(nums[i - 1]);\n                        if (newNum === nums[i - 1]) {\n                            direction = \"left\";\n                            newNum = this.getNextInputNum(nums[0]);\n                        }\n                }\n            }\n            if (!nums.includes(newNum) && (newNum > 0)) {\n                nums.push(newNum);\n            }\n        }\n        nums.sort((a, b) => a - b); // Forces the sort function to treat a and b as numbers\n        return nums;\n    }\n\n    /**\n     * Gets the largest inputNum\n     *\n     * @returns {number}\n     */\n    getLargestInputNum() {\n        const inputNums = Object.keys(this.outputs);\n        if (inputNums.length === 0) return -1;\n        return Math.max(...inputNums);\n    }\n\n    /**\n     * Gets the smallest inputNum\n     *\n     * @returns {number}\n     */\n    getSmallestInputNum() {\n        const inputNums = Object.keys(this.outputs);\n        if (inputNums.length === 0) return -1;\n        return Math.min(...inputNums);\n    }\n\n    /**\n     * Gets the previous inputNum\n     *\n     * @param {number} inputNum - The current input number\n     * @returns {number}\n     */\n    getPreviousInputNum(inputNum) {\n        const inputNums = Object.keys(this.outputs);\n        if (inputNums.length === 0) return -1;\n        let num = Math.min(...inputNums);\n        for (let i = 0; i < inputNums.length; i++) {\n            const iNum = parseInt(inputNums[i], 10);\n            if (iNum < inputNum) {\n                if (iNum > num) {\n                    num = iNum;\n                }\n            }\n        }\n        return num;\n    }\n\n    /**\n     * Gets the next inputNum\n     *\n     * @param {number} inputNum - The current input number\n     * @returns {number}\n     */\n    getNextInputNum(inputNum) {\n        const inputNums = Object.keys(this.outputs);\n        if (inputNums.length === 0) return -1;\n        let num = Math.max(...inputNums);\n        for (let i = 0; i < inputNums.length; i++) {\n            const iNum = parseInt(inputNums[i], 10);\n            if (iNum > inputNum) {\n                if (iNum < num) {\n                    num = iNum;\n                }\n            }\n        }\n        return num;\n    }\n\n    /**\n     * Removes a tab and it's corresponding output\n     *\n     * @param {number} inputNum\n     */\n    removeTab(inputNum) {\n        if (!this.outputExists(inputNum)) return;\n\n        const tabElement = this.manager.tabs.getTabItem(inputNum, \"output\");\n\n        this.removeOutput(inputNum);\n\n        if (tabElement !== null) {\n            this.refreshTabs(this.getPreviousInputNum(inputNum), \"left\");\n        }\n    }\n\n    /**\n     * Redraw the entire tab bar to remove any outdated tabs\n     *\n     * @param {number} activeTab\n     * @param {string} direction - Either \"left\" or \"right\"\n     */\n    refreshTabs(activeTab, direction) {\n        const newNums = this.getNearbyNums(activeTab, direction),\n            tabsLeft = (newNums[0] !== this.getSmallestInputNum() && newNums.length > 0),\n            tabsRight = (newNums[newNums.length - 1] !== this.getLargestInputNum() && newNums.length > 0);\n\n        this.manager.tabs.refreshTabs(newNums, activeTab, tabsLeft, tabsRight, \"output\");\n\n        for (let i = 0; i < newNums.length; i++) {\n            this.displayTabInfo(newNums[i]);\n        }\n    }\n\n    /**\n     * Display output information in the tab header\n     *\n     * @param {number} inputNum\n     */\n    async displayTabInfo(inputNum) {\n        // Don't display anything if there are no, or only one, tabs\n        if (!this.outputExists(inputNum) || Object.keys(this.outputs).length <= 1) return;\n\n        const dish = this.getOutputDish(inputNum);\n        let tabStr = \"\";\n\n        if (dish !== null) {\n            tabStr = await this.getDishTitle(this.getOutputDish(inputNum), 100);\n            tabStr = tabStr.replace(/[\\n\\r]/g, \"\");\n        }\n        this.manager.tabs.updateTabHeader(inputNum, tabStr, \"output\");\n        if (this.manager.worker.recipeConfig !== undefined) {\n            this.manager.tabs.updateTabProgress(inputNum, this.outputs[inputNum]?.progress, this.manager.worker.recipeConfig.length, \"output\");\n        }\n\n        const tabItem = this.manager.tabs.getTabItem(inputNum, \"output\");\n        if (tabItem) {\n            if (this.outputs[inputNum].status === \"error\") {\n                tabItem.style.color = \"#FF0000\";\n            } else {\n                tabItem.style.color = \"\";\n            }\n        }\n    }\n\n    /**\n     * Triggers the BackgroundWorker to attempt Magic on the current output.\n     */\n    async backgroundMagic() {\n        this.hideMagicButton();\n        const dish = this.getOutputDish(this.manager.tabs.getActiveTab(\"output\"));\n        if (!this.app.options.autoMagic || dish === null) return;\n        const buffer = await this.getDishBuffer(dish);\n        const sample = buffer.slice(0, 1000) || \"\";\n\n        if (sample.length || sample.byteLength) {\n            this.manager.background.magic(sample);\n        }\n    }\n\n    /**\n     * Handles the results of a background Magic call.\n     *\n     * @param {Object[]} options\n     */\n    backgroundMagicResult(options) {\n        if (!options.length) return;\n\n        const currentRecipeConfig = this.app.getRecipeConfig();\n        let msg = \"\",\n            newRecipeConfig;\n\n        if (options[0].recipe.length) {\n            const opSequence = options[0].recipe.map(o => o.op).join(\", \");\n            newRecipeConfig = currentRecipeConfig.concat(options[0].recipe);\n            msg = `<i>${opSequence}</i> will produce <span class=\"data-text\">\"${Utils.escapeHtml(Utils.truncate(options[0].data), 30)}\"</span>`;\n        } else if (options[0].fileType && options[0].fileType.name) {\n            const ft = options[0].fileType;\n            newRecipeConfig = currentRecipeConfig.concat([{op: \"Detect File Type\", args: []}]);\n            msg = `<i>${ft.name}</i> file detected`;\n        } else {\n            return;\n        }\n\n        this.showMagicButton(msg, newRecipeConfig);\n    }\n\n    /**\n     * Handler for Magic click events.\n     *\n     * Loads the Magic recipe.\n     *\n     * @fires Manager#statechange\n     */\n    magicClick() {\n        const magicButton = document.getElementById(\"magic\");\n        this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute(\"data-recipe\")));\n        window.dispatchEvent(this.manager.statechange);\n        this.hideMagicButton();\n    }\n\n    /**\n     * Displays the Magic button with a title and adds a link to a recipe.\n     *\n     * @param {string} msg\n     * @param {Object[]} recipeConfig\n     */\n    showMagicButton(msg, recipeConfig) {\n        const magicButton = document.getElementById(\"magic\");\n        magicButton.setAttribute(\"data-original-title\", msg);\n        magicButton.setAttribute(\"data-recipe\", JSON.stringify(recipeConfig), null, \"\");\n        magicButton.classList.remove(\"hidden\");\n        magicButton.classList.add(\"pulse\");\n    }\n\n\n    /**\n     * Hides the Magic button and resets its values.\n     */\n    hideMagicButton() {\n        const magicButton = document.getElementById(\"magic\");\n        magicButton.classList.add(\"hidden\");\n        magicButton.classList.remove(\"pulse\");\n        magicButton.setAttribute(\"data-recipe\", \"\");\n        magicButton.setAttribute(\"data-original-title\", \"Magic!\");\n    }\n\n    /**\n     * Handler for extract file events.\n     *\n     * @param {Event} e\n     */\n    async extractFileClick(e) {\n        e.preventDefault();\n        e.stopPropagation();\n\n        const el = e.target.nodeName === \"I\" ? e.target.parentNode : e.target;\n        const blobURL = el.getAttribute(\"blob-url\");\n        const fileName = el.getAttribute(\"file-name\");\n\n        const blob = await fetch(blobURL).then(r => r.blob());\n        this.manager.input.loadUIFiles([new File([blob], fileName, {type: blob.type})]);\n    }\n\n\n    /**\n     * Handler for copy click events.\n     * Copies the output to the clipboard\n     */\n    async copyClick() {\n        const dish = this.getOutputDish(this.manager.tabs.getActiveTab(\"output\"));\n        if (dish === null) {\n            this.app.alert(\"Could not find data to copy. Has this output been baked yet?\", 3000);\n            return;\n        }\n\n        const output = await this.getDishStr(dish);\n        const self = this;\n\n        navigator.clipboard.writeText(output).then(function() {\n            self.app.alert(\"Copied raw output successfully.\", 2000);\n        }, function(err) {\n            self.app.alert(\"Sorry, the output could not be copied.\", 3000);\n        });\n    }\n\n    /**\n     * Handler for switch click events.\n     * Moves the current output into the input textarea.\n     */\n    async switchClick() {\n        const activeTab = this.manager.tabs.getActiveTab(\"output\");\n        const switchButton = document.getElementById(\"switch\");\n\n        switchButton.classList.add(\"spin\");\n        switchButton.disabled = true;\n        switchButton.firstElementChild.innerHTML = \"autorenew\";\n        $(switchButton).tooltip(\"hide\");\n\n        const activeData = await this.getDishBuffer(this.getOutputDish(activeTab));\n\n        if (this.outputExists(activeTab)) {\n            this.manager.input.set(activeTab, {\n                type: \"userinput\",\n                buffer: activeData,\n                encoding: this.outputs[activeTab].encoding,\n                eolSequence: this.outputs[activeTab].eolSequence\n            });\n        }\n\n        switchButton.classList.remove(\"spin\");\n        switchButton.disabled = false;\n        switchButton.firstElementChild.innerHTML = \"open_in_browser\";\n    }\n\n    /**\n     * Handler for maximise output click events.\n     * Resizes the output frame to be as large as possible, or restores it to its original size.\n     */\n    maximiseOutputClick(e) {\n        const el = e.target.id === \"maximise-output\" ? e.target : e.target.parentNode;\n\n        if (el.getAttribute(\"data-original-title\").indexOf(\"Maximise\") === 0) {\n            document.body.classList.add(\"output-maximised\");\n            this.app.initialiseSplitter(true);\n            this.app.columnSplitter.collapse(0);\n            this.app.columnSplitter.collapse(1);\n            this.app.ioSplitter.collapse(0);\n\n            $(el).attr(\"data-original-title\", \"Restore output pane\");\n            $(el).attr(\"aria-label\", \"Restore output pane\");\n            el.querySelector(\"i\").innerHTML = \"fullscreen_exit\";\n        } else {\n            document.body.classList.remove(\"output-maximised\");\n            $(el).attr(\"data-original-title\", \"Maximise output pane\");\n            $(el).attr(\"aria-label\", \"Maximise output pane\");\n            el.querySelector(\"i\").innerHTML = \"fullscreen\";\n            this.app.initialiseSplitter(false);\n            this.app.resetLayout();\n        }\n    }\n\n    /**\n     * Handler for find tab button clicked\n     */\n    findTab() {\n        this.filterTabSearch();\n        $(\"#output-tab-modal\").modal();\n    }\n\n    /**\n     * Searches the outputs using the filter settings and displays the results\n     */\n    async filterTabSearch() {\n        const showPending = document.getElementById(\"output-show-pending\").checked,\n            showBaking = document.getElementById(\"output-show-baking\").checked,\n            showBaked = document.getElementById(\"output-show-baked\").checked,\n            showStale = document.getElementById(\"output-show-stale\").checked,\n            showErrored = document.getElementById(\"output-show-errored\").checked,\n            contentFilter = document.getElementById(\"output-content-filter\").value,\n            resultsList = document.getElementById(\"output-search-results\"),\n            numResults = parseInt(document.getElementById(\"output-num-results\").value, 10),\n            inputNums = Object.keys(this.outputs),\n            results = [];\n\n        let contentFilterExp;\n        try {\n            contentFilterExp = new RegExp(contentFilter, \"i\");\n        } catch (error) {\n            this.app.handleError(error);\n            return;\n        }\n\n        // Search through the outputs for matching output results\n        for (let i = 0; i < inputNums.length; i++) {\n            const iNum = inputNums[i],\n                output = this.outputs[iNum];\n\n            if (output.status === \"pending\" && showPending ||\n                output.status === \"baking\" && showBaking ||\n                output.status === \"error\" && showErrored ||\n                output.status === \"stale\" && showStale ||\n                output.status === \"inactive\" && showStale) {\n                const outDisplay = {\n                    \"pending\": \"Not baked yet\",\n                    \"baking\": \"Baking\",\n                    \"error\": output.error || \"Errored\",\n                    \"stale\": \"Stale (output is out of date)\",\n                    \"inactive\": \"Not baked yet\"\n                };\n\n                // If the output has a dish object, check it against the filter\n                if (Object.prototype.hasOwnProperty.call(output, \"data\") &&\n                    output.data &&\n                    Object.prototype.hasOwnProperty.call(output.data, \"dish\")) {\n                    const data = await output.data.dish.get(Dish.STRING);\n                    if (contentFilterExp.test(data)) {\n                        results.push({\n                            inputNum: iNum,\n                            textDisplay: data.slice(0, 100)\n                        });\n                    }\n                } else {\n                    results.push({\n                        inputNum: iNum,\n                        textDisplay: outDisplay[output.status]\n                    });\n                }\n            } else if (output.status === \"baked\" && showBaked && output.progress === false) {\n                let data = await output.data.dish.get(Dish.STRING);\n                data = data.replace(/[\\r\\n]/g, \"\");\n                if (contentFilterExp.test(data)) {\n                    results.push({\n                        inputNum: iNum,\n                        textDisplay: data.slice(0, 100)\n                    });\n                }\n            } else if (output.progress !== false && showErrored) {\n                let data = await output.data.dish.get(Dish.STRING);\n                data = data.replace(/[\\r\\n]/g, \"\");\n                if (contentFilterExp.test(data)) {\n                    results.push({\n                        inputNum: iNum,\n                        textDisplay: data.slice(0, 100)\n                    });\n                }\n            }\n\n            if (results.length >= numResults) {\n                break;\n            }\n        }\n\n        for (let i = resultsList.children.length - 1; i >= 0; i--) {\n            resultsList.children.item(i).remove();\n        }\n\n        for (let i = 0; i < results.length; i++) {\n            const newListItem = document.createElement(\"li\");\n            newListItem.classList.add(\"output-filter-result\");\n            newListItem.setAttribute(\"inputNum\", results[i].inputNum);\n            newListItem.innerText = `${results[i].inputNum}: ${results[i].textDisplay}`;\n\n            resultsList.appendChild(newListItem);\n        }\n    }\n\n    /**\n     * Handler for clicking on a filter result.\n     * Changes to the clicked output\n     *\n     * @param {event} e\n     */\n    filterItemClick(e) {\n        if (!e.target) return;\n        const inputNum = parseInt(e.target.getAttribute(\"inputNum\"), 10);\n        if (inputNum <= 0) return;\n\n        $(\"#output-tab-modal\").modal(\"hide\");\n        this.changeTab(inputNum, this.app.options.syncTabs);\n    }\n\n\n    /**\n     * Sets the console log level in the workers.\n     */\n    setLogLevel() {\n        if (!this.zipWorker) return;\n        this.zipWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n    }\n}\n\nexport default OutputWaiter;\n"
  },
  {
    "path": "src/web/waiters/RecipeWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport HTMLOperation from \"../HTMLOperation.mjs\";\nimport Sortable from \"sortablejs\";\nimport Utils from \"../../core/Utils.mjs\";\nimport {escapeControlChars} from \"../utils/editorUtils.mjs\";\nimport DOMPurify from \"dompurify\";\n\n\n/**\n * Waiter to handle events related to the recipe.\n */\nclass RecipeWaiter {\n\n    /**\n     * RecipeWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n        this.removeIntent = false;\n    }\n\n\n    /**\n     * Sets up the drag and drop capability for operations in the operations and recipe areas.\n     */\n    initialiseOperationDragNDrop() {\n        const recList = document.getElementById(\"rec-list\");\n\n        // Recipe list\n        Sortable.create(recList, {\n            group: \"recipe\",\n            sort: true,\n            animation: 0,\n            delay: 0,\n            filter: \".arg\",\n            preventOnFilter: false,\n            setData: function(dataTransfer, dragEl) {\n                dataTransfer.setData(\"Text\", dragEl.querySelector(\".op-title\").textContent);\n            },\n            onEnd: function(evt) {\n                if (this.removeIntent) {\n                    evt.item.remove();\n                    evt.target.dispatchEvent(this.manager.operationremove);\n                }\n            }.bind(this),\n            onSort: function(evt) {\n                if (evt.from.id === \"rec-list\") {\n                    document.dispatchEvent(this.manager.statechange);\n                }\n            }.bind(this)\n        });\n\n        Sortable.utils.on(recList, \"dragover\", function() {\n            this.removeIntent = false;\n        }.bind(this));\n\n        Sortable.utils.on(recList, \"dragleave\", function() {\n            this.removeIntent = true;\n            this.app.progress = 0;\n        }.bind(this));\n\n        Sortable.utils.on(recList, \"touchend\", function(e) {\n            const loc = e.changedTouches[0];\n            const target = document.elementFromPoint(loc.clientX, loc.clientY);\n\n            this.removeIntent = !recList.contains(target);\n        }.bind(this));\n\n        // Favourites category\n        document.querySelector(\"#categories a\").addEventListener(\"dragover\", this.favDragover.bind(this));\n        document.querySelector(\"#categories a\").addEventListener(\"dragleave\", this.favDragleave.bind(this));\n        document.querySelector(\"#categories a\").addEventListener(\"drop\", this.favDrop.bind(this));\n    }\n\n\n    /**\n     * Creates a drag-n-droppable seed list of operations.\n     *\n     * @param {element} listEl - The list to initialise\n     */\n    createSortableSeedList(listEl) {\n        Sortable.create(listEl, {\n            group: {\n                name: \"recipe\",\n                pull: \"clone\",\n                put: false,\n            },\n            sort: false,\n            setData: function(dataTransfer, dragEl) {\n                dataTransfer.setData(\"Text\", dragEl.textContent);\n            },\n            onStart: function(evt) {\n                // Removes popover element and event bindings from the dragged operation but not the\n                // event bindings from the one left in the operations list. Without manually removing\n                // these bindings, we cannot re-initialise the popover on the stub operation.\n                $(evt.item)\n                    .popover(\"dispose\")\n                    .removeData(\"bs.popover\")\n                    .off(\"mouseenter\")\n                    .off(\"mouseleave\")\n                    .attr(\"data-toggle\", \"popover-disabled\");\n                $(evt.clone)\n                    .off(\".popover\")\n                    .removeData(\"bs.popover\");\n            },\n            onEnd: this.opSortEnd.bind(this)\n        });\n    }\n\n\n    /**\n     * Handler for operation sort end events.\n     * Removes the operation from the list if it has been dropped outside. If not, adds it to the list\n     * at the appropriate place and initialises it.\n     *\n     * @fires Manager#operationadd\n     * @param {event} evt\n     */\n    opSortEnd(evt) {\n        if (this.removeIntent && evt.item.parentNode.id === \"rec-list\") {\n            evt.item.remove();\n            return;\n        }\n\n        // Reinitialise the popover on the original element in the ops list because for some reason it\n        // gets destroyed and recreated. If the clone isn't in the ops list, we use the original item instead.\n        let enableOpsElement;\n        if (evt.clone?.parentNode?.classList?.contains(\"op-list\")) {\n            enableOpsElement = evt.clone;\n        } else {\n            enableOpsElement = evt.item;\n            $(evt.item).attr(\"data-toggle\", \"popover\");\n        }\n        this.manager.ops.enableOpsListPopovers(enableOpsElement);\n\n        if (evt.item.parentNode.id !== \"rec-list\") {\n            return;\n        }\n\n        this.buildRecipeOperation(evt.item);\n        evt.item.dispatchEvent(this.manager.operationadd);\n    }\n\n\n    /**\n     * Handler for favourite dragover events.\n     * If the element being dragged is an operation, displays a visual cue so that the user knows it can\n     * be dropped here.\n     *\n     * @param {event} e\n     */\n    favDragover(e) {\n        if (e.dataTransfer.effectAllowed !== \"move\")\n            return false;\n\n        e.stopPropagation();\n        e.preventDefault();\n        if (e.target?.className?.indexOf(\"category-title\") > -1) {\n            // Hovering over the a\n            e.target.classList.add(\"favourites-hover\");\n        } else if (e.target?.parentNode?.className?.indexOf(\"category-title\") > -1) {\n            // Hovering over the Edit button\n            e.target.parentNode.classList.add(\"favourites-hover\");\n        } else if (e.target?.parentNode?.parentNode?.className?.indexOf(\"category-title\") > -1) {\n            // Hovering over the image on the Edit button\n            e.target.parentNode.parentNode.classList.add(\"favourites-hover\");\n        }\n    }\n\n\n    /**\n     * Handler for favourite dragleave events.\n     * Removes the visual cue.\n     *\n     * @param {event} e\n     */\n    favDragleave(e) {\n        e.stopPropagation();\n        e.preventDefault();\n        document.querySelector(\"#categories a\").classList.remove(\"favourites-hover\");\n    }\n\n\n    /**\n     * Handler for favourite drop events.\n     * Adds the dragged operation to the favourites list.\n     *\n     * @param {event} e\n     */\n    favDrop(e) {\n        e.stopPropagation();\n        e.preventDefault();\n        e.target.classList.remove(\"favourites-hover\");\n\n        const opName = e.dataTransfer.getData(\"Text\");\n        this.app.addFavourite(opName);\n    }\n\n\n    /**\n     * Handler for ingredient change events.\n     *\n     * @fires Manager#statechange\n     */\n    ingChange(e) {\n        if (e && e?.target?.classList?.contains(\"no-state-change\")) return;\n        window.dispatchEvent(this.manager.statechange);\n    }\n\n    /**\n     * Handler for hide-args click events.\n     * Updates the icon status.\n     *\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    hideArgsClick(e) {\n        const icon = e.target;\n\n        if (icon.getAttribute(\"hide-args\") === \"false\") {\n            icon.setAttribute(\"hide-args\", \"true\");\n            icon.innerText = \"keyboard_arrow_down\";\n            icon.classList.add(\"hide-args-selected\");\n            icon.parentNode.previousElementSibling.style.display = \"none\";\n        } else {\n            icon.setAttribute(\"hide-args\", \"false\");\n            icon.innerText = \"keyboard_arrow_up\";\n            icon.classList.remove(\"hide-args-selected\");\n            icon.parentNode.previousElementSibling.style.display = \"grid\";\n        }\n\n        const icons = Array.from(document.getElementsByClassName(\"hide-args-icon\"));\n        if (icons.length > 1) {\n            // Check if ALL the icons are hidden/shown\n            const uniqueIcons = icons.map(function(item) {\n                return item.getAttribute(\"hide-args\");\n            }).unique();\n\n            const controlsIconStatus = document.getElementById(\"hide-icon\").getAttribute(\"hide-args\");\n\n            // If all icons are in the same state and the global icon isn't, fix it\n            if (uniqueIcons.length === 1 && icon.getAttribute(\"hide-args\") !== controlsIconStatus) {\n                this.manager.controls.hideRecipeArgsClick();\n            }\n        }\n\n        window.dispatchEvent(this.manager.statechange);\n    }\n\n    /**\n     * Handler for disable click events.\n     * Updates the icon status.\n     *\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    disableClick(e) {\n        const icon = e.target;\n\n        if (icon.getAttribute(\"disabled\") === \"false\") {\n            icon.setAttribute(\"disabled\", \"true\");\n            icon.classList.add(\"disable-icon-selected\");\n            icon.parentNode.parentNode.classList.add(\"disabled\");\n        } else {\n            icon.setAttribute(\"disabled\", \"false\");\n            icon.classList.remove(\"disable-icon-selected\");\n            icon.parentNode.parentNode.classList.remove(\"disabled\");\n        }\n\n        this.app.progress = 0;\n        window.dispatchEvent(this.manager.statechange);\n    }\n\n\n    /**\n     * Handler for breakpoint click events.\n     * Updates the icon status.\n     *\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    breakpointClick(e) {\n        const bp = e.target;\n\n        if (bp.getAttribute(\"break\") === \"false\") {\n            bp.setAttribute(\"break\", \"true\");\n            bp.classList.add(\"breakpoint-selected\");\n        } else {\n            bp.setAttribute(\"break\", \"false\");\n            bp.classList.remove(\"breakpoint-selected\");\n        }\n\n        window.dispatchEvent(this.manager.statechange);\n    }\n\n\n    /**\n     * Handler for operation doubleclick events.\n     * Removes the operation from the recipe and auto bakes.\n     *\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    operationDblclick(e) {\n        e.target.remove();\n        this.opRemove(e);\n    }\n\n\n    /**\n     * Handler for operation child doubleclick events.\n     * Removes the operation from the recipe.\n     *\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    operationChildDblclick(e) {\n        e.target.parentNode.remove();\n        this.opRemove(e);\n    }\n\n\n    /**\n     * Generates a configuration object to represent the current recipe.\n     *\n     * @returns {recipeConfig}\n     */\n    getConfig() {\n        const config = [];\n        let ingredients, ingList, disabled, bp, item;\n        const operations = document.querySelectorAll(\"#rec-list li.operation\");\n\n        for (let i = 0; i < operations.length; i++) {\n            ingredients = [];\n            disabled = operations[i].querySelector(\".disable-icon\");\n            bp = operations[i].querySelector(\".breakpoint\");\n            ingList = operations[i].querySelectorAll(\".arg\");\n\n            for (let j = 0; j < ingList.length; j++) {\n                if (ingList[j].getAttribute(\"type\") === \"checkbox\") {\n                    // checkbox\n                    ingredients[j] = ingList[j].checked;\n                } else if (ingList[j].classList.contains(\"toggle-string\")) {\n                    // toggleString\n                    ingredients[j] = {\n                        option: ingList[j].parentNode.parentNode.querySelector(\"button\").textContent,\n                        string: ingList[j].value\n                    };\n                } else if (ingList[j].getAttribute(\"type\") === \"number\") {\n                    // number\n                    ingredients[j] = parseFloat(ingList[j].value);\n                } else {\n                    // all others\n                    ingredients[j] = ingList[j].value;\n                }\n            }\n\n            item = {\n                op: operations[i].querySelector(\".op-title\").textContent,\n                args: ingredients\n            };\n\n            if (disabled && disabled.getAttribute(\"disabled\") === \"true\") {\n                item.disabled = true;\n            }\n\n            if (bp && bp.getAttribute(\"break\") === \"true\") {\n                item.breakpoint = true;\n            }\n\n            config.push(item);\n        }\n\n        return config;\n    }\n\n\n    /**\n     * Moves or removes the breakpoint indicator in the recipe based on the position.\n     *\n     * @param {number|boolean} position - If boolean, turn off all indicators\n     */\n    updateBreakpointIndicator(position) {\n        const operations = document.querySelectorAll(\"#rec-list li.operation\");\n        if (typeof position === \"boolean\") position = operations.length;\n        for (let i = 0; i < operations.length; i++) {\n            if (i === position) {\n                operations[i].classList.add(\"break\");\n            } else {\n                operations[i].classList.remove(\"break\");\n            }\n        }\n    }\n\n\n    /**\n     * Given an operation stub element, this function converts it into a full recipe element with\n     * arguments.\n     *\n     * @param {element} el - The operation stub element from the operations pane\n     */\n    buildRecipeOperation(el) {\n        const opName = el.textContent;\n        const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);\n        el.innerHTML = op.toFullHtml();\n\n        if (this.app.operations[opName].flowControl) {\n            el.classList.add(\"flow-control-op\");\n        }\n\n        $(el).find(\"[data-toggle='tooltip']\").tooltip();\n\n        // Disable auto-bake if this is a manual op\n        if (op.manualBake && this.app.autoBake_) {\n            this.manager.controls.setAutoBake(false);\n            this.app.alert(\"Auto-Bake is disabled by default when using this operation.\", 5000);\n        }\n    }\n\n\n    /**\n     * Adds the specified operation to the recipe.\n     *\n     * @fires Manager#operationadd\n     * @param {string} name - The name of the operation to add\n     * @returns {element}\n     */\n    addOperation(name) {\n        const item = document.createElement(\"li\");\n\n        item.classList.add(\"operation\");\n        const clean = DOMPurify.sanitize(name);\n        item.innerHTML = clean;\n\n        this.buildRecipeOperation(item);\n        document.getElementById(\"rec-list\").appendChild(item);\n\n        item.dispatchEvent(this.manager.operationadd);\n        return item;\n    }\n\n\n    /**\n     * Removes all operations from the recipe.\n     *\n     * @fires Manager#operationremove\n     */\n    clearRecipe() {\n        const recList = document.getElementById(\"rec-list\");\n        while (recList.firstChild) {\n            recList.removeChild(recList.firstChild);\n        }\n        recList.dispatchEvent(this.manager.operationremove);\n    }\n\n\n    /**\n     * Handler for operation dropdown events from toggleString arguments.\n     * Sets the selected option as the name of the button.\n     *\n     * @param {event} e\n     */\n    dropdownToggleClick(e) {\n        e.stopPropagation();\n        e.preventDefault();\n\n        const el = e.target;\n        const button = el.parentNode.parentNode.querySelector(\"button\");\n\n        button.innerHTML = el.textContent;\n        this.ingChange();\n    }\n\n\n    /**\n     * Triggers various change events for operation arguments that have just been initialised.\n     *\n     * @param {HTMLElement} op\n     */\n    triggerArgEvents(op) {\n        // Trigger populateOption and argSelector events\n        const triggerableOptions = op.querySelectorAll(\".populate-option, .arg-selector\");\n        const evt = new Event(\"change\", {bubbles: true});\n        if (triggerableOptions.length) {\n            for (const el of triggerableOptions) {\n                el.dispatchEvent(evt);\n            }\n        }\n    }\n\n\n    /**\n     * Handler for operationadd events.\n     *\n     * @listens Manager#operationadd\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    opAdd(e) {\n        log.debug(`'${e.target.querySelector(\".op-title\").textContent}' added to recipe`);\n\n        this.triggerArgEvents(e.target);\n        window.dispatchEvent(this.manager.statechange);\n    }\n\n\n    /**\n     * Handler for operationremove events.\n     *\n     * @listens Manager#operationremove\n     * @fires Manager#statechange\n     * @param {event} e\n     */\n    opRemove(e) {\n        log.debug(\"Operation removed from recipe\");\n        window.dispatchEvent(this.manager.statechange);\n    }\n\n\n    /**\n     * Handler for text argument dragover events.\n     * Gives the user a visual cue to show that items can be dropped here.\n     *\n     * @param {event} e\n     */\n    textArgDragover (e) {\n        // This will be set if we're dragging an operation\n        if (e.dataTransfer.effectAllowed === \"move\")\n            return false;\n\n        e.stopPropagation();\n        e.preventDefault();\n        e.target.closest(\"textarea.arg\").classList.add(\"dropping-file\");\n    }\n\n\n    /**\n     * Handler for text argument dragleave events.\n     * Removes the visual cue.\n     *\n     * @param {event} e\n     */\n    textArgDragLeave (e) {\n        e.stopPropagation();\n        e.preventDefault();\n        e.target.classList.remove(\"dropping-file\");\n    }\n\n\n    /**\n     * Handler for text argument drop events.\n     * Loads the dragged data into the argument textarea.\n     *\n     * @param {event} e\n     */\n    textArgDrop(e) {\n        // This will be set if we're dragging an operation\n        if (e.dataTransfer.effectAllowed === \"move\")\n            return false;\n\n        e.stopPropagation();\n        e.preventDefault();\n        const targ = e.target;\n        const file = e.dataTransfer.files[0];\n        const text = e.dataTransfer.getData(\"Text\");\n\n        targ.classList.remove(\"dropping-file\");\n\n        if (text) {\n            targ.value = text;\n            return;\n        }\n\n        if (file) {\n            const reader = new FileReader();\n            const self = this;\n            reader.onload = function (e) {\n                targ.value = e.target.result;\n                // Trigger floating label move\n                const changeEvent = new Event(\"change\");\n                targ.dispatchEvent(changeEvent);\n                window.dispatchEvent(self.manager.statechange);\n            };\n            reader.readAsText(file);\n        }\n    }\n\n\n    /**\n     * Sets register values.\n     *\n     * @param {number} opIndex\n     * @param {number} numPrevRegisters\n     * @param {string[]} registers\n     */\n    setRegisters(opIndex, numPrevRegisters, registers) {\n        const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`),\n            prevRegList = op.querySelector(\".register-list\");\n\n        // Remove previous div\n        if (prevRegList) prevRegList.remove();\n\n        const registerList = [];\n        for (let i = 0; i < registers.length; i++) {\n            registerList.push(`$R${numPrevRegisters + i} = ${escapeControlChars(Utils.escapeHtml(Utils.truncate(registers[i], 100)))}`);\n        }\n        const registerListEl = `<div class=\"register-list\">\n                ${registerList.join(\"<br>\")}\n            </div>`;\n\n        op.insertAdjacentHTML(\"beforeend\", registerListEl);\n    }\n\n\n    /**\n     * Adjusts the number of ingredient columns as the width of the recipe changes.\n     */\n    adjustWidth() {\n        const recList = document.getElementById(\"rec-list\");\n\n        // Hide Chef icon on Bake button if the page is compressed\n        const bakeIcon = document.querySelector(\"#bake img\");\n\n        if (recList.clientWidth < 370) {\n            // Hide Chef icon on Bake button\n            bakeIcon.style.display = \"none\";\n        } else {\n            bakeIcon.style.display = \"inline-block\";\n        }\n\n        // Scale controls to fit pane width\n        const controls = document.getElementById(\"controls\");\n        const controlsContent = document.getElementById(\"controls-content\");\n        const scale = (controls.clientWidth - 1) / controlsContent.scrollWidth;\n\n        controlsContent.style.transform = `scale(${scale})`;\n    }\n\n}\n\nexport default RecipeWaiter;\n"
  },
  {
    "path": "src/web/waiters/SeasonalWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\n/**\n * Waiter to handle seasonal events and easter eggs.\n */\nclass SeasonalWaiter {\n\n    /**\n     * SeasonalWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n    }\n\n\n    /**\n     * Loads all relevant items depending on the current date.\n     */\n    load() {\n        // Konami code\n        this.kkeys = [];\n        window.addEventListener(\"keydown\", this.konamiCodeListener.bind(this));\n\n        // CyberChef Challenge\n        log.info(\"43 6f 6e 67 72 61 74 75 6c 61 74 69 6f 6e 73 2c 20 79 6f 75 20 68 61 76 65 20 63 6f 6d 70 6c 65 74 65 64 20 43 79 62 65 72 43 68 65 66 20 63 68 61 6c 6c 65 6e 67 65 20 23 31 21 0a 0a 54 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 65 78 70 6c 6f 72 65 64 20 68 65 78 61 64 65 63 69 6d 61 6c 20 65 6e 63 6f 64 69 6e 67 2e 20 54 6f 20 6c 65 61 72 6e 20 6d 6f 72 65 2c 20 76 69 73 69 74 20 77 69 6b 69 70 65 64 69 61 2e 6f 72 67 2f 77 69 6b 69 2f 48 65 78 61 64 65 63 69 6d 61 6c 2e 0a 0a 54 68 65 20 63 6f 64 65 20 66 6f 72 20 74 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 69 73 20 39 64 34 63 62 63 65 66 2d 62 65 35 32 2d 34 37 35 31 2d 61 32 62 32 2d 38 33 33 38 65 36 34 30 39 34 31 36 20 28 6b 65 65 70 20 74 68 69 73 20 70 72 69 76 61 74 65 29 2e 0a 0a 54 68 65 20 6e 65 78 74 20 63 68 61 6c 6c 65 6e 67 65 20 63 61 6e 20 62 65 20 66 6f 75 6e 64 20 61 74 20 68 74 74 70 73 3a 2f 2f 70 61 73 74 65 62 69 6e 2e 63 6f 6d 2f 47 53 6e 54 41 6d 6b 56 2e\");\n    }\n\n\n    /**\n     * Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in\n     * sequence.\n     * #konamicode\n     */\n    konamiCodeListener(e) {\n        this.kkeys.push(e.keyCode);\n        const konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];\n        for (let i = 0; i < this.kkeys.length; i++) {\n            if (this.kkeys[i] !== konami[i]) {\n                this.kkeys = [];\n                break;\n            }\n            if (i === konami.length - 1) {\n                $(\"body\").children().toggleClass(\"konami\");\n                this.kkeys = [];\n            }\n        }\n    }\n\n}\n\nexport default SeasonalWaiter;\n"
  },
  {
    "path": "src/web/waiters/TabWaiter.mjs",
    "content": "/**\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n/**\n * Waiter to handle events related to the input and output tabs\n */\nclass TabWaiter {\n\n    /**\n     * TabWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n    }\n\n    /**\n     * Calculates the maximum number of tabs to display\n     *\n     * @returns {number}\n     */\n    calcMaxTabs() {\n        let numTabs = Math.floor((document.getElementById(\"IO\").offsetWidth - 75)  / 120);\n        numTabs = (numTabs > 1) ? numTabs : 2;\n\n        return numTabs;\n    }\n\n    /**\n     * Gets the currently active input or active tab number\n     *\n     * @param {string} io - Either \"input\" or \"output\"\n     * @returns {number} - The currently active tab or -1\n     */\n    getActiveTab(io) {\n        const activeTabs = document.getElementsByClassName(`active-${io}-tab`);\n        if (activeTabs.length > 0) {\n            if (!activeTabs.item(0).hasAttribute(\"inputNum\")) return -1;\n            const tabNum = activeTabs.item(0).getAttribute(\"inputNum\");\n            return parseInt(tabNum, 10);\n        }\n        return -1;\n    }\n\n    /**\n     * Gets the li element for the tab of a given input number\n     *\n     * @param {number} inputNum - The inputNum of the tab we're trying to get\n     * @param {string} io - Either \"input\" or \"output\"\n     * @returns {Element}\n     */\n    getTabItem(inputNum, io) {\n        const tabs = document.getElementById(`${io}-tabs`).children;\n        for (let i = 0; i < tabs.length; i++) {\n            if (parseInt(tabs.item(i).getAttribute(\"inputNum\"), 10) === inputNum) {\n                return tabs.item(i);\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Gets a list of tab numbers for the currently displayed tabs\n     *\n     * @param {string} io - Either \"input\" or \"output\"\n     * @returns {number[]}\n     */\n    getTabList(io) {\n        const nums = [],\n            tabs = document.getElementById(`${io}-tabs`).children;\n\n        for (let i = 0; i < tabs.length; i++) {\n            nums.push(parseInt(tabs.item(i).getAttribute(\"inputNum\"), 10));\n        }\n\n        return nums;\n    }\n\n    /**\n     * Creates a new tab element for the tab bar\n     *\n     * @param {number} inputNum - The inputNum of the new tab\n     * @param {boolean} active - If true, sets the tab to active\n     * @param {string} io - Either \"input\" or \"output\"\n     * @returns {Element}\n     */\n    createTabElement(inputNum, active, io) {\n        const newTab = document.createElement(\"li\");\n        newTab.setAttribute(\"inputNum\", inputNum.toString());\n\n        if (active) newTab.classList.add(`active-${io}-tab`);\n\n        const newTabContent = document.createElement(\"div\");\n        newTabContent.classList.add(`${io}-tab-content`);\n        newTabContent.innerText = `Tab ${inputNum.toString()}`;\n        newTabContent.addEventListener(\"wheel\", this.manager[io].scrollTab.bind(this.manager[io]), {passive: false});\n        newTab.appendChild(newTabContent);\n\n        if (io === \"input\") {\n            const newTabButton = document.createElement(\"button\"),\n                newTabButtonIcon = document.createElement(\"i\");\n            newTabButton.type = \"button\";\n            newTabButton.className = \"btn btn-primary bmd-btn-icon btn-close-tab\";\n            newTabButtonIcon.classList.add(\"material-icons\");\n            newTabButtonIcon.innerText = \"clear\";\n            newTabButton.appendChild(newTabButtonIcon);\n            newTabButton.addEventListener(\"click\", this.manager.input.removeTabClick.bind(this.manager.input));\n            newTab.appendChild(newTabButton);\n        }\n\n        return newTab;\n    }\n\n    /**\n     * Displays the tab bar for both the input and output\n     */\n    showTabBar() {\n        document.getElementById(\"input-tabs-wrapper\").style.display = \"block\";\n        document.getElementById(\"output-tabs-wrapper\").style.display = \"block\";\n        document.getElementById(\"input-wrapper\").classList.add(\"show-tabs\");\n        document.getElementById(\"output-wrapper\").classList.add(\"show-tabs\");\n        document.getElementById(\"save-all-to-file\").style.display = \"inline-block\";\n    }\n\n    /**\n     * Hides the tab bar for both the input and output\n     */\n    hideTabBar() {\n        document.getElementById(\"input-tabs-wrapper\").style.display = \"none\";\n        document.getElementById(\"output-tabs-wrapper\").style.display = \"none\";\n        document.getElementById(\"input-wrapper\").classList.remove(\"show-tabs\");\n        document.getElementById(\"output-wrapper\").classList.remove(\"show-tabs\");\n        document.getElementById(\"save-all-to-file\").style.display = \"none\";\n    }\n\n    /**\n     * Redraws the tab bar with an updated list of tabs, then changes to activeTab\n     *\n     * @param {number[]} nums - The inputNums of the tab bar to be drawn\n     * @param {number} activeTab - The inputNum of the activeTab\n     * @param {boolean} tabsLeft - True if there are tabs to the left of the displayed tabs\n     * @param {boolean} tabsRight - True if there are tabs to the right of the displayed tabs\n     * @param {string} io - Either \"input\" or \"output\"\n     */\n    refreshTabs(nums, activeTab, tabsLeft, tabsRight, io) {\n        const tabsList = document.getElementById(`${io}-tabs`);\n\n        // Remove existing tab elements\n        for (let i = tabsList.children.length - 1; i >= 0; i--) {\n            tabsList.children.item(i).remove();\n        }\n\n        // Create and add new tab elements\n        for (let i = 0; i < nums.length; i++) {\n            const active = (nums[i] === activeTab);\n            tabsList.appendChild(this.createTabElement(nums[i], active, io));\n        }\n\n        // Display shadows if there are tabs left / right of the displayed tabs\n        if (tabsLeft) {\n            tabsList.classList.add(\"tabs-left\");\n        } else {\n            tabsList.classList.remove(\"tabs-left\");\n        }\n        if (tabsRight) {\n            tabsList.classList.add(\"tabs-right\");\n        } else {\n            tabsList.classList.remove(\"tabs-right\");\n        }\n\n        // Show or hide the tab bar depending on how many tabs we have\n        if (nums.length > 1) {\n            this.showTabBar();\n        } else {\n            this.hideTabBar();\n        }\n    }\n\n    /**\n     * Changes the active tab to a different tab\n     *\n     * @param {number} inputNum - The inputNum of the tab to change to\n     * @param {string} io - Either \"input\" or \"output\"\n     * @return {boolean} - False if the tab is not currently being displayed\n     */\n    changeTab(inputNum, io) {\n        const tabsList = document.getElementById(`${io}-tabs`);\n\n        let found = false;\n        for (let i = 0; i < tabsList.children.length; i++) {\n            const tabNum = parseInt(tabsList.children.item(i).getAttribute(\"inputNum\"), 10);\n            if (tabNum === inputNum) {\n                tabsList.children.item(i).classList.add(`active-${io}-tab`);\n                found = true;\n            } else {\n                tabsList.children.item(i).classList.remove(`active-${io}-tab`);\n            }\n        }\n\n        return found;\n    }\n\n    /**\n     * Updates the tab header to display a preview of the tab contents\n     *\n     * @param {number} inputNum - The inputNum of the tab to update the header of\n     * @param {string} data - The data to display in the tab header\n     * @param {string} io - Either \"input\" or \"output\"\n     */\n    updateTabHeader(inputNum, data, io) {\n        const tab = this.getTabItem(inputNum, io);\n        if (tab === null) return;\n\n        let headerData = `Tab ${inputNum}`;\n        if (data.length > 0) {\n            headerData = data.slice(0, 100);\n            headerData = `${inputNum}: ${headerData}`;\n        }\n        tab.firstElementChild.innerText = headerData;\n    }\n\n    /**\n     * Updates the tab background to display the progress of the current tab\n     *\n     * @param {number} inputNum - The inputNum of the tab\n     * @param {number} progress - The current progress\n     * @param {number} total - The total which the progress is a percent of\n     * @param {string} io - Either \"input\" or \"output\"\n     */\n    updateTabProgress(inputNum, progress, total, io) {\n        const tabItem = this.getTabItem(inputNum, io);\n        if (tabItem === null) return;\n\n        const percentComplete = (progress / total) * 100;\n        if (percentComplete >= 100 || progress === false) {\n            tabItem.style.background = \"\";\n        } else {\n            tabItem.style.background = `linear-gradient(to right, var(--title-background-colour) ${percentComplete}%, var(--primary-background-colour) ${percentComplete}%)`;\n        }\n    }\n\n}\n\nexport default TabWaiter;\n"
  },
  {
    "path": "src/web/waiters/TimingWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\n/**\n * Waiter to handle timing of the baking process.\n */\nclass TimingWaiter {\n\n    /**\n     * TimingWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     * @param {Manager} manager - The CyberChef event manager.\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.inputs = {};\n        /*\n            Inputs example:\n            \"1\": {\n                \"inputEncodingStart\": 0,\n                \"inputEncodingEnd\": 0,\n                \"trigger\": 0\n                \"chefWorkerTasked\": 0,\n                \"bakeComplete\": 0,\n                \"bakeDuration\": 0,\n                \"settingOutput\": 0,\n                \"outputDecodingStart\": 0,\n                \"outputDecodingEnd\": 0,\n                \"complete\": 0\n            }\n        */\n    }\n\n\n    /**\n     * Record the time for an input\n     *\n     * @param {string} event\n     * @param {number} inputNum\n     * @param {number} value\n     */\n    recordTime(event, inputNum, value=Date.now()) {\n        inputNum = inputNum.toString();\n        if (!Object.keys(this.inputs).includes(inputNum)) {\n            this.inputs[inputNum] = {};\n        }\n        log.debug(`Recording ${event} for input ${inputNum}`);\n        this.inputs[inputNum][event] = value;\n    }\n\n    /**\n     * The duration of the main stages of a bake\n     *\n     * @param {number} inputNum\n     * @returns {number}\n     */\n    duration(inputNum) {\n        const input = this.inputs[inputNum.toString()];\n\n        // If this input has not been encoded yet, we cannot calculate a time\n        if (!input ||\n            !input.trigger ||\n            !input.inputEncodingEnd ||\n            !input.inputEncodingStart)\n            return 0;\n\n        // input encoding can happen before a bake is triggered, so it is calculated separately\n        const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart;\n\n        let total = 0, outputDecodingTotal = 0;\n\n        if (input.bakeComplete && input.bakeComplete > input.trigger)\n            total = input.bakeComplete - input.trigger;\n\n        if (input.settingOutput && input.settingOutput > input.trigger)\n            total = input.settingOutput - input.trigger;\n\n        if (input.outputDecodingStart && (input.outputDecodingStart > input.trigger) &&\n            input.outputDecodingEnd && (input.outputDecodingEnd > input.trigger)) {\n            total = input.outputDecodingEnd - input.trigger;\n            outputDecodingTotal = input.outputDecodingEnd - input.outputDecodingStart;\n        }\n\n        if (input.complete && input.complete > input.trigger)\n            total = inputEncodingTotal + input.bakeDuration + outputDecodingTotal;\n\n        return total;\n    }\n\n    /**\n     * The total time for a completed bake\n     *\n     * @param {number} inputNum\n     * @returns {number}\n     */\n    overallDuration(inputNum) {\n        const input = this.inputs[inputNum.toString()];\n\n        // If this input has not been encoded yet, we cannot calculate a time\n        if (!input ||\n            !input.trigger ||\n            !input.inputEncodingEnd ||\n            !input.inputEncodingStart)\n            return 0;\n\n        // input encoding can happen before a bake is triggered, so it is calculated separately\n        const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart;\n\n        let total = 0;\n        if (input.bakeComplete && input.bakeComplete > input.trigger)\n            total = input.bakeComplete - input.trigger;\n\n        if (input.settingOutput && input.settingOutput > input.trigger)\n            total = input.settingOutput - input.trigger;\n\n        if (input.outputDecodingStart && input.outputDecodingStart > input.trigger)\n            total = input.outputDecodingStart - input.trigger;\n\n        if (input.outputDecodingEnd && input.outputDecodingEnd > input.trigger)\n            total = input.outputDecodingEnd - input.trigger;\n\n        if (input.complete && input.complete > input.trigger)\n            total = input.complete - input.trigger;\n\n        return total + inputEncodingTotal;\n    }\n\n    /**\n     * Prints out the time between stages\n     *\n     * @param {number} inputNum\n     * @returns {string}\n     */\n    printStages(inputNum) {\n        const input = this.inputs[inputNum.toString()];\n        if (!input || !input.trigger) return \"\";\n\n        const total = this.overallDuration(inputNum),\n            inputEncoding = input.inputEncodingEnd - input.inputEncodingStart,\n            outputDecoding = input.outputDecodingEnd - input.outputDecodingStart,\n            overhead = total - inputEncoding - outputDecoding - input.bakeDuration;\n\n        return `Input encoding: ${inputEncoding}ms\nRecipe duration: ${input.bakeDuration}ms\nOutput decoding: ${outputDecoding}ms\n<span class=\"small\">Threading overhead: ${overhead}ms</span>`;\n    }\n\n    /**\n     * Logs every interval\n     *\n     * @param {number} inputNum\n     */\n    logAllTimes(inputNum) {\n        const input = this.inputs[inputNum.toString()];\n        if (!input || !input.trigger) return;\n\n        try {\n            log.debug(`Trigger:             ${input.trigger}\ninputEncodingStart:  ${input.inputEncodingStart} | ${input.inputEncodingStart - input.trigger}ms since trigger\ninputEncodingEnd:    ${input.inputEncodingEnd} | ${input.inputEncodingEnd - input.inputEncodingStart}ms input encoding time\nchefWorkerTasked:    ${input.chefWorkerTasked} | ${input.chefWorkerTasked - input.trigger}ms since trigger\nbakeDuration:                      | ${input.bakeDuration}ms duration in worker\nbakeComplete:        ${input.bakeComplete} | ${input.bakeComplete - input.chefWorkerTasked}ms since worker tasked\nsettingOutput:       ${input.settingOutput} | ${input.settingOutput - input.bakeComplete}ms since worker finished\noutputDecodingStart: ${input.outputDecodingStart} | ${input.outputDecodingStart - input.settingOutput}ms since output set\noutputDecodingEnd:   ${input.outputDecodingEnd} | ${input.outputDecodingEnd - input.outputDecodingStart}ms output encoding time\ncomplete:            ${input.complete} | ${input.complete - input.outputDecodingEnd}ms since output decoded\nTotal:                             | ${input.complete - input.trigger}ms since trigger`);\n        } catch (err) {}\n\n    }\n\n}\n\nexport default TimingWaiter;\n"
  },
  {
    "path": "src/web/waiters/WindowWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2016\n * @license Apache-2.0\n */\n\nimport { debounce } from \"../../core/Utils.mjs\";\n\n/**\n * Waiter to handle events related to the window object.\n */\nclass WindowWaiter {\n\n    /**\n     * WindowWaiter constructor.\n     *\n     * @param {App} app - The main view object for CyberChef.\n     */\n    constructor(app) {\n        this.app = app;\n    }\n\n\n    /**\n     * Handler for window resize events.\n     * Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause\n     * continuous resetting).\n     */\n    windowResize() {\n        debounce(this.app.adjustComponentSizes, 200, \"windowResize\", this.app, [])();\n    }\n\n\n    /**\n     * Handler for window blur events.\n     * Saves the current time so that we can calculate how long the window was unfocused for when\n     * focus is returned.\n     */\n    windowBlur() {\n        this.windowBlurTime = Date.now();\n    }\n\n\n    /**\n     * Handler for window focus events.\n     *\n     * When a browser tab is unfocused and the browser has to run lots of dynamic content in other\n     * tabs, it swaps out the memory for that tab.\n     * If the CyberChef tab has been unfocused for more than a minute, we run a silent bake which will\n     * force the browser to load and cache all the relevant JavaScript code needed to do a real bake.\n     * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for\n     * a long time and the browser has swapped out all its memory.\n     */\n    windowFocus() {\n        const unfocusedTime = Date.now() - this.windowBlurTime;\n        if (unfocusedTime > 60000) {\n            this.app.silentBake();\n        }\n    }\n\n}\n\nexport default WindowWaiter;\n"
  },
  {
    "path": "src/web/waiters/WorkerWaiter.mjs",
    "content": "/**\n * @author n1474335 [n1474335@gmail.com]\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport ChefWorker from \"worker-loader?inline=no-fallback!../../core/ChefWorker.js\";\nimport DishWorker from \"worker-loader?inline=no-fallback!../workers/DishWorker.mjs\";\nimport { debounce } from \"../../core/Utils.mjs\";\n\n/**\n * Waiter to handle conversations with the ChefWorker\n */\nclass WorkerWaiter {\n\n    /**\n     * WorkerWaiter constructor\n     *\n     * @param {App} app - The main view object for CyberChef\n     * @param {Manager} manager - The CyberChef event manager\n     */\n    constructor(app, manager) {\n        this.app = app;\n        this.manager = manager;\n\n        this.loaded = false;\n        this.chefWorkers = [];\n        this.inputs = [];\n        this.inputNums = [];\n        this.totalOutputs = 0;\n        this.loadingOutputs = 0;\n        this.bakeId = 0;\n        this.callbacks = {};\n        this.callbackID = 0;\n\n        this.maxWorkers = 1;\n        if (navigator.hardwareConcurrency !== undefined &&\n            navigator.hardwareConcurrency > 1) {\n            this.maxWorkers = navigator.hardwareConcurrency - 1;\n        }\n\n        // Store dishWorker action (getDishAs or getDishTitle)\n        this.dishWorker = {\n            worker: null,\n            currentAction: \"\"\n        };\n        this.dishWorkerQueue = [];\n    }\n\n    /**\n     * Terminates any existing ChefWorkers and sets up a new worker\n     */\n    setupChefWorker() {\n        for (let i = this.chefWorkers.length - 1; i >= 0; i--) {\n            this.removeChefWorker(this.chefWorkers[i]);\n        }\n\n        this.addChefWorker();\n        this.setupDishWorker();\n    }\n\n    /**\n     * Sets up a DishWorker to be used for performing Dish operations\n     */\n    setupDishWorker() {\n        if (this.dishWorker.worker !== null) {\n            this.dishWorker.worker.terminate();\n            this.dishWorker.currentAction = \"\";\n        }\n        log.debug(\"Adding new DishWorker\");\n\n        this.dishWorker.worker = new DishWorker();\n        this.dishWorker.worker.addEventListener(\"message\", this.handleDishMessage.bind(this));\n        this.dishWorker.worker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n\n        if (this.dishWorkerQueue.length > 0) {\n            this.postDishMessage(this.dishWorkerQueue.splice(0, 1)[0]);\n        }\n    }\n\n    /**\n     * Adds a new ChefWorker\n     *\n     * @returns {number} The index of the created worker\n     */\n    addChefWorker() {\n        if (this.chefWorkers.length === this.maxWorkers) {\n            // Can't create any more workers\n            return -1;\n        }\n\n        log.debug(`Adding new ChefWorker (${this.chefWorkers.length + 1}/${this.maxWorkers})`);\n\n        // Create a new ChefWorker and send it the docURL\n        const newWorker = new ChefWorker();\n        newWorker.addEventListener(\"message\", this.handleChefMessage.bind(this));\n        newWorker.postMessage({\n            action: \"setLogPrefix\",\n            data: \"ChefWorker\"\n        });\n        newWorker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n\n        let docURL = document.location.href.split(/[#?]/)[0];\n        const index = docURL.lastIndexOf(\"/\");\n        if (index > 0) {\n            docURL = docURL.substring(0, index);\n        }\n        newWorker.postMessage({\"action\": \"docURL\", \"data\": docURL});\n\n\n        // Store the worker, whether or not it's active, and the inputNum as an object\n        const newWorkerObj = {\n            worker: newWorker,\n            active: false,\n            inputNum: -1\n        };\n\n        this.chefWorkers.push(newWorkerObj);\n        return this.chefWorkers.indexOf(newWorkerObj);\n    }\n\n    /**\n     * Gets an inactive ChefWorker to be used for baking\n     *\n     * @param {boolean} [setActive=true] - If true, set the worker status to active\n     * @returns {number} - The index of the ChefWorker\n     */\n    getInactiveChefWorker(setActive=true) {\n        for (let i = 0; i < this.chefWorkers.length; i++) {\n            if (!this.chefWorkers[i].active) {\n                this.chefWorkers[i].active = setActive;\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    /**\n     * Removes a ChefWorker\n     *\n     * @param {Object} workerObj\n     */\n    removeChefWorker(workerObj) {\n        const index = this.chefWorkers.indexOf(workerObj);\n        if (index === -1) {\n            return;\n        }\n\n        if (this.chefWorkers.length > 1 || this.chefWorkers[index].active) {\n            log.debug(`Removing ChefWorker at index ${index}`);\n            this.chefWorkers[index].worker.terminate();\n            this.chefWorkers.splice(index, 1);\n        }\n\n        // There should always be a ChefWorker loaded\n        if (this.chefWorkers.length === 0) {\n            this.addChefWorker();\n        }\n    }\n\n    /**\n     * Finds and returns the object for the ChefWorker of a given inputNum\n     *\n     * @param {number} inputNum\n     */\n    getChefWorker(inputNum) {\n        for (let i = 0; i < this.chefWorkers.length; i++) {\n            if (this.chefWorkers[i].inputNum === inputNum) {\n                return this.chefWorkers[i];\n            }\n        }\n    }\n\n    /**\n     * Handler for messages sent back by the ChefWorkers\n     *\n     * @param {MessageEvent} e\n     */\n    handleChefMessage(e) {\n        const r = e.data;\n        let inputNum = 0;\n        log.debug(`Receiving '${r.action}' from ChefWorker.`);\n\n        if (Object.prototype.hasOwnProperty.call(r.data, \"inputNum\")) {\n            inputNum = r.data.inputNum;\n        }\n\n        const currentWorker = this.getChefWorker(inputNum);\n\n        switch (r.action) {\n            case \"bakeComplete\":\n                log.debug(`Bake ${inputNum} complete.`);\n                this.manager.timing.recordTime(\"bakeComplete\", inputNum);\n                this.manager.timing.recordTime(\"bakeDuration\", inputNum, r.data.duration);\n\n                if (r.data.error) {\n                    this.app.handleError(r.data.error);\n                    this.manager.output.updateOutputError(r.data.error, inputNum, r.data.progress);\n                } else {\n                    this.updateOutput(r.data, r.data.inputNum, r.data.bakeId, r.data.progress);\n                }\n\n                this.app.progress = r.data.progress;\n\n                if (r.data.progress === this.recipeConfig.length) {\n                    this.step = false;\n                }\n\n                this.workerFinished(currentWorker);\n                break;\n            case \"bakeError\":\n                this.app.handleError(r.data.error);\n                this.manager.output.updateOutputError(r.data.error, inputNum, r.data.progress);\n                this.app.progress = r.data.progress;\n                this.workerFinished(currentWorker);\n                break;\n            case \"dishReturned\":\n                this.callbacks[r.data.id](r.data);\n                break;\n            case \"silentBakeComplete\":\n                break;\n            case \"workerLoaded\":\n                this.app.workerLoaded = true;\n                log.debug(\"ChefWorker loaded\");\n                if (!this.loaded) {\n                    this.app.loaded();\n                    this.loaded = true;\n                } else {\n                    this.bakeNextInput(this.getInactiveChefWorker(false));\n                }\n                break;\n            case \"statusMessage\":\n                this.manager.output.updateOutputMessage(r.data.message, r.data.inputNum, true);\n                break;\n            case \"progressMessage\":\n                this.manager.output.updateOutputProgress(r.data.progress, r.data.total, r.data.inputNum);\n                break;\n            case \"optionUpdate\":\n                log.debug(`Setting ${r.data.option} to ${r.data.value}`);\n                this.app.options[r.data.option] = r.data.value;\n                break;\n            case \"setRegisters\":\n                this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers);\n                break;\n            case \"highlightsCalculated\":\n                this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction);\n                break;\n            default:\n                log.error(\"Unrecognised message from ChefWorker\", e);\n                break;\n        }\n    }\n\n    /**\n     * Update the value of an output\n     *\n     * @param {Object} data\n     * @param {number} inputNum\n     * @param {number} bakeId\n     * @param {number} progress\n     */\n    updateOutput(data, inputNum, bakeId, progress) {\n        this.manager.output.updateOutputBakeId(bakeId, inputNum);\n        if (progress === this.recipeConfig.length) {\n            progress = false;\n        }\n        this.manager.output.updateOutputProgress(progress, this.recipeConfig.length, inputNum);\n        this.manager.output.updateOutputValue(data, inputNum, false);\n\n        if (progress !== false) {\n            this.manager.output.updateOutputStatus(\"error\", inputNum);\n\n            if (inputNum === this.manager.tabs.getActiveTab(\"input\")) {\n                this.manager.recipe.updateBreakpointIndicator(progress);\n            }\n\n        } else {\n            this.manager.output.updateOutputStatus(\"baked\", inputNum);\n        }\n    }\n\n    /**\n     * Updates the UI to show if baking is in progress or not.\n     *\n     * @param {boolean} bakingStatus\n     */\n    setBakingStatus(bakingStatus) {\n        this.app.baking = bakingStatus;\n        debounce(this.manager.controls.toggleBakeButtonFunction, 20, \"toggleBakeButton\", this, [bakingStatus ? \"cancel\" : \"bake\"])();\n\n        if (bakingStatus) this.manager.output.hideMagicButton();\n    }\n\n    /**\n     * Get the progress of the ChefWorkers\n     */\n    getBakeProgress() {\n        const pendingInputs = this.inputNums.length + this.loadingOutputs + this.inputs.length;\n        let bakingInputs = 0;\n\n        for (let i = 0; i < this.chefWorkers.length; i++) {\n            if (this.chefWorkers[i].active) {\n                bakingInputs++;\n            }\n        }\n\n        const total = this.totalOutputs;\n        const bakedInputs = total - pendingInputs - bakingInputs;\n\n        return {\n            total: total,\n            pending: pendingInputs,\n            baking: bakingInputs,\n            baked: bakedInputs\n        };\n    }\n\n    /**\n     * Cancels the current bake making it possible to autobake again\n     */\n    cancelBakeForAutoBake() {\n        if (this.totalOutputs > 1) {\n            this.cancelBake();\n        } else {\n            // In this case the UI changes can be skipped\n\n            for (let i = this.chefWorkers.length - 1; i >= 0; i--) {\n                if (this.chefWorkers[i].active) {\n                    this.removeChefWorker(this.chefWorkers[i]);\n                }\n            }\n\n            this.inputs = [];\n            this.inputNums = [];\n            this.totalOutputs = 0;\n            this.loadingOutputs = 0;\n        }\n    }\n\n    /**\n     * Cancels the current bake by terminating and removing all ChefWorkers\n     *\n     * @param {boolean} [silent=false] - If true, don't set the output\n     * @param {boolean} [killAll=false] - If true, kills all chefWorkers regardless of status\n     */\n    cancelBake(silent=false, killAll=false) {\n        const deactiveOutputs = new Set();\n\n        for (let i = this.chefWorkers.length - 1; i >= 0; i--) {\n            if (this.chefWorkers[i].active || killAll) {\n                const inputNum = this.chefWorkers[i].inputNum;\n                this.removeChefWorker(this.chefWorkers[i]);\n                deactiveOutputs.add(inputNum);\n            }\n        }\n        this.setBakingStatus(false);\n\n        this.inputs.forEach(input => {\n            deactiveOutputs.add(input.inputNum);\n        });\n\n        this.inputNums.forEach(inputNum => {\n            deactiveOutputs.add(inputNum);\n        });\n\n        deactiveOutputs.forEach(num => {\n            this.manager.output.updateOutputStatus(\"inactive\", num);\n        });\n\n        const tabList = this.manager.tabs.getTabList(\"output\");\n        tabList.forEach(tab => {\n            this.manager.tabs.getTabItem(tab, \"output\").style.background = \"\";\n        });\n\n        this.inputs = [];\n        this.inputNums = [];\n        this.totalOutputs = 0;\n        this.loadingOutputs = 0;\n        if (!silent) this.manager.output.set(this.manager.tabs.getActiveTab(\"output\"));\n    }\n\n    /**\n     * Handle a worker completing baking\n     *\n     * @param {object} workerObj - Object containing the worker information\n     * @param {ChefWorker} workerObj.worker - The actual worker object\n     * @param {number} workerObj.inputNum - The inputNum of the input being baked by the worker\n     * @param {boolean} workerObj.active - If true, the worker is currently baking an input\n     */\n    workerFinished(workerObj) {\n        const workerIdx = this.chefWorkers.indexOf(workerObj);\n        this.chefWorkers[workerIdx].active = false;\n        if (this.inputs.length > 0) {\n            this.bakeNextInput(workerIdx);\n        } else if (this.inputNums.length === 0 && this.loadingOutputs === 0) {\n            // The ChefWorker is no longer needed\n            log.debug(\"No more inputs to bake.\");\n            const progress = this.getBakeProgress();\n            if (progress.total === progress.baked) {\n                this.bakingComplete();\n            }\n        }\n    }\n\n    /**\n     * Handler for completed bakes\n     */\n    bakingComplete() {\n        this.setBakingStatus(false);\n        let duration = Date.now() - this.bakeStartTime;\n        duration = duration.toLocaleString() + \"ms\";\n        const progress = this.getBakeProgress();\n\n        if (progress.total > 1) {\n            let width = progress.total.toLocaleString().length;\n            if (duration.length > width) {\n                width = duration.length;\n            }\n            width = width < 2 ? 2 : width;\n\n            const totalStr = progress.total.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n            const durationStr = duration.padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n\n            const inputNums = Object.keys(this.manager.output.outputs);\n            let avgTime = 0,\n                numOutputs = 0;\n            for (let i = 0; i < inputNums.length; i++) {\n                const output = this.manager.output.outputs[inputNums[i]];\n                if (output.status === \"baked\") {\n                    numOutputs++;\n                    avgTime += output.data.duration;\n                }\n            }\n            avgTime = Math.round(avgTime / numOutputs).toLocaleString() + \"ms\";\n            avgTime = avgTime.padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n\n            const msg = `total: ${totalStr}<br>time: ${durationStr}<br>average: ${avgTime}`;\n\n            const bakeInfo = document.getElementById(\"bake-info\");\n            bakeInfo.innerHTML = msg;\n            bakeInfo.style.display = \"\";\n        } else {\n            document.getElementById(\"bake-info\").style.display = \"none\";\n        }\n\n        document.getElementById(\"bake\").style.background = \"\";\n        this.totalOutputs = 0; // Reset for next time\n        log.debug(\"--- Bake complete ---\");\n    }\n\n    /**\n     * Bakes the next input and tells the inputWorker to load the next input\n     *\n     * @param {number} workerIdx - The index of the worker to bake with\n     */\n    bakeNextInput(workerIdx) {\n        if (this.inputs.length === 0) return;\n        if (workerIdx === -1) return;\n        if (!this.chefWorkers[workerIdx]) return;\n        this.chefWorkers[workerIdx].active = true;\n        const nextInput = this.inputs.splice(0, 1)[0];\n        if (typeof nextInput.inputNum === \"string\") nextInput.inputNum = parseInt(nextInput.inputNum, 10);\n\n        log.debug(`Baking input ${nextInput.inputNum}.`);\n        this.manager.output.updateOutputMessage(`Baking input ${nextInput.inputNum}...`, nextInput.inputNum, false);\n        this.manager.output.updateOutputStatus(\"baking\", nextInput.inputNum);\n\n        this.chefWorkers[workerIdx].inputNum = nextInput.inputNum;\n        const input = nextInput.input,\n            recipeConfig = this.recipeConfig;\n\n        if (this.step) {\n            // Remove all breakpoints from the recipe up to progress\n            if (nextInput.progress !== false) {\n                for (let i = 0; i < nextInput.progress; i++) {\n                    if (\"breakpoint\" in recipeConfig[i]) {\n                        delete recipeConfig[i].breakpoint;\n                    }\n                }\n            }\n\n            // Set a breakpoint at the next operation so we stop baking there\n            if (recipeConfig[this.app.progress]) recipeConfig[this.app.progress].breakpoint = true;\n        }\n\n        let transferable;\n        if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\n            transferable = [input];\n        }\n        this.manager.timing.recordTime(\"chefWorkerTasked\", nextInput.inputNum);\n        this.chefWorkers[workerIdx].worker.postMessage({\n            action: \"bake\",\n            data: {\n                input: input,\n                recipeConfig: recipeConfig,\n                options: this.options,\n                inputNum: nextInput.inputNum,\n                bakeId: this.bakeId\n            }\n        }, transferable);\n\n        if (this.inputNums.length > 0) {\n            this.manager.input.inputWorker.postMessage({\n                action: \"bakeNext\",\n                data: {\n                    inputNum: this.inputNums.splice(0, 1)[0],\n                    bakeId: this.bakeId\n                }\n            });\n            this.loadingOutputs++;\n        }\n    }\n\n    /**\n     * Bakes the current input using the current recipe.\n     *\n     * @param {Object[]} recipeConfig\n     * @param {Object} options\n     * @param {number} progress\n     * @param {boolean} step\n     */\n    bake(recipeConfig, options, progress, step) {\n        this.setBakingStatus(true);\n        this.manager.recipe.updateBreakpointIndicator(false);\n        this.bakeStartTime = Date.now();\n        this.bakeId++;\n        this.recipeConfig = recipeConfig;\n        this.options = options;\n        this.progress = progress;\n        this.step = step;\n\n        this.displayProgress();\n    }\n\n    /**\n     * Queues an input ready to be baked\n     *\n     * @param {object} inputData\n     * @param {string | ArrayBuffer} inputData.input\n     * @param {number} inputData.inputNum\n     * @param {number} inputData.bakeId\n     */\n    queueInput(inputData) {\n        this.loadingOutputs--;\n        if (this.app.baking && inputData.bakeId === this.bakeId) {\n            this.inputs.push(inputData);\n            this.bakeNextInput(this.getInactiveChefWorker(true));\n        }\n    }\n\n    /**\n     * Handles if an error is thrown by QueueInput\n     *\n     * @param {object} inputData\n     * @param {number} inputData.inputNum\n     * @param {number} inputData.bakeId\n     */\n    queueInputError(inputData) {\n        this.loadingOutputs--;\n        if (this.app.baking && inputData.bakeId === this.bakeId) {\n            this.manager.output.updateOutputError(\"Error queueing the input for a bake.\", inputData.inputNum, 0);\n\n            if (this.inputNums.length === 0) return;\n\n            // Load the next input\n            this.manager.input.inputWorker.postMessage({\n                action: \"bakeNext\",\n                data: {\n                    inputNum: this.inputNums.splice(0, 1)[0],\n                    bakeId: this.bakeId\n                }\n            });\n            this.loadingOutputs++;\n\n        }\n    }\n\n    /**\n     * Queues a list of inputNums to be baked by ChefWorkers, and begins baking\n     *\n     * @param {object} inputData\n     * @param {number[]} inputData.nums - The inputNums to be queued for baking\n     * @param {boolean} inputData.step - If true, only execute the next operation in the recipe\n     * @param {number} inputData.progress - The current progress through the recipe. Used when stepping\n     */\n    async bakeInputs(inputData) {\n        log.debug(`Baking input list [${inputData.nums.join(\",\")}]`);\n\n        return await new Promise(resolve => {\n            if (this.app.baking) return;\n            const inputNums = inputData.nums.filter(n => n > 0);\n            const step = inputData.step;\n\n            // Use cancelBake to clear out the inputs\n            this.cancelBake(true, false);\n\n            this.inputNums = inputNums;\n            this.totalOutputs = inputNums.length;\n            this.app.progress = inputData.progress;\n\n            let inactiveWorkers = 0;\n            for (let i = 0; i < this.chefWorkers.length; i++) {\n                if (!this.chefWorkers[i].active) {\n                    inactiveWorkers++;\n                }\n            }\n\n            for (let i = 0; i < inputNums.length - inactiveWorkers; i++) {\n                if (this.addChefWorker() === -1) break;\n            }\n\n            this.app.bake(step);\n\n            for (let i = 0; i < this.inputNums.length; i++) {\n                this.manager.output.updateOutputMessage(`Input ${inputNums[i]} has not been baked yet.`, inputNums[i], false);\n                this.manager.output.updateOutputStatus(\"pending\", inputNums[i]);\n            }\n\n            let numBakes = this.chefWorkers.length;\n            if (this.inputNums.length < numBakes) {\n                numBakes = this.inputNums.length;\n            }\n            for (let i = 0; i < numBakes; i++) {\n                this.manager.timing.recordTime(\"trigger\", this.inputNums[0]);\n                this.manager.input.inputWorker.postMessage({\n                    action: \"bakeNext\",\n                    data: {\n                        inputNum: this.inputNums.splice(0, 1)[0],\n                        bakeId: this.bakeId\n                    }\n                });\n                this.loadingOutputs++;\n            }\n            if (numBakes === 0) this.bakingComplete();\n        });\n    }\n\n    /**\n     * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant\n     * JavaScript code needed to do a real bake.\n     *\n     * @param {Object[]} [recipeConfig]\n     */\n    silentBake(recipeConfig) {\n        // If there aren't any active ChefWorkers, try to add one\n        let workerId = this.getInactiveChefWorker();\n        if (workerId === -1) {\n            workerId = this.addChefWorker();\n        }\n        if (workerId === -1) return;\n        this.chefWorkers[workerId].worker.postMessage({\n            action: \"silentBake\",\n            data: {\n                recipeConfig: recipeConfig\n            }\n        });\n    }\n\n    /**\n     * Handler for messages sent back from DishWorker\n     *\n     * @param {MessageEvent} e\n     */\n    handleDishMessage(e) {\n        const r = e.data;\n        log.debug(`Receiving '${r.action}' from DishWorker`);\n\n        switch (r.action) {\n            case \"dishReturned\":\n                this.dishWorker.currentAction = \"\";\n                this.callbacks[r.data.id](r.data);\n\n                if (this.dishWorkerQueue.length > 0) {\n                    this.postDishMessage(this.dishWorkerQueue.splice(0, 1)[0]);\n                }\n\n                break;\n            default:\n                log.error(\"Unrecognised message from DishWorker\", e);\n                break;\n        }\n    }\n\n    /**\n     * Asks the DishWorker to return the dish as the specified type\n     *\n     * @param {Dish} dish\n     * @param {string} type\n     * @param {Function} callback\n     */\n    getDishAs(dish, type, callback) {\n        const id = this.callbackID++;\n        this.callbacks[id] = callback;\n        if (this.dishWorker.worker === null) this.setupDishWorker();\n\n        this.postDishMessage({\n            action: \"getDishAs\",\n            data: {\n                dish: dish,\n                type: type,\n                id: id\n            }\n        });\n    }\n\n    /**\n     * Asks the DishWorker to get the title of the dish\n     *\n     * @param {Dish} dish\n     * @param {number} maxLength\n     * @param {Function} callback\n     * @returns {string}\n     */\n    getDishTitle(dish, maxLength, callback) {\n        const id = this.callbackID++;\n        this.callbacks[id] = callback;\n        if (this.dishWorker.worker === null) this.setupDishWorker();\n\n        this.postDishMessage({\n            action: \"getDishTitle\",\n            data: {\n                dish: dish,\n                maxLength: maxLength,\n                id: id\n            }\n        });\n    }\n\n    /**\n     * Asks the DishWorker to translate a buffer into a specific character encoding\n     *\n     * @param {ArrayBuffer} buffer\n     * @param {number} encoding\n     * @param {Function} callback\n     * @returns {string}\n     */\n    bufferToStr(buffer, encoding, callback) {\n        const id = this.callbackID++;\n        this.callbacks[id] = callback;\n        if (this.dishWorker.worker === null) this.setupDishWorker();\n\n        this.postDishMessage({\n            action: \"bufferToStr\",\n            data: {\n                buffer: buffer,\n                encoding: encoding,\n                id: id\n            }\n        });\n    }\n\n    /**\n     * Queues a message to be sent to the dishWorker\n     *\n     * @param {object} message\n     * @param {string} message.action\n     * @param {object} message.data\n     * @param {Dish} message.data.dish\n     * @param {number} message.data.id\n     */\n    queueDishMessage(message) {\n        if (message.action === \"getDishAs\") {\n            this.dishWorkerQueue = [message].concat(this.dishWorkerQueue);\n        } else {\n            this.dishWorkerQueue.push(message);\n        }\n    }\n\n    /**\n     * Sends a message to the DishWorker\n     *\n     * @param {object} message\n     * @param {string} message.action\n     * @param {object} message.data\n     */\n    postDishMessage(message) {\n        if (this.dishWorker.currentAction !== \"\") {\n            this.queueDishMessage(message);\n        } else {\n            this.dishWorker.currentAction = message.action;\n            this.dishWorker.worker.postMessage(message);\n        }\n    }\n\n    /**\n     * Sets the console log level in the workers.\n     */\n    setLogLevel() {\n        this.chefWorkers.forEach(cw => {\n            cw.worker.postMessage({\n                action: \"setLogLevel\",\n                data: log.getLevel()\n            });\n        });\n\n        if (!this.dishWorker.worker) return;\n        this.dishWorker.worker.postMessage({\n            action: \"setLogLevel\",\n            data: log.getLevel()\n        });\n    }\n\n    /**\n     * Display the bake progress in the output bar and bake button\n     */\n    displayProgress() {\n        const progress = this.getBakeProgress();\n        if (progress.total === progress.baked) return;\n\n        const percentComplete = ((progress.pending + progress.baking) / progress.total) * 100;\n        const bakeButton = document.getElementById(\"bake\");\n        if (this.app.baking) {\n            if (percentComplete < 100) {\n                bakeButton.style.background = `linear-gradient(to left, #fea79a ${percentComplete}%, #f44336 ${percentComplete}%)`;\n            } else {\n                bakeButton.style.background = \"\";\n            }\n        } else {\n            // not baking\n            bakeButton.style.background = \"\";\n        }\n\n        const bakeInfo = document.getElementById(\"bake-info\");\n        if (progress.total > 1) {\n            let width = progress.total.toLocaleString().length;\n            width = width < 2 ? 2 : width;\n\n            const totalStr = progress.total.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n            const bakedStr = progress.baked.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n            const pendingStr = progress.pending.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n            const bakingStr = progress.baking.toLocaleString().padStart(width, \" \").replace(/ /g, \"&nbsp;\");\n\n            let msg = \"total: \" + totalStr;\n            msg += \"<br>baked: \" + bakedStr;\n\n            if (progress.pending > 0) {\n                msg += \"<br>pending: \" + pendingStr;\n            } else if (progress.baking > 0) {\n                msg += \"<br>baking: \" + bakingStr;\n            }\n            bakeInfo.innerHTML = msg;\n            bakeInfo.style.display = \"\";\n        } else {\n            bakeInfo.style.display = \"none\";\n        }\n\n        if (progress.total !== progress.baked) {\n            setTimeout(function() {\n                this.displayProgress();\n            }.bind(this), 100);\n        }\n\n    }\n\n    /**\n     * Asks the ChefWorker to calculate highlight offsets if possible.\n     *\n     * @param {Object[]} recipeConfig\n     * @param {string} direction\n     * @param {Object[]} pos - The position object for the highlight.\n     * @param {number} pos.start - The start offset.\n     * @param {number} pos.end - The end offset.\n     */\n    highlight(recipeConfig, direction, pos) {\n        let workerIdx = this.getInactiveChefWorker(false);\n        if (workerIdx === -1) {\n            workerIdx = this.addChefWorker();\n        }\n        if (workerIdx === -1) return;\n        this.chefWorkers[workerIdx].worker.postMessage({\n            action: \"highlight\",\n            data: {\n                recipeConfig: recipeConfig,\n                direction: direction,\n                pos: pos\n            }\n        });\n    }\n}\n\nexport default WorkerWaiter;\n"
  },
  {
    "path": "src/web/workers/DishWorker.mjs",
    "content": "/**\n * Web worker to handle dish conversion operations.\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Dish from \"../../core/Dish.mjs\";\nimport DishError from \"../../core/errors/DishError.mjs\";\nimport { CHR_ENC_SIMPLE_REVERSE_LOOKUP } from \"../../core/lib/ChrEnc.mjs\";\nimport Utils from \"../../core/Utils.mjs\";\nimport cptable from \"codepage\";\nimport loglevelMessagePrefix from \"loglevel-message-prefix\";\n\nloglevelMessagePrefix(log, {\n    prefixes: [],\n    staticPrefixes: [\"DishWorker\"]\n});\n\nself.addEventListener(\"message\", function(e) {\n    // Handle message from the main thread\n    const r = e.data;\n    log.debug(`Receiving command '${r.action}'`);\n\n    switch (r.action) {\n        case \"getDishAs\":\n            getDishAs(r.data);\n            break;\n        case \"getDishTitle\":\n            getDishTitle(r.data);\n            break;\n        case \"bufferToStr\":\n            bufferToStr(r.data);\n            break;\n        case \"setLogLevel\":\n            log.setLevel(r.data, false);\n            break;\n        default:\n            log.error(`Unknown action: '${r.action}'`);\n    }\n});\n\n/**\n * Translates the dish to a given type\n *\n * @param {object} data\n * @param {Dish} data.dish\n * @param {string} data.type\n * @param {number} data.id\n */\nasync function getDishAs(data) {\n    const newDish = new Dish(data.dish),\n        value = await newDish.get(data.type),\n        transferable = (data.type === \"ArrayBuffer\") ? [value] : undefined;\n\n    self.postMessage({\n        action: \"dishReturned\",\n        data: {\n            value: value,\n            id: data.id\n        }\n    }, transferable);\n}\n\n/**\n * Gets the title of the given dish\n *\n * @param {object} data\n * @param {Dish} data.dish\n * @param {number} data.id\n * @param {number} data.maxLength\n */\nasync function getDishTitle(data) {\n    const newDish = new Dish(data.dish),\n        title = await newDish.getTitle(data.maxLength);\n\n    self.postMessage({\n        action: \"dishReturned\",\n        data: {\n            value: title,\n            id: data.id\n        }\n    });\n}\n\n/**\n * Translates a buffer to a string using a specified encoding\n *\n * @param {object} data\n * @param {ArrayBuffer} data.buffer\n * @param {number} data.id\n * @param {number} data.encoding\n */\nasync function bufferToStr(data) {\n    let str;\n    if (data.encoding === 0) {\n        str = Utils.arrayBufferToStr(data.buffer);\n    } else {\n        try {\n            str = cptable.utils.decode(data.encoding, new Uint8Array(data.buffer));\n        } catch (err) {\n            str = new DishError(`Error decoding buffer with encoding ${CHR_ENC_SIMPLE_REVERSE_LOOKUP[data.encoding]}: ${err.message}`).toString();\n        }\n    }\n\n    self.postMessage({\n        action: \"dishReturned\",\n        data: {\n            value: str,\n            id: data.id\n        }\n    });\n}\n"
  },
  {
    "path": "src/web/workers/InputWorker.mjs",
    "content": "/**\n * Web worker to handle the inputs.\n * Handles storage, modification and retrieval of the inputs.\n *\n * @author j433866 [j433866@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport Utils from \"../../core/Utils.mjs\";\nimport loglevelMessagePrefix from \"loglevel-message-prefix\";\n\nloglevelMessagePrefix(log, {\n    prefixes: [],\n    staticPrefixes: [\"InputWorker\"]\n});\n\n// Default max values\n// These will be correctly calculated automatically\nself.maxWorkers = 4;\nself.maxTabs = 1;\n\n/**\n * Dictionary of inputs keyed on the inputNum\n * Each entry is an object with the following type:\n * @typedef {Object} Input\n * @property {string} type\n * @property {ArrayBuffer} buffer\n * @property {string} stringSample\n * @property {Object} file\n *     @property {string} file.name\n *     @property {number} file.size\n *     @property {string} file.type\n * @property {string} status\n * @property {number} progress\n * @property {number} encoding\n * @property {string} eolSequence\n */\nself.inputs = {};\nself.loaderWorkers = [];\nself.pendingFiles = [];\nself.currentInputNum = 1;\nself.numInputs = 0;\nself.pendingInputs = 0;\nself.loadingInputs = 0;\n\n/**\n * Respond to message from parent thread.\n *\n * @param {MessageEvent} e\n */\nself.addEventListener(\"message\", function(e) {\n    const r = e.data;\n    if (!(\"action\" in r)) {\n        log.error(\"No action\");\n        return;\n    }\n\n    log.debug(`Receiving command '${r.action}'`);\n\n    switch (r.action) {\n        case \"loadUIFiles\":\n            self.loadFiles(r.data);\n            break;\n        case \"loaderWorkerReady\":\n            self.loaderWorkerReady(r.data);\n            break;\n        case \"updateMaxWorkers\":\n            self.maxWorkers = r.data;\n            break;\n        case \"updateMaxTabs\":\n            self.updateMaxTabs(r.data.maxTabs, r.data.activeTab);\n            break;\n        case \"updateInputValue\":\n            self.updateInputValue(r.data);\n            break;\n        case \"updateInputProgress\":\n            self.updateInputProgress(r.data);\n            break;\n        case \"bakeAll\":\n            self.bakeAllInputs();\n            break;\n        case \"bakeNext\":\n            self.bakeInput(r.data.inputNum, r.data.bakeId);\n            break;\n        case \"getLoadProgress\":\n            self.getLoadProgress(r.data);\n            break;\n        case \"setInput\":\n            self.setInput(r.data);\n            break;\n        case \"setLogLevel\":\n            log.setLevel(r.data, false);\n            break;\n        case \"addInput\":\n            self.addInput(r.data, \"userinput\");\n            break;\n        case \"refreshTabs\":\n            self.refreshTabs(r.data.inputNum, r.data.direction);\n            break;\n        case \"removeInput\":\n            self.removeInput(r.data);\n            break;\n        case \"changeTabRight\":\n            self.changeTabRight(r.data.activeTab);\n            break;\n        case \"changeTabLeft\":\n            self.changeTabLeft(r.data.activeTab);\n            break;\n        case \"autobake\":\n            self.autoBake(r.data.activeTab, 0, false);\n            break;\n        case \"filterTabs\":\n            self.filterTabs(r.data);\n            break;\n        case \"loaderWorkerMessage\":\n            self.handleLoaderMessage(r.data);\n            break;\n        case \"updateTabHeader\":\n            self.updateTabHeader(r.data);\n            break;\n        case \"step\":\n            self.autoBake(r.data.activeTab, r.data.progress, true);\n            break;\n        case \"getInput\":\n            self.getInput(r.data);\n            break;\n        case \"getInputNums\":\n            self.getInputNums(r.data);\n            break;\n        default:\n            log.error(`Unknown action '${r.action}'.`);\n    }\n});\n\n/**\n * Gets the load progress of the input files, and the\n * load progress for the input given in inputNum\n *\n * @param {number} inputNum - The input to get the file loading progress for\n */\nself.getLoadProgress = function(inputNum) {\n    const total = self.numInputs;\n    const pending = self.pendingFiles.length;\n    const loading = self.loadingInputs;\n    const loaded = total - pending - loading;\n\n    self.postMessage({\n        action: \"loadingInfo\",\n        data: {\n            pending: pending,\n            loading: loading,\n            loaded: loaded,\n            total: total,\n            activeProgress: {\n                inputNum: inputNum,\n                progress: self.getInputProgress(inputNum)\n            }\n        }\n    });\n};\n\n/**\n * Fired when an autobake is initiated.\n * Queues the active input and sends a bake command.\n *\n * @param {number} inputNum - The input to be baked\n * @param {number} progress - The current progress of the bake through the recipe\n * @param {boolean} [step=false] - Set to true if we should only execute one operation instead of the\n * whole recipe\n */\nself.autoBake = function(inputNum, progress, step=false) {\n    const input = self.inputs[inputNum];\n    if (input) {\n        self.postMessage({\n            action: \"bakeInputs\",\n            data: {\n                nums: [parseInt(inputNum, 10)],\n                step: step,\n                progress: progress\n            }\n        });\n    }\n};\n\n/**\n * Fired when we want to bake all inputs (bake button clicked)\n * Sends a list of inputNums to the workerwaiter\n */\nself.bakeAllInputs = function() {\n    const inputNums = Object.keys(self.inputs);\n\n    const nums = inputNums\n        .filter(n => self.inputs[n].status === \"loaded\")\n        .map(n => parseInt(n, 10));\n\n    self.postMessage({\n        action: \"bakeInputs\",\n        data: {\n            nums: nums,\n            step: false,\n            progress: 0\n        }\n    });\n};\n\n/**\n * Gets the data for the provided inputNum and sends it to the WorkerWaiter\n *\n * @param {number} inputNum\n * @param {number} bakeId\n */\nself.bakeInput = function(inputNum, bakeId) {\n    const inputObj = self.inputs[inputNum];\n    if (inputObj === null ||\n        inputObj === undefined ||\n        inputObj.status !== \"loaded\") {\n\n        self.postMessage({\n            action: \"queueInputError\",\n            data: {\n                inputNum: inputNum,\n                bakeId: bakeId\n            }\n        });\n        return;\n    }\n\n    self.postMessage({\n        action: \"queueInput\",\n        data: {\n            input: inputObj.buffer,\n            inputNum: inputNum,\n            bakeId: bakeId\n        }\n    });\n};\n\n/**\n * Gets the stored value or object for a specific inputNum and sends it to the inputWaiter.\n *\n * @param {object} inputData - Object containing data about the input to retrieve\n * @param {number} inputData.inputNum - The inputNum of the input to get\n * @param {boolean} inputData.getObj - If true, returns the entire input object instead of just the value\n * @param {number} inputData.id - The callback ID for the callback to run when returned to the inputWaiter\n */\nself.getInput = function(inputData) {\n    const input = self.inputs[inputData.inputNum];\n    self.postMessage({\n        action: \"getInput\",\n        data: {\n            data: inputData.getObj ? input : input.buffer,\n            id: inputData.id\n        }\n    });\n};\n\n/**\n * Gets a list of the stored inputNums, along with the minimum and maximum\n *\n * @param {number} id - The callback ID to be executed when returned to the inputWaiter\n */\nself.getInputNums = function(id) {\n    const inputNums = Object.keys(self.inputs),\n        min = self.getSmallestInputNum(inputNums),\n        max = self.getLargestInputNum(inputNums);\n\n    self.postMessage({\n        action: \"getInputNums\",\n        data: {\n            inputNums: inputNums,\n            min: min,\n            max: max,\n            id: id\n        }\n    });\n};\n\n/**\n * Gets the load progress for a specific inputNum\n *\n * @param {number} inputNum - The input we want to get the progress of\n * @returns {number | string} - Returns \"error\" if there was a load error\n */\nself.getInputProgress = function(inputNum) {\n    const inputObj = self.inputs[inputNum];\n    if (!inputObj) return;\n    if (inputObj.status === \"error\") {\n        return \"error\";\n    }\n    return inputObj.progress;\n};\n\n/**\n * Gets the largest inputNum of all the inputs\n *\n * @param {string[]} inputNums - The numbers to find the largest of\n * @returns {number}\n */\nself.getLargestInputNum = function(inputNums) {\n    return inputNums.reduce((acc, val) => {\n        val = parseInt(val, 10);\n        return val > acc ? val : acc;\n    }, -1);\n};\n\n/**\n * Gets the smallest inputNum of all the inputs\n *\n * @param {string[]} inputNums - The numbers to find the smallest of\n * @returns {number}\n */\nself.getSmallestInputNum = function(inputNums) {\n    const min = inputNums.reduce((acc, val) => {\n        val = parseInt(val, 10);\n        return val < acc ? val : acc;\n    }, Number.MAX_SAFE_INTEGER);\n\n    // Assume we don't have this many tabs!\n    if (min === Number.MAX_SAFE_INTEGER) return -1;\n\n    return min;\n};\n\n/**\n * Gets the next smallest inputNum\n *\n * @param {number} inputNum - The current input number\n * @returns {number}\n */\nself.getPreviousInputNum = function(inputNum) {\n    const inputNums = Object.keys(self.inputs);\n    if (inputNums.length === 0) return -1;\n\n    return inputNums.reduce((acc, val) => {\n        val = parseInt(val, 10);\n        return (val < inputNum && val > acc) ? val : acc;\n    }, self.getSmallestInputNum(inputNums));\n};\n\n/**\n * Gets the next largest inputNum\n *\n * @param {number} inputNum - The current input number\n * @returns {number}\n */\nself.getNextInputNum = function(inputNum) {\n    const inputNums = Object.keys(self.inputs);\n\n    return inputNums.reduce((acc, val) => {\n        val = parseInt(val, 10);\n        return (val > inputNum && val < acc) ? val : acc;\n    }, self.getLargestInputNum(inputNums));\n};\n\n/**\n * Gets a list of inputNums starting from the provided inputNum.\n * If direction is \"left\", gets the inputNums higher than the provided number.\n * If direction is \"right\", gets the inputNums lower than the provided number.\n * @param {number} inputNum - The inputNum we want to get the neighbours of\n * @param {string} direction - Either \"left\" or \"right\". Determines which direction we search for nearby numbers in\n * @returns {number[]}\n */\nself.getNearbyNums = function(inputNum, direction) {\n    const nums = [];\n    for (let i = 0; i < self.maxTabs; i++) {\n        let newNum;\n        if (i === 0 && self.inputs[inputNum] !== undefined) {\n            newNum = inputNum;\n        } else {\n            switch (direction) {\n                case \"left\":\n                    newNum = self.getNextInputNum(nums[i - 1]);\n                    if (newNum === nums[i - 1]) {\n                        direction = \"right\";\n                        newNum = self.getPreviousInputNum(nums[0]);\n                    }\n                    break;\n                case \"right\":\n                    newNum = self.getPreviousInputNum(nums[i - 1]);\n                    if (newNum === nums[i - 1]) {\n                        direction = \"left\";\n                        newNum = self.getNextInputNum(nums[0]);\n                    }\n            }\n        }\n        if (!nums.includes(newNum) && (newNum > 0)) {\n            nums.push(newNum);\n        }\n    }\n    nums.sort(function(a, b) {\n        return a - b;\n    });\n    return nums;\n};\n\n/**\n * Gets the data to display in the tab header for an input, and\n * posts it back to the inputWaiter\n *\n * @param {number} inputNum - The inputNum of the tab header\n */\nself.updateTabHeader = function(inputNum) {\n    const input = self.inputs[inputNum];\n    if (!input) return;\n\n    let header = input.type === \"file\" ? input.file.name : input.stringSample;\n    header = header.slice(0, 100).replace(/[\\n\\r\\u2028\\u2029]/g, \"\");\n\n    self.postMessage({\n        action: \"updateTabHeader\",\n        data: {\n            inputNum: inputNum,\n            input: header\n        }\n    });\n};\n\n/**\n * Gets the input for a specific inputNum, and posts it to the inputWaiter\n * so that it can be displayed in the input area\n *\n * @param {object} inputData\n * @param {number} inputData.inputNum - The input to get the data for\n * @param {boolean} inputData.silent - If false, the manager statechange event will be fired\n */\nself.setInput = function(inputData) {\n    const {inputNum, silent} = inputData;\n    const input = self.inputs[inputNum];\n    if (!input) return;\n\n    self.postMessage({\n        action: \"setInput\",\n        data: {\n            inputNum: inputNum,\n            inputObj: input,\n            silent: silent\n        }\n    });\n\n    self.updateTabHeader(inputNum);\n};\n\n/**\n * Gets the nearby inputNums to the provided number, and posts them\n * to the inputWaiter to be displayed on the page.\n *\n * @param {number} inputNum - The inputNum to find the nearby numbers for\n * @param {string} direction - The direction to search for inputNums in. Either \"left\" or \"right\"\n */\nself.refreshTabs = function(inputNum, direction) {\n    const nums = self.getNearbyNums(inputNum, direction),\n        inputNums = Object.keys(self.inputs),\n        tabsLeft = (self.getSmallestInputNum(inputNums) !== nums[0] && nums.length > 0),\n        tabsRight = (self.getLargestInputNum(inputNums) !== nums[nums.length - 1] && nums.length > 0);\n\n    self.postMessage({\n        action: \"refreshTabs\",\n        data: {\n            nums: nums,\n            activeTab: (nums.includes(inputNum)) ? inputNum : self.getNextInputNum(inputNum),\n            tabsLeft: tabsLeft,\n            tabsRight: tabsRight\n        }\n    });\n\n    // Update the tab headers for the new tabs\n    for (let i = 0; i < nums.length; i++) {\n        self.updateTabHeader(nums[i]);\n    }\n};\n\n/**\n * Update the stored status for an input\n *\n * @param {number} inputNum - The input that's having its status changed\n * @param {string} status - The status of the input\n */\nself.updateInputStatus = function(inputNum, status) {\n    if (self.inputs[inputNum] !== undefined) {\n        self.inputs[inputNum].status = status;\n    }\n};\n\n/**\n * Update the stored load progress of an input\n *\n * @param {object} inputData\n * @param {number} inputData.inputNum - The input that's having its progress updated\n * @param {number} inputData.progress - The load progress of the input\n */\nself.updateInputProgress = function(inputData) {\n    const {inputNum, progress} = inputData;\n\n    if (self.inputs[inputNum] !== undefined) {\n        self.inputs[inputNum].progress = progress;\n    }\n};\n\n/**\n * Update the stored value of an input.\n *\n * @param {object} inputData\n * @param {number} inputData.inputNum - The input that's having its value updated\n * @param {ArrayBuffer} inputData.buffer - The new value of the input as a buffer\n * @param {number} [inputData.encoding] - The character encoding of the input data\n * @param {string} [inputData.eolSequence] - The end of line sequence of the input data\n * @param {string} [inputData.stringSample] - A sample of the value as a string (truncated to 4096 chars)\n */\nself.updateInputValue = function(inputData) {\n    const inputNum = parseInt(inputData.inputNum, 10);\n    if (inputNum < 1) return;\n\n    if (!Object.prototype.hasOwnProperty.call(self.inputs, inputNum))\n        throw new Error(`No input with ID ${inputNum} exists`);\n\n    self.inputs[inputNum].buffer = inputData.buffer;\n    if (\"encoding\" in inputData) {\n        self.inputs[inputNum].encoding = inputData.encoding;\n    }\n    if (\"eolSequence\" in inputData) {\n        self.inputs[inputNum].eolSequence = inputData.eolSequence;\n    }\n    if (!(\"stringSample\" in inputData)) {\n        inputData.stringSample = Utils.arrayBufferToStr(inputData.buffer.slice(0, 4096));\n    }\n    self.inputs[inputNum].stringSample = inputData.stringSample;\n    self.inputs[inputNum].status = \"loaded\";\n    self.inputs[inputNum].progress = 100;\n};\n\n/**\n * Get the index of a loader worker object.\n * Returns -1 if the worker could not be found\n *\n * @param {number} workerId - The ID of the worker we're searching for\n * @returns {number}\n */\nself.getLoaderWorkerIdx = function(workerId) {\n    for (let i = 0; i < self.loaderWorkers.length; i++) {\n        if (self.loaderWorkers[i].id === workerId) {\n            return i;\n        }\n    }\n    return -1;\n};\n\n/**\n * Fires when a loaderWorker is ready to load files.\n * Stores data about the new loaderWorker in the loaderWorkers array,\n * and sends the next file to the loaderWorker to be loaded.\n *\n * @param {object} workerData\n * @param {number} workerData.id - The ID of the new loaderWorker\n */\nself.loaderWorkerReady = function(workerData) {\n    const newWorkerObj = {\n        id: workerData.id,\n        inputNum: -1,\n        active: true\n    };\n    self.loaderWorkers.push(newWorkerObj);\n    self.loadNextFile(self.loaderWorkers.indexOf(newWorkerObj));\n};\n\n/**\n * Handler for messages sent by loaderWorkers.\n * (Messages are sent between the inputWorker and loaderWorkers via the main thread)\n *\n * @param {object} r - The data sent by the loaderWorker\n * @param {number} r.inputNum - The inputNum which the message corresponds to\n * @param {string} r.error - Present if an error is fired by the loaderWorker. Contains the error message string.\n * @param {ArrayBuffer} r.fileBuffer - Present if a file has finished loading. Contains the loaded file buffer.\n */\nself.handleLoaderMessage = function(r) {\n    let inputNum = 0;\n\n    if (\"inputNum\" in r) {\n        inputNum = r.inputNum;\n    }\n\n    if (\"error\" in r) {\n        self.updateInputProgress(r.inputNum, 0);\n        self.updateInputStatus(r.inputNum, \"error\");\n\n        log.error(r.error);\n        self.loadingInputs--;\n\n        self.terminateLoaderWorker(r.id);\n        self.activateLoaderWorker();\n\n        self.setInput({inputNum: inputNum, silent: true});\n        return;\n    }\n\n    if (\"fileBuffer\" in r) {\n        log.debug(`Input file ${inputNum} loaded.`);\n        self.loadingInputs--;\n\n        self.updateInputValue({\n            inputNum: inputNum,\n            buffer: r.fileBuffer\n        });\n\n        self.postMessage({\n            action: \"fileLoaded\",\n            data: {\n                inputNum: inputNum\n            }\n        });\n\n        const idx = self.getLoaderWorkerIdx(r.id);\n        self.loadNextFile(idx);\n    } else if (\"progress\" in r) {\n        self.updateInputProgress(r);\n    }\n};\n\n/**\n * Loads the next file using a loaderWorker\n *\n * @param {number} - The loaderWorker which will load the file\n */\nself.loadNextFile = function(workerIdx) {\n    if (workerIdx === -1) return;\n    const id = self.loaderWorkers[workerIdx].id;\n    if (self.pendingFiles.length === 0) {\n        const workerObj = self.loaderWorkers.splice(workerIdx, 1)[0];\n        self.terminateLoaderWorker(workerObj.id);\n        return;\n    }\n\n    const nextFile = self.pendingFiles.splice(0, 1)[0];\n    self.loaderWorkers[workerIdx].inputNum = nextFile.inputNum;\n    self.loadingInputs++;\n    self.postMessage({\n        action: \"loadInput\",\n        data: {\n            file: nextFile.file,\n            inputNum: nextFile.inputNum,\n            workerId: id\n        }\n    });\n};\n\n/**\n * Sends a message to the inputWaiter to create a new loaderWorker.\n * If there's an inactive loaderWorker that already exists, use that instead.\n */\nself.activateLoaderWorker = function() {\n    for (let i = 0; i < self.loaderWorkers.length; i++) {\n        if (!self.loaderWorkers[i].active) {\n            self.loaderWorkers[i].active = true;\n            self.loadNextFile(i);\n            return;\n        }\n    }\n    self.postMessage({\n        action: \"activateLoaderWorker\"\n    });\n};\n\n/**\n * Sends a message to the inputWaiter to terminate a loaderWorker.\n *\n * @param {number} id - The ID of the worker to be terminated\n */\nself.terminateLoaderWorker = function(id) {\n    self.postMessage({\n        action: \"terminateLoaderWorker\",\n        data: id\n    });\n    // If we still have pending files, spawn a worker\n    if (self.pendingFiles.length > 0) {\n        self.activateLoaderWorker();\n    }\n};\n\n/**\n * Loads files using LoaderWorkers\n *\n * @param {object} filesData\n * @param {FileList} filesData.files - The list of files to be loaded\n * @param {number} filesData.activeTab - The active tab in the UI\n */\nself.loadFiles = function(filesData) {\n    const {files, activeTab} = filesData;\n    let lastInputNum = -1;\n    const inputNums = [];\n    for (let i = 0; i < files.length; i++) {\n        // If the first input is empty, replace it rather than adding a new one\n        if (i === 0 && (!self.inputs[activeTab].buffer || self.inputs[activeTab].buffer.byteLength === 0)) {\n            self.removeInput({\n                inputNum: activeTab,\n                refreshTabs: false,\n                removeChefWorker: false\n            });\n            lastInputNum = self.addInput(false, \"file\", {\n                name: files[i].name,\n                size: files[i].size.toLocaleString(),\n                type: files[i].type || \"unknown\"\n            }, activeTab);\n        } else {\n            lastInputNum = self.addInput(false, \"file\", {\n                name: files[i].name,\n                size: files[i].size.toLocaleString(),\n                type: files[i].type || \"unknown\"\n            });\n        }\n        inputNums.push(lastInputNum);\n\n        self.pendingFiles.push({\n            file: files[i],\n            inputNum: lastInputNum\n        });\n    }\n    let max = self.maxWorkers;\n    if (self.pendingFiles.length < self.maxWorkers) max = self.pendingFiles.length;\n\n    // Create loaderWorkers to load the new files\n    for (let i = 0; i < max; i++) {\n        self.activateLoaderWorker();\n    }\n\n    self.getLoadProgress();\n    self.setInput({inputNum: lastInputNum, silent: true});\n};\n\n/**\n * Adds an input to the input dictionary\n *\n * @param {boolean} [changetab=false] - Whether or not to change to the new input\n * @param {string} type - Either \"userinput\" or \"file\"\n * @param {Object} fileData - Contains information about the file to be added to the input (only used when type is \"file\")\n * @param {string} fileData.name - The filename of the input being added\n * @param {number} fileData.size - The file size (in bytes) of the input being added\n * @param {string} fileData.type - The MIME type of the input being added\n * @param {number} inputNum - Defaults to auto-incrementing self.currentInputNum\n */\nself.addInput = function(\n    changeTab = false,\n    type,\n    fileData = {\n        name: \"unknown\",\n        size: 0,\n        type: \"unknown\"\n    },\n    inputNum = self.currentInputNum++\n) {\n    self.numInputs++;\n    const newInputObj = {\n        type: null,\n        buffer: new ArrayBuffer(),\n        stringSample: \"\",\n        file: null,\n        status: \"pending\",\n        progress: 0,\n        encoding: 0,\n        eolSequence: \"\\u000a\"\n    };\n\n    switch (type) {\n        case \"userinput\":\n            newInputObj.type = \"userinput\";\n            newInputObj.status = \"loaded\";\n            newInputObj.progress = 100;\n            break;\n        case \"file\":\n            newInputObj.type = \"file\";\n            newInputObj.file = {\n                name: fileData.name,\n                size: fileData.size,\n                type: fileData.type\n            };\n            newInputObj.status = \"pending\";\n            newInputObj.progress = 0;\n            break;\n        default:\n            log.error(`Invalid input type '${type}'.`);\n            return -1;\n    }\n    self.inputs[inputNum] = newInputObj;\n\n    // Tell the inputWaiter we've added an input, so it can create a tab to display it\n    self.postMessage({\n        action: \"inputAdded\",\n        data: {\n            changeTab: changeTab,\n            inputNum: inputNum\n        }\n    });\n\n    return inputNum;\n};\n\n/**\n * Remove an input from the inputs dictionary\n *\n * @param {object} removeInputData\n * @param {number} removeInputData.inputNum - The number of the input to be removed\n * @param {boolean} removeInputData.refreshTabs - If true, refresh the tabs after removing the input\n * @param {boolean} removeInputData.removeChefWorker - If true, remove a chefWorker from the WorkerWaiter\n */\nself.removeInput = function(removeInputData) {\n    const inputNum = removeInputData.inputNum;\n    const refreshTabs = removeInputData.refreshTabs;\n    self.numInputs--;\n\n    for (let i = 0; i < self.loaderWorkers.length; i++) {\n        if (self.loaderWorkers[i].inputNum === inputNum) {\n            // Terminate any loaderWorker that's loading the removed input\n            self.loadingInputs--;\n            self.terminateLoaderWorker(self.loaderWorkers[i].id);\n            break;\n        }\n    }\n\n    for (let i = 0; i < self.pendingFiles.length; i++) {\n        // Remove the input from the pending files list\n        if (self.pendingFiles[i].inputNum === inputNum) {\n            self.pendingFiles.splice(i, 1);\n            break;\n        }\n    }\n\n    delete self.inputs[inputNum];\n\n    if (refreshTabs) {\n        self.refreshTabs(self.getPreviousInputNum(inputNum), \"left\");\n    }\n\n    if (self.numInputs < self.maxWorkers && removeInputData.removeChefWorker) {\n        self.postMessage({\n            action: \"removeChefWorker\"\n        });\n    }\n};\n\n/**\n * Change to the next tab.\n *\n * @param {number} inputNum - The inputNum of the tab to change to\n */\nself.changeTabRight = function(inputNum) {\n    const newInput = self.getNextInputNum(inputNum);\n    self.postMessage({\n        action: \"changeTab\",\n        data: newInput\n    });\n};\n\n/**\n * Change to the previous tab.\n *\n * @param {number} inputNum - The inputNum of the tab to change to\n */\nself.changeTabLeft = function(inputNum) {\n    const newInput = self.getPreviousInputNum(inputNum);\n    self.postMessage({\n        action: \"changeTab\",\n        data: newInput\n    });\n};\n\n/**\n * Updates the maximum number of tabs, and refreshes them if it changes\n *\n * @param {number} maxTabs - The new max number of tabs\n * @param {number} activeTab - The currently selected tab\n */\nself.updateMaxTabs = function(maxTabs, activeTab) {\n    if (self.maxTabs !== maxTabs) {\n        self.maxTabs = maxTabs;\n        self.refreshTabs(activeTab, \"right\");\n    }\n};\n\n/**\n * Search the inputs for any that match the filters provided,\n * posting the results back to the inputWaiter\n *\n * @param {object} searchData - Object containing the search filters\n * @param {boolean} searchData.showPending - If true, include pending inputs in the results\n * @param {boolean} searchData.showLoading - If true, include loading inputs in the results\n * @param {boolean} searchData.showLoaded - If true, include loaded inputs in the results\n * @param {string} searchData.filter - A regular expression to match the inputs on\n * @param {string} searchData.filterType - Either \"CONTENT\" or \"FILENAME\". Determines what should be matched with filter\n * @param {number} searchData.numResults - The maximum number of results to be returned\n */\nself.filterTabs = function(searchData) {\n    const showPending = searchData.showPending,\n        showLoading = searchData.showLoading,\n        showLoaded = searchData.showLoaded,\n        filterType = searchData.filterType;\n\n    let filterExp;\n    try {\n        filterExp = new RegExp(searchData.filter, \"i\");\n    } catch (error) {\n        self.postMessage({\n            action: \"filterTabError\",\n            data: error.message\n        });\n        return;\n    }\n    const numResults = searchData.numResults;\n\n    const inputs = [];\n    const inputNums = Object.keys(self.inputs);\n    for (let i = 0; i < inputNums.length; i++) {\n        const iNum = inputNums[i];\n        let textDisplay = \"\";\n        let addInput = false;\n        if (self.inputs[iNum].status === \"pending\" && showPending ||\n            self.inputs[iNum].status === \"loading\" && showLoading ||\n            self.inputs[iNum].status === \"loaded\" && showLoaded) {\n            try {\n                if (self.inputs[iNum].type === \"userinput\") {\n                    if (filterType.toLowerCase() === \"content\" &&\n                        filterExp.test(self.inputs[iNum].stringSample)) {\n                        textDisplay = self.inputs[iNum].stringSample;\n                        addInput = true;\n                    }\n                } else {\n                    if ((filterType.toLowerCase() === \"filename\" &&\n                        filterExp.test(self.inputs[iNum].file.name)) ||\n                        (filterType.toLowerCase() === \"content\" &&\n                        filterExp.test(self.inputs[iNum].stringSample))) {\n                        textDisplay = self.inputs[iNum].file.name;\n                        addInput = true;\n                    }\n                }\n            } catch (error) {\n                self.postMessage({\n                    action: \"filterTabError\",\n                    data: error.message\n                });\n                return;\n            }\n        }\n\n        if (addInput) {\n            if (textDisplay === \"\" || textDisplay === undefined) {\n                textDisplay = \"New Tab\";\n            }\n            const inputItem = {\n                inputNum: iNum,\n                textDisplay: textDisplay\n            };\n            inputs.push(inputItem);\n        }\n        if (inputs.length >= numResults) {\n            break;\n        }\n    }\n\n    // Send the results back to the inputWaiter\n    self.postMessage({\n        action: \"displayTabSearchResults\",\n        data: inputs\n    });\n};\n"
  },
  {
    "path": "src/web/workers/LoaderWorker.js",
    "content": "/**\n * Web Worker to load large amounts of data without locking up the UI.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport loglevelMessagePrefix from \"loglevel-message-prefix\";\n\nloglevelMessagePrefix(log, {\n    prefixes: [],\n    staticPrefixes: [\"LoaderWorker\"]\n});\n\nself.id = null;\n\n\n/**\n * Respond to message from parent thread.\n */\nself.addEventListener(\"message\", function(e) {\n    // Handle message\n    const r = e.data;\n    log.debug(`Receiving command '${r.action}'`);\n\n    switch (r.action) {\n        case \"setID\":\n            self.id = r.data.id;\n            break;\n        case \"loadFile\":\n            self.loadFile(r.data.file, r.data?.inputNum ?? \"\");\n            break;\n        case \"setLogLevel\":\n            log.setLevel(r.data, false);\n            break;\n        default:\n            log.error(`Unknown action '${r.action}'.`);\n    }\n});\n\n\n/**\n * Loads a file object into an ArrayBuffer, then transfers it back to the parent thread.\n *\n * @param {File} file\n * @param {string} inputNum\n */\nself.loadFile = function(file, inputNum) {\n    const reader = new FileReader();\n    if (file.size >= 256*256*256*128) {\n        self.postMessage({\"error\": \"File size too large.\", \"inputNum\": inputNum, \"id\": self.id});\n        return;\n    }\n    const data = new Uint8Array(file.size);\n    let offset = 0;\n    const CHUNK_SIZE = 10485760; // 10MiB\n\n    const seek = function() {\n        if (offset >= file.size) {\n            self.postMessage({\"fileBuffer\": data.buffer, \"inputNum\": inputNum, \"id\": self.id}, [data.buffer]);\n            return;\n        }\n        self.postMessage({\"progress\": Math.round(offset / file.size * 100), \"inputNum\": inputNum});\n        const slice = file.slice(offset, offset + CHUNK_SIZE);\n        reader.readAsArrayBuffer(slice);\n    };\n\n    reader.onload = function(e) {\n        data.set(new Uint8Array(reader.result), offset);\n        offset += CHUNK_SIZE;\n        seek();\n    };\n\n    reader.onerror = function(e) {\n        self.postMessage({\"error\": reader.error.message, \"inputNum\": inputNum, \"id\": self.id});\n    };\n\n    seek();\n};\n"
  },
  {
    "path": "src/web/workers/ZipWorker.mjs",
    "content": "/**\n * Web Worker to handle zipping the outputs for download.\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport zip from \"zlibjs/bin/zip.min.js\";\nimport Utils from \"../../core/Utils.mjs\";\nimport Dish from \"../../core/Dish.mjs\";\nimport {detectFileType} from \"../../core/lib/FileType.mjs\";\nimport loglevelMessagePrefix from \"loglevel-message-prefix\";\n\nloglevelMessagePrefix(log, {\n    prefixes: [],\n    staticPrefixes: [\"ZipWorker\"],\n});\n\nconst Zlib = zip.Zlib;\n\n/**\n * Respond to message from parent thread.\n */\nself.addEventListener(\"message\", function(e) {\n    // Handle message from the main thread\n    const r = e.data;\n    log.debug(`Receiving command '${r.action}'`);\n\n    switch (r.action) {\n        case \"zipFiles\":\n            self.zipFiles(r.data.outputs, r.data.filename, r.data.fileExtension);\n            break;\n        case \"setLogLevel\":\n            log.setLevel(r.data, false);\n            break;\n        default:\n            log.error(`Unknown action: '${r.action}'`);\n    }\n});\n\nself.setOption = function(...args) {};\n\n/**\n * Compress the files into a zip file and send the zip back\n * to the OutputWaiter.\n *\n * @param {object} outputs\n * @param {string} filename\n * @param {string} fileExtension\n */\nself.zipFiles = async function(outputs, filename, fileExtension) {\n    const zip = new Zlib.Zip();\n    const inputNums = Object.keys(outputs);\n\n    for (let i = 0; i < inputNums.length; i++) {\n        const iNum = inputNums[i];\n        let ext = fileExtension;\n\n        const cloned = new Dish(outputs[iNum].data.dish);\n        const output = new Uint8Array(await cloned.get(Dish.ARRAY_BUFFER));\n\n        if (fileExtension === undefined || fileExtension === \"\") {\n            // Detect automatically\n            const types = detectFileType(output);\n            if (!types.length) {\n                ext = \".dat\";\n            } else {\n                ext = `.${types[0].extension.split(\",\", 1)[0]}`;\n            }\n        }\n        const name = Utils.strToByteArray(iNum + ext);\n\n        zip.addFile(output, {filename: name});\n    }\n\n    const zippedFile = zip.compress();\n    self.postMessage({\n        zippedFile: zippedFile.buffer,\n        filename: filename\n    }, [zippedFile.buffer]);\n};\n"
  },
  {
    "path": "tests/browser/00_nightwatch.js",
    "content": "/**\n * Tests to ensure that the app loads correctly in a reasonable time and that operations can be run.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nconst utils = require(\"./browserUtils.js\");\n\nmodule.exports = {\n    before: browser => {\n        browser\n            .resizeWindow(1280, 800)\n            .url(browser.launchUrl);\n    },\n\n    \"Loading screen\": browser => {\n        // Check that the loading screen appears and then disappears within a reasonable time\n        browser\n            .waitForElementVisible(\"#preloader\", 300)\n            .waitForElementNotPresent(\"#preloader\", 10000);\n    },\n\n    \"App loaded\": browser => {\n        browser.useCss();\n        // Check that various important elements are loaded\n        browser.expect.element(\"#operations\").to.be.visible;\n        browser.expect.element(\"#recipe\").to.be.visible;\n        browser.expect.element(\"#input\").to.be.present;\n        browser.expect.element(\"#output\").to.be.present;\n        browser.expect.element(\".op-list\").to.be.present;\n        browser.expect.element(\"#rec-list\").to.be.visible;\n        browser.expect.element(\"#controls\").to.be.visible;\n        browser.expect.element(\"#input-text\").to.be.visible;\n        browser.expect.element(\"#output-text\").to.be.visible;\n    },\n\n    \"Operations loaded\": browser => {\n        browser.useXpath();\n        // Check that an operation in every category has been populated\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='To Base64']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='To Binary']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='AES Decrypt']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='PEM to Hex']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Power Set']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Parse IP range']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Remove Diacritics']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Sort']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='To UNIX Timestamp']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Extract dates']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Gzip']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Keccak']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='JSON Beautify']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Detect File Type']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Play Media']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Disassemble x86']\").to.be.present;\n        browser.expect.element(\"//li[contains(@class, 'operation') and text()='Register']\").to.be.present;\n    },\n\n    \"Recipe can be run\": browser => {\n        const toHex = \"//li[contains(@class, 'operation') and text()='To Hex']\";\n        const op = \"#rec-list .operation .op-title\";\n\n        // Check that operation is visible\n        browser\n            .useXpath()\n            .expect.element(toHex).to.be.visible;\n\n        // Add it to the recipe by double clicking\n        browser\n            .useXpath()\n            .moveToElement(toHex, 10, 10)\n            .useCss()\n            .waitForElementVisible(\".popover-body\", 1000)\n            .doubleClick(\"xpath\", toHex);\n\n        // Confirm that it has been added to the recipe\n        browser\n            .useCss()\n            .waitForElementVisible(op, 100)\n            .expect.element(op).text.to.contain(\"To Hex\");\n\n        // Enter input\n        browser\n            .useCss()\n            .sendKeys(\"#input-text .cm-content\", \"Don't Panic.\")\n            .pause(1000)\n            .click(\"#bake\");\n\n        // Check output\n        browser\n            .useCss()\n            .waitForElementNotVisible(\"#stale-indicator\", 1000)\n            .expect.element(\"#output-text .cm-content\").text.that.equals(\"44 6f 6e 27 74 20 50 61 6e 69 63 2e\");\n\n        // Clear recipe\n        browser\n            .useCss()\n            .moveToElement(op, 10, 10)\n            .waitForElementNotPresent(\".popover-body\", 1000)\n            .click(\"#clr-recipe\")\n            .waitForElementNotPresent(op);\n    },\n\n    \"Test every module\": browser => {\n        browser.useCss();\n\n        // BSON\n        loadOp(\"BSON deserialise\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Charts\n        loadOp(\"Entropy\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Ciphers\n        loadOp(\"AES Encrypt\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Code\n        loadOp(\"XPath expression\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Compression\n        loadOp(\"Gzip\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Crypto\n        loadOp(\"MD5\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Default\n        loadOp(\"Fork\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Diff\n        loadOp(\"Diff\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Encodings\n        loadOp(\"Encode text\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Hashing\n        loadOp(\"Streebog\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Image\n        loadOp(\"Extract EXIF\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // PGP\n        loadOp(\"PGP Encrypt\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // PublicKey\n        loadOp(\"Hex to PEM\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Regex\n        loadOp(\"Strings\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // Shellcode\n        loadOp(\"Disassemble x86\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // URL\n        loadOp(\"URL Encode\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // UserAgent\n        loadOp(\"Parse User Agent\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        // YARA\n        loadOp(\"YARA Rules\", browser)\n            .waitForElementNotVisible(\"#output-loader\", 5000);\n\n        browser.click(\"#clr-recipe\");\n    },\n\n    \"Move around the UI\": browser => {\n        const otherCat = \"//a[contains(@class, 'category-title') and contains(@data-target, '#catOther')]\",\n            genUUID = \"//li[contains(@class, 'operation') and text()='Generate UUID']\";\n\n        browser.useXpath();\n\n        // Scroll to a lower category\n        browser\n            .getLocationInView(otherCat)\n            .expect.element(otherCat).to.be.visible;\n\n        // Open category\n        browser\n            .useCss()\n            .waitForElementNotVisible(\"#snackbar-container\", 10000)\n            .useXpath()\n            .click(otherCat)\n            .expect.element(genUUID).to.be.visible;\n\n        // Add op to recipe\n        /* mouseButtonUp drops wherever the actual cursor is, not necessarily in the right place,\n        so we can't test Sortable.js properly using Nightwatch. html-dnd doesn't work either.\n        Instead of relying on drag and drop, we double click on the op to load it. */\n        browser\n            .getLocationInView(genUUID)\n            .moveToElement(genUUID, 10, 10)\n            .doubleClick(\"xpath\", genUUID)\n            .useCss()\n            .waitForElementVisible(\".operation .op-title\", 1000)\n            .waitForElementNotVisible(\"#stale-indicator\", 1000)\n            .expect.element(\"#output-text .cm-content\").text.which.matches(/[\\da-f-]{36}/);\n\n        browser.click(\"#clr-recipe\");\n    },\n\n    \"Search\": browser => {\n        // Search for an op\n        browser\n            .useCss()\n            .clearValue(\"#search\")\n            .setValue(\"#search\", \"md5\")\n            .useXpath()\n            .waitForElementVisible(\"//ul[@id='search-results']//b[text()='MD5']\", 1000);\n    },\n\n    \"Alert bar\": browser => {\n        // Bake nothing to create an empty output which can be copied\n        utils.clear(browser);\n        utils.bake(browser);\n\n        // Alert bar shows and contains correct content\n        browser\n            .waitForElementNotVisible(\"#snackbar-container\")\n            .click(\"#copy-output\")\n            .waitForElementVisible(\"#snackbar-container .snackbar-content\")\n            .expect.element(\"#snackbar-container .snackbar-content\").text.to.equal(\"Copied raw output successfully.\");\n\n        // Alert bar disappears after the correct amount of time\n        // Should disappear after 2000ms\n        browser\n            .waitForElementNotPresent(\"#snackbar-container .snackbar-content\", 2500)\n            .waitForElementNotVisible(\"#snackbar-container\");\n    },\n\n    after: browser => {\n        browser.end();\n    }\n};\n\n/**\n * Clears the current recipe and loads a new operation.\n *\n * @param {string} opName\n * @param {Browser} browser\n */\nfunction loadOp(opName, browser) {\n    return browser\n        .useCss()\n        .click(\"#clr-recipe\")\n        .urlHash(\"op=\" + opName);\n}\n"
  },
  {
    "path": "tests/browser/01_io.js",
    "content": "/**\n * Tests for input and output of various types to ensure the editors work as expected\n * and retain data integrity, especially when it comes to special characters.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\n// import {\n//     clear,\n//     utils.setInput,\n//     bake,\n//     setChrEnc,\n//     setEOLSeq,\n//     copy,\n//     paste,\n//     loadRecipe,\n//     expectOutput,\n//     uploadFile,\n//     uploadFolder\n// } from \"./browserUtils.js\";\n\nconst utils = require(\"./browserUtils.js\");\n\nconst SPECIAL_CHARS = [\n    \"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\u0008\\u0009\\u000a\\u000b\\u000c\\u000d\\u000e\\u000f\",\n    \"\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\",\n    \"\\u007f\",\n    \"\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f\",\n    \"\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097\\u0098\\u0099\\u009a\\u009b\\u009c\\u009d\\u009e\\u009f\",\n    \"\\u00ad\\u061c\\u200b\\u200e\\u200f\\u2028\\u2029\\u202d\\u202e\\u2066\\u2067\\u2069\\ufeff\\ufff9\\ufffa\\ufffb\\ufffc\"\n].join(\"\");\n\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nconst PUA_CHARS = \"\\ue000\\ue001\\uf8fe\\uf8ff\";\n\nconst MULTI_LINE_STRING =`\"You know,\" said Arthur, \"it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young.\"\n\"Why, what did she tell you?\"\n\"I don't know, I didn't listen.\"`;\n\nconst SELECTABLE_STRING = `ONE\ntwo\nONE\nthree\nONE\nfour\nONE`;\n\n// Descriptions for named control characters\nconst CONTROL_CHAR_NAMES = {\n    0: \"null\",\n    7: \"bell\",\n    8: \"backspace\",\n    10: \"line feed\",\n    11: \"vertical tab\",\n    13: \"carriage return\",\n    27: \"escape\",\n    8203: \"zero width space\",\n    8204: \"zero width non-joiner\",\n    8205: \"zero width joiner\",\n    8206: \"left-to-right mark\",\n    8207: \"right-to-left mark\",\n    8232: \"line separator\",\n    8237: \"left-to-right override\",\n    8238: \"right-to-left override\",\n    8294: \"left-to-right isolate\",\n    8295: \"right-to-left isolate\",\n    8297: \"pop directional isolate\",\n    8233: \"paragraph separator\",\n    65279: \"zero width no-break space\",\n    65532: \"object replacement\"\n};\n\nmodule.exports = {\n    before: browser => {\n        browser\n            .resizeWindow(1280, 800)\n            .url(browser.launchUrl)\n            .useCss()\n            .waitForElementNotPresent(\"#preloader\", 10000)\n            .click(\"#auto-bake-label\");\n    },\n\n    \"CodeMirror has loaded correctly\": browser => {\n        /* Editor has initialised */\n        browser\n            .useCss()\n            // Input\n            .waitForElementVisible(\"#input-text\")\n            .waitForElementVisible(\"#input-text .cm-editor\")\n            .waitForElementVisible(\"#input-text .cm-editor .cm-scroller\")\n            .waitForElementVisible(\"#input-text .cm-editor .cm-scroller .cm-content\")\n            .waitForElementVisible(\"#input-text .cm-editor .cm-scroller .cm-content .cm-line\")\n            // Output\n            .waitForElementVisible(\"#output-text\")\n            .waitForElementVisible(\"#output-text .cm-editor\")\n            .waitForElementVisible(\"#output-text .cm-editor .cm-scroller\")\n            .waitForElementVisible(\"#output-text .cm-editor .cm-scroller .cm-content\")\n            .waitForElementVisible(\"#output-text .cm-editor .cm-scroller .cm-content .cm-line\");\n\n        /* Status bar is showing and has correct values */\n        browser // Input\n            .waitForElementVisible(\"#input-text .cm-status-bar\")\n            .waitForElementVisible(\"#input-text .cm-status-bar .stats-length-value\")\n            .expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"0\");\n        browser.waitForElementVisible(\"#input-text .cm-status-bar .stats-lines-value\")\n            .expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n        browser.waitForElementVisible(\"#input-text .cm-status-bar .chr-enc-value\")\n            .expect.element(\"#input-text .cm-status-bar .chr-enc-value\").text.to.equal(\"Raw Bytes\");\n        browser.waitForElementVisible(\"#input-text .cm-status-bar .eol-value\")\n            .expect.element(\"#input-text .cm-status-bar .eol-value\").text.to.equal(\"LF\");\n\n        browser // Output\n            .waitForElementVisible(\"#output-text .cm-status-bar\")\n            .waitForElementVisible(\"#output-text .cm-status-bar .stats-length-value\")\n            .expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"0\");\n        browser.waitForElementVisible(\"#output-text .cm-status-bar .stats-lines-value\")\n            .expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n        browser.waitForElementVisible(\"#output-text .cm-status-bar .baking-time-info\")\n            .expect.element(\"#output-text .cm-status-bar .baking-time-info\").text.to.contain(\"ms\");\n        browser.waitForElementVisible(\"#output-text .cm-status-bar .chr-enc-value\")\n            .expect.element(\"#output-text .cm-status-bar .chr-enc-value\").text.to.equal(\"Raw Bytes\");\n        browser.waitForElementVisible(\"#output-text .cm-status-bar .eol-value\")\n            .expect.element(\"#output-text .cm-status-bar .eol-value\").text.to.equal(\"LF\");\n    },\n\n    \"Adding content\": browser => {\n        /* Status bar updates correctly */\n        utils.setInput(browser, MULTI_LINE_STRING);\n\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"3\");\n        browser.expect.element(\"#input-text .cm-status-bar .chr-enc-value\").text.to.equal(\"Raw Bytes\");\n        browser.expect.element(\"#input-text .cm-status-bar .eol-value\").text.to.equal(\"LF\");\n\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"0\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n        browser.expect.element(\"#output-text .cm-status-bar .baking-time-info\").text.to.contain(\"ms\");\n        browser.expect.element(\"#output-text .cm-status-bar .chr-enc-value\").text.to.equal(\"Raw Bytes\");\n        browser.expect.element(\"#output-text .cm-status-bar .eol-value\").text.to.equal(\"LF\");\n\n        /* Output updates correctly */\n        utils.bake(browser);\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"3\");\n        browser.expect.element(\"#output-text .cm-status-bar .baking-time-info\").text.to.contain(\"ms\");\n        browser.expect.element(\"#output-text .cm-status-bar .chr-enc-value\").text.to.equal(\"Raw Bytes\");\n        browser.expect.element(\"#output-text .cm-status-bar .eol-value\").text.to.equal(\"LF\");\n    },\n\n    \"Autobaking the latest input\": browser => {\n        // Use the sleep recipe to simulate a long running task\n        utils.loadRecipe(browser, \"Sleep\", \"input\", [2000]);\n\n        browser.waitForElementVisible(\"#stale-indicator\");\n\n        // Enable previously disabled autobake\n        browser.expect.element(\"#auto-bake\").to.not.be.selected;\n        browser.click(\"#auto-bake-label\");\n        browser.expect.element(\"#auto-bake\").to.be.selected.before(1000);\n\n        // Add content to the input\n        browser.pause(100);\n        browser.sendKeys(\"#input-text .cm-content\", \"1\");\n        browser.waitForElementVisible(\"#output-loader\");\n        browser.pause(500);\n\n        // Make another change while the previous input is being baked\n        browser\n            .sendKeys(\"#input-text .cm-content\", \"2\")\n            .waitForElementNotVisible(\"#stale-indicator\")\n            .waitForElementNotVisible(\"#output-loader\");\n\n        // Ensure we got the latest input baked\n        utils.expectOutput(browser, \"input12\");\n\n        // Turn autobake off again\n        browser.click(\"#auto-bake-label\");\n        browser.expect.element(\"#auto-bake\").to.not.be.selected.before(1000);\n    },\n\n    \"Special content\": browser => {\n        /* Special characters are rendered correctly */\n        utils.setInput(browser, SPECIAL_CHARS, false);\n\n        // First line\n        for (let i = 0x0; i <= 0x8; i++) {\n            browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)\n                .to.have.property(\"title\").equals(`Control character ${CONTROL_CHAR_NAMES[i] || \"0x\" + i.toString(16)}`);\n            browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)\n                .text.to.equal(String.fromCharCode(0x2400 + i));\n        }\n\n        // Tab \\u0009\n        browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property(\"textContent\").match(/\\u0009$/);\n\n        // Line feed \\u000a\n        browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property(\"textContent\").match(/^.{10}$/);\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n        // Second line\n        for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) {\n            const index = SPECIAL_CHARS.charCodeAt(i);\n            const name = CONTROL_CHAR_NAMES[index] || \"0x\" + index.toString(16);\n            const value = index >= 32 ? \"\\u2022\" : String.fromCharCode(0x2400 + index);\n\n            browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)\n                .to.have.property(\"title\").equals(`Control character ${name}`);\n            browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)\n                .text.to.equal(value);\n        }\n\n        /* Output renders correctly */\n        utils.setChrEnc(browser, \"output\", \"UTF-8\");\n        utils.bake(browser);\n\n        // First line\n        for (let i = 0x0; i <= 0x8; i++) {\n            browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)\n                .to.have.property(\"title\").equals(`Control character ${CONTROL_CHAR_NAMES[i] || \"0x\" + i.toString(16)}`);\n            browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)\n                .text.to.equal(String.fromCharCode(0x2400 + i));\n        }\n\n        // Tab \\u0009\n        browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property(\"textContent\").match(/\\u0009$/);\n\n        // Line feed \\u000a\n        browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property(\"textContent\").match(/^.{10}$/);\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n        // Second line\n        for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) {\n            const index = SPECIAL_CHARS.charCodeAt(i);\n            const name = CONTROL_CHAR_NAMES[index] || \"0x\" + index.toString(16);\n            const value = index >= 32 ? \"\\u2022\" : String.fromCharCode(0x2400 + index);\n\n            browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)\n                .to.have.property(\"title\").equals(`Control character ${name}`);\n            browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)\n                .text.to.equal(value);\n        }\n\n        /* Bytes are rendered correctly */\n        utils.setInput(browser, ALL_BYTES, false);\n        // Expect length to be 255, since one character is creating a newline\n        browser.expect.element(`#input-text .cm-content`).to.have.property(\"textContent\").match(/^.{255}$/);\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"256\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n\n        /* PUA \\ue000-\\uf8ff */\n        utils.setInput(browser, PUA_CHARS, false);\n        utils.setChrEnc(browser, \"output\", \"UTF-8\");\n        utils.bake(browser);\n\n        // Confirm input and output as expected\n        /*  In order to render whitespace characters as control character pictures in the output, even\n            when they are the designated line separator, CyberChef sometimes chooses to represent them\n            internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas).\n            See `Utils.escapeWhitespace()` for an example of this.\n            Therefore, PUA characters should be rendered normally in the Input but as control character\n            pictures in the output.\n        */\n        browser.expect.element(`#input-text .cm-content`).to.have.property(\"textContent\").match(/^\\ue000\\ue001\\uf8fe\\uf8ff$/);\n        browser.expect.element(`#output-text .cm-content`).to.have.property(\"textContent\").match(/^\\u2400\\u2401\\u3cfe\\u3cff$/);\n\n        /* Can be copied */\n        utils.setInput(browser, SPECIAL_CHARS, false);\n        utils.setChrEnc(browser, \"output\", \"UTF-8\");\n        utils.bake(browser);\n\n        // Manual copy\n        browser\n            .doubleClick(\"#output-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)\")\n            .waitForElementVisible(\"#output-text .cm-selectionBackground\");\n        utils.copy(browser);\n        utils.paste(browser, \"#search\"); // Paste into search box as this won't mess with the values\n\n        // Ensure that the values are as expected\n        browser.expect.element(\"#search\").to.have.value.that.equals(\"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\u0008\");\n        browser.clearValue(\"#search\");\n\n        // Raw copy\n        browser\n            .click(\"#copy-output\")\n            .pause(100);\n        utils.paste(browser, \"#search\"); // Paste into search box as this won't mess with the values\n\n        // Ensure that the values are as expected\n        browser.expect.element(\"#search\").to.have.value.that.matches(/^\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\u0008\\u0009/);\n        browser.clearValue(\"#search\");\n    },\n\n    \"HTML output\": browser => {\n        /* Displays correctly */\n        utils.loadRecipe(browser, \"Entropy\", ALL_BYTES);\n        utils.bake(browser);\n\n        browser\n            .waitForElementVisible(\"#output-html\")\n            .waitForElementVisible(\"#output-html #chart-area\");\n\n        /* Status bar widgets are disabled */\n        browser.expect.element(\"#output-text .cm-status-bar .disabled .stats-length-value\").to.be.visible;\n        browser.expect.element(\"#output-text .cm-status-bar .disabled .stats-lines-value\").to.be.visible;\n        browser.expect.element(\"#output-text .cm-status-bar .disabled .chr-enc-value\").to.be.visible;\n        browser.expect.element(\"#output-text .cm-status-bar .disabled .eol-value\").to.be.visible;\n\n        /* Displays special chars correctly */\n        utils.loadRecipe(browser, \"To Table\", \",\\u0000\\u0001\\u0002\\u0003\\u0004\", [\",\", \"\\\\r\\\\n\", false, \"HTML\"]);\n        utils.bake(browser);\n\n        for (let i = 0x0; i <= 0x4; i++) {\n            browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`)\n                .to.have.property(\"title\").equals(`Control character ${CONTROL_CHAR_NAMES[i] || \"0x\" + i.toString(16)}`);\n            browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`)\n                .text.to.equal(String.fromCharCode(0x2400 + i));\n        }\n\n        /* Can be copied */\n        // Raw copy\n        browser\n            .click(\"#copy-output\")\n            .pause(100);\n        utils.paste(browser, \"#search\"); // Paste into search box as this won't mess with the values\n\n        // Ensure that the values are as expected\n        browser.expect.element(\"#search\").to.have.value.that.matches(/\\u0000\\u0001\\u0002\\u0003\\u0004/);\n        browser.clearValue(\"#search\");\n    },\n\n    \"Highlighting\": browser => {\n        utils.setInput(browser, SELECTABLE_STRING);\n        utils.bake(browser);\n\n        /* Selecting input text also selects other instances in input and output */\n        browser // Input\n            .click(\"#auto-bake-label\")\n            .doubleClick(\"#input-text .cm-content .cm-line:nth-of-type(1)\")\n            .waitForElementVisible(\"#input-text .cm-selectionLayer .cm-selectionBackground\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch\")\n            .waitForElementVisible(\"#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch\")\n            .waitForElementVisible(\"#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch\")\n            .waitForElementVisible(\"#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch\");\n\n        browser // Output\n            .waitForElementVisible(\"#output-text .cm-selectionLayer .cm-selectionBackground\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch\")\n            .waitForElementVisible(\"#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch\")\n            .waitForElementVisible(\"#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch\")\n            .waitForElementVisible(\"#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch\");\n\n        /* Selecting output text highlights in input */\n        browser // Output\n            .click(\"#output-text\")\n            .waitForElementNotPresent(\"#input-text .cm-selectionLayer .cm-selectionBackground\")\n            .waitForElementNotPresent(\"#output-text .cm-selectionLayer .cm-selectionBackground\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line .cm-selectionMatch\")\n            .doubleClick(\"#output-text .cm-content .cm-line:nth-of-type(7)\")\n            .waitForElementVisible(\"#output-text .cm-selectionLayer .cm-selectionBackground\")\n            .waitForElementVisible(\"#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch\")\n            .waitForElementVisible(\"#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch\")\n            .waitForElementVisible(\"#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch\");\n\n        browser // Input\n            .waitForElementVisible(\"#input-text .cm-selectionLayer .cm-selectionBackground\")\n            .waitForElementVisible(\"#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch\")\n            .waitForElementVisible(\"#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch\")\n            .waitForElementVisible(\"#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch\");\n\n        // Turn autobake off again\n        browser.click(\"#auto-bake-label\");\n    },\n\n    \"Character encoding\": browser => {\n        const CHINESE_CHARS = \"不要恐慌。\";\n        /* Dropup works */\n        /* Selecting changes output correctly */\n        utils.setInput(browser, CHINESE_CHARS, false);\n        utils.setChrEnc(browser, \"input\", \"UTF-8\");\n        utils.bake(browser);\n\n        /* Output encoding should be autodetected */\n        browser\n            .waitForElementVisible(\"#snackbar-container .snackbar-content\", 5000)\n            .expect.element(\"#snackbar-container .snackbar-content\").text.to.equal(\"Output character encoding has been detected and changed to UTF-8\");\n\n        utils.expectOutput(browser, CHINESE_CHARS);\n\n        /* Change the output encoding manually to test for URL presence */\n        utils.setChrEnc(browser, \"output\", \"UTF-8\");\n\n        /* Encodings appear in the URL */\n        browser.assert.urlContains(\"ienc=65001\");\n        browser.assert.urlContains(\"oenc=65001\");\n\n        /* Preserved when changing tabs */\n        browser\n            .click(\"#btn-new-tab\")\n            .waitForElementVisible(\"#input-tabs li:nth-of-type(2).active-input-tab\");\n        browser.expect.element(\"#input-text .chr-enc-value\").text.that.equals(\"Raw Bytes\");\n        browser.expect.element(\"#output-text .chr-enc-value\").text.that.equals(\"Raw Bytes\");\n\n        utils.setChrEnc(browser, \"input\", \"UTF-7\");\n        utils.setChrEnc(browser, \"output\", \"UTF-7\");\n\n        browser\n            .click(\"#input-tabs li:nth-of-type(1)\")\n            .waitForElementVisible(\"#input-tabs li:nth-of-type(1).active-input-tab\");\n        browser.expect.element(\"#input-text .chr-enc-value\").text.that.equals(\"UTF-8\");\n        browser.expect.element(\"#output-text .chr-enc-value\").text.that.equals(\"UTF-8\");\n\n        /* Try various encodings */\n        // These are not meant to be realistic encodings for this data\n        utils.setInput(browser, CHINESE_CHARS, false);\n        utils.setChrEnc(browser, \"input\", \"UTF-8\");\n        utils.setChrEnc(browser, \"output\", \"UTF-16LE\");\n        utils.bake(browser);\n        utils.expectOutput(browser, \"\\uB8E4\\uE88D\\u81A6\\u81E6\\uE690\\u8C85\\u80E3\");\n\n        utils.setChrEnc(browser, \"output\", \"Simplified Chinese GBK\");\n        utils.bake(browser);\n        utils.expectOutput(browser, \"\\u6D93\\u5D88\\uFDFF\\u93AD\\u612D\\u53A1\\u9286\\u0000\");\n\n        utils.setChrEnc(browser, \"input\", \"UTF-7\");\n        utils.bake(browser);\n        utils.expectOutput(browser, \"+Tg0-+iYE-+YFA-+YUw-\");\n\n        utils.setChrEnc(browser, \"input\", \"Traditional Chinese Big5\");\n        utils.bake(browser);\n        utils.expectOutput(browser, \"\\u3043\\u74B6\\uFDFF\\u7A3A\\uFDFF\");\n\n        utils.setChrEnc(browser, \"output\", \"Windows-1251 Cyrillic\");\n        utils.bake(browser);\n        utils.expectOutput(browser, \"\\u00A4\\u0408\\u00ADn\\u00AE\\u0408\\u00B7W\\u040EC\");\n    },\n\n    \"Line endings\": browser => {\n        /* Dropup works */\n        /* Selecting changes view in input */\n        utils.setInput(browser, MULTI_LINE_STRING);\n\n        // Line endings: LF\n\n        // Input\n        browser\n            .waitForElementPresent(\"#input-text .cm-content .cm-line:nth-of-type(3)\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(4)\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-specialChar\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"3\");\n\n        // Output\n        utils.bake(browser);\n        browser\n            .waitForElementPresent(\"#output-text .cm-content .cm-line:nth-of-type(3)\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(4)\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-specialChar\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"3\");\n\n        // Input EOL: VT\n        utils.setEOLSeq(browser, \"input\", \"VT\");\n\n        // Input\n        browser\n            .waitForElementPresent(\"#input-text .cm-content .cm-line:nth-of-type(1)\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(2)\")\n            .waitForElementPresent(\"#input-text .cm-content .cm-specialChar\");\n        browser.expect.element(\"#input-text .cm-content .cm-specialChar\").text.to.equal(\"␊\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n\n        // Output\n        utils.bake(browser);\n        browser\n            .waitForElementPresent(\"#output-text .cm-content .cm-line:nth-of-type(3)\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(4)\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-specialChar\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"3\");\n\n        // Output EOL: VT\n        utils.setEOLSeq(browser, \"output\", \"VT\");\n\n        // Input\n        browser\n            .waitForElementPresent(\"#input-text .cm-content .cm-line:nth-of-type(1)\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(2)\")\n            .waitForElementPresent(\"#input-text .cm-content .cm-specialChar\");\n        browser.expect.element(\"#input-text .cm-content .cm-specialChar\").text.to.equal(\"␊\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n\n        // Output\n        browser\n            .waitForElementPresent(\"#output-text .cm-content .cm-line:nth-of-type(1)\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(2)\")\n            .waitForElementPresent(\"#output-text .cm-content .cm-specialChar\");\n        browser.expect.element(\"#output-text .cm-content .cm-specialChar\").text.to.equal(\"␊\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"301\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n\n        /* Adding new line ending changes output correctly */\n        browser.sendKeys(\"#input-text .cm-content\", browser.Keys.RETURN);\n\n        // Input\n        browser\n            .waitForElementPresent(\"#input-text .cm-content .cm-line:nth-of-type(2)\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(3)\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"302\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n        // Output\n        utils.bake(browser);\n        browser\n            .waitForElementPresent(\"#output-text .cm-content .cm-line:nth-of-type(2)\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(3)\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"302\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n        // Input EOL: CRLF\n        utils.setEOLSeq(browser, \"input\", \"CRLF\");\n        // Output EOL: CR\n        utils.setEOLSeq(browser, \"output\", \"CR\");\n        browser.sendKeys(\"#input-text .cm-content\", browser.Keys.RETURN);\n\n        // Input\n        browser\n            .waitForElementPresent(\"#input-text .cm-content .cm-line:nth-of-type(2)\")\n            .waitForElementNotPresent(\"#input-text .cm-content .cm-line:nth-of-type(3)\")\n            .waitForElementPresent(\"#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)\");\n        browser.expect.element(\"#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)\").text.to.equal(\"␋\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"304\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n        // Output\n        utils.bake(browser);\n        browser\n            .waitForElementPresent(\"#output-text .cm-content .cm-line:nth-of-type(2)\")\n            .waitForElementNotPresent(\"#output-text .cm-content .cm-line:nth-of-type(3)\")\n            .waitForElementPresent(\"#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar\");\n        browser.expect.element(\"#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar\").text.to.equal(\"␊\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"304\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n        /* Line endings appear in the URL */\n        browser.assert.urlContains(\"ieol=CRLF\");\n        browser.assert.urlContains(\"oeol=CR\");\n\n        /* Preserved when changing tabs */\n        browser\n            .click(\"#btn-new-tab\")\n            .waitForElementVisible(\"#input-tabs li:nth-of-type(2).active-input-tab\");\n        browser.expect.element(\"#input-text .eol-value\").text.that.equals(\"LF\");\n        browser.expect.element(\"#output-text .eol-value\").text.that.equals(\"LF\");\n\n        utils.setEOLSeq(browser, \"input\", \"FF\");\n        utils.setEOLSeq(browser, \"output\", \"LS\");\n\n        browser\n            .click(\"#input-tabs li:nth-of-type(1)\")\n            .waitForElementVisible(\"#input-tabs li:nth-of-type(1).active-input-tab\");\n        browser.expect.element(\"#input-text .eol-value\").text.that.equals(\"CRLF\");\n        browser.expect.element(\"#output-text .eol-value\").text.that.equals(\"CR\");\n    },\n\n    \"File inputs\": browser => {\n        utils.clear(browser);\n\n        /* Side panel displays correct info */\n        utils.uploadFile(browser, \"files/TowelDay.jpeg\");\n\n        browser\n            .waitForElementVisible(\"#input-text .cm-file-details\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-toggle-shown\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-thumbnail\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-name\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-size\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-type\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-loaded\");\n        browser.expect.element(\"#input-text .cm-file-details .file-details-name\").text.that.equals(\"TowelDay.jpeg\");\n        browser.expect.element(\"#input-text .cm-file-details .file-details-size\").text.that.equals(\"61,379 bytes\");\n        browser.expect.element(\"#input-text .cm-file-details .file-details-type\").text.that.equals(\"image/jpeg\");\n        browser.expect.element(\"#input-text .cm-file-details .file-details-loaded\").text.that.equals(\"100%\");\n\n        /* Side panel can be hidden */\n        browser\n            .click(\"#input-text .cm-file-details .file-details-toggle-shown\")\n            .waitForElementNotPresent(\"#input-text .cm-file-details .file-details-toggle-shown\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-toggle-hidden\")\n            .expect.element(\"#input-text .cm-file-details\").to.have.css(\"width\").which.equals(\"1px\");\n\n        browser\n            .click(\"#input-text .cm-file-details .file-details-toggle-hidden\")\n            .waitForElementNotPresent(\"#input-text .cm-file-details .file-details-toggle-hidden\")\n            .waitForElementVisible(\"#input-text .cm-file-details .file-details-toggle-shown\")\n            .expect.element(\"#input-text .cm-file-details\").to.have.css(\"width\").which.equals(\"200px\");\n    },\n\n    \"Folder inputs\": browser => {\n        utils.clear(browser);\n\n        /* Side panel displays correct info */\n        utils.uploadFolder(browser, \"files\");\n\n        // Loop through tabs\n        for (let i = 1; i < 3; i++) {\n            browser\n                .click(`#input-tabs li:nth-of-type(${i})`)\n                .waitForElementVisible(`#input-tabs li:nth-of-type(${i}).active-input-tab`);\n\n            browser\n                .waitForElementVisible(\"#input-text .cm-file-details\")\n                .waitForElementVisible(\"#input-text .cm-file-details .file-details-toggle-shown\")\n                .waitForElementVisible(\"#input-text .cm-file-details .file-details-thumbnail\")\n                .waitForElementVisible(\"#input-text .cm-file-details .file-details-name\")\n                .waitForElementVisible(\"#input-text .cm-file-details .file-details-size\")\n                .waitForElementVisible(\"#input-text .cm-file-details .file-details-type\")\n                .waitForElementVisible(\"#input-text .cm-file-details .file-details-loaded\");\n\n            browser.getText(\"#input-text .cm-file-details .file-details-name\", function(result) {\n                switch (result.value) {\n                    case \"TowelDay.jpeg\":\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-name\").text.that.equals(\"TowelDay.jpeg\");\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-size\").text.that.equals(\"61,379 bytes\");\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-type\").text.that.equals(\"image/jpeg\");\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-loaded\").text.that.equals(\"100%\");\n                        break;\n                    case \"Hitchhikers_Guide.jpeg\":\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-name\").text.that.equals(\"Hitchhikers_Guide.jpeg\");\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-size\").text.that.equals(\"36,595 bytes\");\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-type\").text.that.equals(\"image/jpeg\");\n                        browser.expect.element(\"#input-text .cm-file-details .file-details-loaded\").text.that.equals(\"100%\");\n                        break;\n                    default:\n                        break;\n                }\n            });\n        }\n    },\n\n    // \"Loading from URL\": browser => {\n    //     utils.clear(browser);\n\n    //     /* Side panel displays correct info */\n    //     utils.uploadFile(browser, \"files/TowelDay.jpeg\");\n\n    //     browser\n    //         .waitForElementVisible(\"#input-text .cm-file-details\")\n    //         .waitForElementVisible(\"#input-text .cm-file-details .file-details-toggle-shown\")\n    //         .waitForElementVisible(\"#input-text .cm-file-details .file-details-thumbnail\")\n    //         .waitForElementVisible(\"#input-text .cm-file-details .file-details-name\")\n    //         .waitForElementVisible(\"#input-text .cm-file-details .file-details-size\")\n    //         .waitForElementVisible(\"#input-text .cm-file-details .file-details-type\")\n    //         .waitForElementVisible(\"#input-text .cm-file-details .file-details-loaded\");\n\n    //     /* Complex deep link populates the input correctly (encoding, eol, input) */\n    //     browser\n    //         .urlHash(\"recipe=To_Base64('A-Za-z0-9%2B/%3D')&input=VGhlIHNoaXBzIGh1bmcgaW4gdGhlIHNreSBpbiBtdWNoIHRoZSBzYW1lIHdheSB0aGF0IGJyaWNrcyBkb24ndC4M&ienc=21866&oenc=1201&ieol=FF&oeol=PS\")\n    //         .waitForElementVisible(\"#rec-list li.operation\");\n\n    //     browser.expect.element(`#input-text .cm-content`).to.have.property(\"textContent\").match(/^.{65}$/);\n    //     browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"66\");\n    //     browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n\n    //     browser.expect.element(\"#input-text .chr-enc-value\").text.that.equals(\"KOI8-U Ukrainian Cyrillic\");\n    //     browser.expect.element(\"#output-text .chr-enc-value\").text.that.equals(\"UTF-16BE\");\n\n    //     browser.expect.element(\"#input-text .eol-value\").text.that.equals(\"FF\");\n    //     browser.expect.element(\"#output-text .eol-value\").text.that.equals(\"PS\");\n\n    //     utils.bake(browser);\n\n    //     browser.expect.element(`#output-text .cm-content`).to.have.property(\"textContent\").match(/^.{44}$/);\n    //     browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"44\");\n    //     browser.expect.element(\"#output-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n    // },\n\n    \"Replace input with output\": browser => {\n        /* Input is correctly populated */\n        utils.loadRecipe(browser, \"XOR\", \"The ships hung in the sky in much the same way that bricks don't.\", [{ \"option\": \"Hex\", \"string\": \"65\" }, \"Standard\", false]);\n        utils.setChrEnc(browser, \"input\", \"UTF-32LE\");\n        utils.setChrEnc(browser, \"output\", \"UTF-7\");\n        utils.setEOLSeq(browser, \"input\", \"CRLF\");\n        utils.setEOLSeq(browser, \"output\", \"LS\");\n\n        browser\n            .sendKeys(\"#input-text .cm-content\", browser.Keys.RETURN)\n            .expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n        utils.bake(browser);\n\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"67\");\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"2\");\n        browser.expect.element(\"#input-text .chr-enc-value\").text.that.equals(\"UTF-32LE\");\n        browser.expect.element(\"#input-text .eol-value\").text.that.equals(\"CRLF\");\n        browser.expect.element(\"#output-text .cm-status-bar .stats-length-value\").text.to.equal(\"268\");\n\n        browser\n            .click(\"#switch\")\n            .waitForElementVisible(\"#stale-indicator\");\n\n        browser.expect.element(\"#input-text .cm-status-bar .stats-length-value\").text.to.equal(\"268\");\n\n        /* Special characters, encodings and line endings all as expected */\n        browser.expect.element(\"#input-text .cm-status-bar .stats-lines-value\").text.to.equal(\"1\");\n        browser.expect.element(\"#input-text .chr-enc-value\").text.that.equals(\"UTF-7\");\n        browser.expect.element(\"#input-text .eol-value\").text.that.equals(\"LS\");\n        browser.expect.element(\"#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)\").text.to.equal(\"␍\");\n        browser.expect.element(\"#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(49)\").text.to.equal(\"␑\");\n        browser.waitForElementNotPresent(\"#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(50)\");\n    },\n\n\n    after: browser => {\n        browser.end();\n    }\n};\n"
  },
  {
    "path": "tests/browser/02_ops.js",
    "content": "/**\n * Tests for operations.\n * The primary purpose for these test is to ensure that the operations\n * output something vaguely expected (i.e. they aren't completely broken\n * after a dependency update or changes to the UI), rather than to confirm\n * that this output is actually accurate. Accuracy of output and testing\n * of edge cases should be carried out in the operations test suite found\n * in /tests/operations as this is much faster and easier to configure\n * than the UI tests found here.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nconst utils = require(\"./browserUtils.js\");\n\nmodule.exports = {\n    before: browser => {\n        browser\n            .resizeWindow(1280, 800)\n            .url(browser.launchUrl)\n            .useCss()\n            .waitForElementNotPresent(\"#preloader\", 10000)\n            .click(\"#auto-bake-label\");\n    },\n\n    \"Sanity check operations\": async browser => {\n        const Images = await import(\"../samples/Images.mjs\");\n        testOp(browser, \"A1Z26 Cipher Decode\", \"20 5 19 20 15 21 20 16 21 20\", \"testoutput\");\n        testOp(browser, \"A1Z26 Cipher Encode\", \"test input\", \"20 5 19 20 9 14 16 21 20\");\n        testOp(browser, \"ADD\", \"test input\", \"Ê»ÉÊv¿ÄÆËÊ\", [{ \"option\": \"Hex\", \"string\": \"56\" }]);\n        testOp(browser, \"AES Decrypt\", \"b443f7f7c16ac5396a34273f6f639caa\", \"test output\", [{ \"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\" }, { \"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\" }, \"CBC\", \"Hex\", \"Raw\", { \"option\": \"Hex\", \"string\": \"\" }]);\n        testOp(browser, \"AES Encrypt\", \"test input\", \"e42eb8fbfb7a98fff061cd2c1a794d92\", [{\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"}, {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"}, \"CBC\", \"Raw\", \"Hex\"]);\n        testOp(browser, \"AND\", \"test input\", \"4$04  $044\", [{ \"option\": \"Hex\", \"string\": \"34\" }]);\n        testOp(browser, \"Add line numbers\", \"test input\", \"1 test input\");\n        testOp(browser, [\"From Hex\", \"Add Text To Image\", \"SHA2\"], Images.PNG_HEX, \"50cdf8ea483c55564a091650c2bccb4586f919b721e5fe9d6a61660505b4346d6ebdb2ef0cf075a7728cd84cb26ea3e477b5bd86a94a49a27d79423994afb60a\", [[], [\"Chef\", \"Center\", \"Middle\", 0, 0, 16, \"Roboto\"], []]);\n        testOp(browser, [\"From Hex\", \"Add Text To Image\", \"SHA2\"], Images.PNG_HEX, \"78b3055463d9167dd039e47f451acaf06c593d209f8e405b4e18011cdcf190dc0af5952be887d93c0ebd38738e978120c1294c71104e6b00d3f9de8d6320ec1c\", [[], [\"Chef\", \"Center\", \"Middle\", 0, 0, 16, \"Roboto Black\"], []]);\n        testOp(browser, [\"From Hex\", \"Add Text To Image\", \"SHA2\"], Images.PNG_HEX, \"4ab4d4b6cb22ad700f6cd144c2c8ecad2a094f21a1d1d5d48eb6c8f97417192f89b4512f6a78276d49668ebef5e89c3a4d14860cb79399a0dafce98c92209e07\", [[], [\"Chef\", \"Center\", \"Middle\", 0, 0, 16, \"Roboto Mono\"], []]);\n        testOp(browser, [\"From Hex\", \"Add Text To Image\", \"SHA2\"], Images.PNG_HEX, \"11490db4907516b4d9e256da1ac0b02b561fa7547971e6316a8a0b90c9c66585a11f3145672c6d972b1a221d3bfad9c8a97de7ff77fd9442ebc40f39c1ef9ef7\", [[], [\"Chef\", \"Center\", \"Middle\", 0, 0, 16, \"Roboto Slab\"], []]);\n        testOp(browser, [\"From Hex\", \"Dither Image\", \"SHA2\"], Images.PNG_HEX, \"cbf587a78915cfb14546ba83080b13e5054800802488dd0cb786b8951e7dc0b48f055260917bd0ccfc075e422b9d6aff112948562653995d74e70f0b66367ac3\", [[], [], []]);\n        testOp(browser, [\"From Hex\", \"Generate Image\", \"SHA2\"], Images.PNG_HEX, \"2c451762a6c9192fd31dc80765eab3f447be70ea51f6fdb6911ade4d89d4a98bd0a1ff00b08d76aac472faeceb54b66092e3f3be7bbf899bf3e55ca9c96a56aa\", [[], [], []]);\n        testOp(browser, [\"From Hex\", \"Image Hue/Saturation/Lightness\", \"SHA2\"], Images.PNG_HEX, \"522dfc0bbef00e05c5d6861a002039fa2952e4bbb7fe8d21d0d538ef6f9d65da82065929b4150dc5b8b49460ee6c9bef7f660b86f8d4e7442a07c61c0a152a4b\", [[], [50, 50, 50], []]);\n        testOp(browser, [\"From Hex\", \"Resize Image\", \"SHA2\"], Images.PNG_HEX, \"654bfbf0a0537c901459c4bc22c5fb0bacbf01af775a0733e3a1c46cda5b699bcc4ed85322d813c7bb9b245d62d64425c0766fe03d3d20bc63634e2a4df17626\", [[], [64, 64], []]);\n        testOp(browser, \"Adler-32 Checksum\", \"test input\", \"16160411\");\n        testOp(browser, \"Affine Cipher Decode\", \"test input\", \"rcqr glnsr\", [1, 2]);\n        testOp(browser, \"Affine Cipher Encode\", \"test input\", \"gndg zoujg\", [3, 1]);\n        testOp(browser, \"AMF Decode\", \"\\u000A\\u0013\\u0001\\u0003a\\u0006\\u0009test\", /\"\\$value\": \"test\"/);\n        testOp(browser, \"AMF Encode\", '{\"a\": \"test\"}', \"\\u000A\\u0013\\u0001\\u0003a\\u0006\\u0009test\");\n        testOp(browser, \"Analyse hash\", \"0123456789abcdef\", /CRC-64/);\n        testOp(browser, \"Atbash Cipher\", \"test input\", \"gvhg rmkfg\");\n        // testOp(browser, \"Avro to JSON\", \"test input\", \"test_output\");\n        testOp(browser,\n            [\n                \"From Hex\", \"Avro to JSON\"\n            ],\n            \"4f626a0104166176726f2e736368656d6196017b2274797065223a227265636f7264222c226e616d65223a22736d616c6c222c226669656c6473223a5b7b226e616d65223a226e616d65222c2274797065223a22737472696e67227d5d7d146176726f2e636f646563086e756c6c004e0247632e3702e5b75cdab9a62f1541020e0c6d796e616d654e0247632e3702e5b75cdab9a62f1541\",\n            '{\"name\":\"myname\"}\\n',\n            [[], [false]]\n        );\n        testOp(browser, \"BLAKE2b\", \"test input\", \"33ebdc8f38177f3f3f334eeb117a84e11f061bbca4db6b8923e5cec85103f59f415551a5d5a933fdb6305dc7bf84671c2540b463dbfa08ee1895cfaa5bd780b5\", [\"512\", \"Hex\", { \"option\": \"UTF8\", \"string\": \"pass\" }]);\n        testOp(browser, \"BLAKE2s\", \"test input\", \"defe73d61dfa6e5807e4f9643e159a09ccda6be3c26dcd65f8a9bb38bfc973a7\", [\"256\", \"Hex\", { \"option\": \"UTF8\", \"string\": \"pass\" }]);\n        testOp(browser, \"BSON deserialise\", \"\\u0011\\u0000\\u0000\\u0000\\u0002a\\u0000\\u0005\\u0000\\u0000\\u0000test\\u0000\\u0000\", '{\\u000A  \"a\": \"test\"\\u000A}');\n        testOp(browser, \"BSON serialise\", '{\"a\":\"test\"}', \"\\u0011\\u0000\\u0000\\u0000\\u0002a\\u0000\\u0005\\u0000\\u0000\\u0000test\\u0000\\u0000\");\n        // testOp(browser, \"Bacon Cipher Decode\", \"test input\", \"test_output\");\n        // testOp(browser, \"Bacon Cipher Encode\", \"test input\", \"test_output\");\n        testOp(browser, \"Bcrypt\", \"test input\", /^\\$2a\\$06\\$.{53}$/, [6]);\n        testOp(browser, \"Bcrypt compare\", \"test input\", \"Match: test input\", [\"$2a$05$FCfBSVX7OeRkK.9kQVFCiOYu9XtwtIbePqUiroD1lkASW9q5QClzG\"]);\n        testOp(browser, \"Bcrypt parse\", \"$2a$05$kXWtAIGB/R8VEzInoM5ocOTBtyc0m2YTIwFiBU/0XoW032f9QrkWW\", /Rounds: 5/);\n        testOp(browser, \"Bifid Cipher Decode\", \"qblb tfovy\", \"test input\", [\"pass\"]);\n        testOp(browser, \"Bifid Cipher Encode\", \"test input\", \"qblb tfovy\", [\"pass\"]);\n        testOp(browser, \"Bit shift left\", \"test input\", \"\\u00E8\\u00CA\\u00E6\\u00E8@\\u00D2\\u00DC\\u00E0\\u00EA\\u00E8\");\n        testOp(browser, \"Bit shift right\", \"test input\", \":29:\\u0010478::\");\n        testOp(browser, \"Blowfish Decrypt\", \"10884e15427dd84ec35204e9c8e921ae\", \"test_output\", [{\"option\": \"Hex\", \"string\": \"1234567801234567\"}, {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, \"CBC\", \"Hex\", \"Raw\"]);\n        testOp(browser, \"Blowfish Encrypt\", \"test input\", \"f0fadbd1d90d774f714248cf26b96410\", [{\"option\": \"Hex\", \"string\": \"1234567801234567\"}, {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, \"CBC\", \"Raw\", \"Hex\"]);\n        testOp(browser, [\"From Hex\", \"Blur Image\", \"SHA2\"], Images.PNG_HEX, \"24f2e89f3e00cc35f551bbc48ea82e76474946ce0282183494d1ca3d3b0012c27b6102c4368ae056dc7fecb6df7886d86ff3d29b7e5965493f30c371eee9a24e\");\n        testOp(browser, [\"From Hex\", \"Blur Image\", \"SHA2\"], Images.PNG_HEX, \"2c49d89fc10c94352c9a19f82de353c37928831d6f976a6b36eb918825a0ba027980801838228a4a0da63f1886e4fa59b6666f992ad2d2b7d4622253dc034052\", [[], [5, \"Gaussian\"], []]);\n        testOp(browser, [\"From Hex\", \"Sharpen Image\", \"SHA2\"], Images.PNG_HEX, \"acc7027642c2eeb67d7356a80ed8a1bdce9adabf656ea1294e47723f506626a7aa41f1660fa844a1e1e83b17180017ab0d5bccd7f6a341692832020dc887eaa5\");\n        testOp(browser, [\"From Hex\", \"Contain Image\", \"SHA2\"], Images.PNG_HEX, \"cb871ad0722d487d56a2b18247b1aa30ecc244eb717e08e23a55cae78759553312dc1717196d7cb9daa04743e57c56fc3901ba92be5a68fb03c377f718e8efe7\");\n        testOpHtml(browser, \"Bombe\", \"XTSYN WAEUG EZALY NRQIM AMLZX MFUOD AWXLY LZCUZ QOQBQ JLCPK NDDRW F\", \"table tr:last-child td:first-child\", \"ECG\", [\"3-rotor\", \"LEYJVCNIXWPBQMDRTAKZGFUHOS\", \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"ESOVPZJAYQUIRHXLNFTGKDCMWB<K\", \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", \"HELLO CYBER CHEFU SER\", 0, true]);\n        testOp(browser, [\"Bzip2 Compress\", \"To Hex\"], \"test input\", \"42 5a 68 39 31 41 59 26 53 59 cf 96 82 1d 00 00 03 91 80 40 00 02 21 4e 00 20 00 21 90 c2 10 c0 88 33 92 8e df 17 72 45 38 50 90 cf 96 82 1d\");\n        testOp(browser, [\"From Hex\", \"Bzip2 Decompress\"], \"425a68393141592653597b0884b7000003038000008200ce00200021a647a4218013709517c5dc914e14241ec2212dc0\", \"test_output\", [[], [true]]);\n    // testOp(browser, \"CBOR Decode\", \"test input\", \"test output\");\n    // testOp(browser, \"CBOR Encode\", \"test input\", \"test output\");\n        testOp(browser, \"CRC Checksum\", \"test input\", \"77c7\", [\"CRC-16\"]);\n        testOp(browser, \"CRC Checksum\", \"test input\", \"29822bc8\", [\"CRC-32\"]);\n        testOp(browser, \"CRC Checksum\", \"test input\", \"9d\", [\"CRC-8\"]);\n    // testOp(browser, \"CSS Beautify\", \"test input\", \"test_output\");\n    // testOp(browser, \"CSS Minify\", \"test input\", \"test_output\");\n        // testOp(browser, \"CSS selector\", \"test input\", \"test_output\");\n        // testOp(browser, \"CSV to JSON\", \"test input\", \"test_output\");\n    // testOp(browser, \"CTPH\", \"test input\", \"test_output\");\n        // testOp(browser, \"Cartesian Product\", \"test input\", \"test_output\");\n        // testOp(browser, \"Change IP format\", \"test input\", \"test_output\");\n        // testOp(browser, \"Chi Square\", \"test input\", \"test_output\");\n        // testOp(browser, \"CipherSaber2 Decrypt\", \"test input\", \"test_output\");\n        // testOp(browser, \"CipherSaber2 Encrypt\", \"test input\", \"test_output\");\n        // testOp(browser, \"Citrix CTX1 Decode\", \"test input\", \"test_output\");\n        // testOp(browser, \"Citrix CTX1 Encode\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Colossus\", \"CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/N\", \"table tr:last-child td:first-child\", \"30\", [\"\", \"KH Pattern\", \"Z\", \"\", \"\", \"None\", \"Select Program\", \"Letter Count\", \"\", \"\", \"\", \"\", \"\", \"\", false, \"\", \"\", \"\", \"\", \"\", \"\", false, \"\", \"\", \"\", \"\", \"\", \"\", false, \"\", false, \"\", false, false, false, false, false, \"\", false, false, \"\", \"\", 0, \"\", \"\", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);\n        // testOp(browser, \"Comment\", \"test input\", \"test_output\");\n        // testOp(browser, \"Compare CTPH hashes\", \"test input\", \"test_output\");\n        // testOp(browser, \"Compare SSDEEP hashes\", \"test input\", \"test_output\");\n        // testOp(browser, \"Conditional Jump\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Contain Image\", \"files/Hitchhikers_Guide.jpeg\");\n        // testOp(browser, \"Convert area\", \"test input\", \"test_output\");\n    // testOp(browser, \"Convert co-ordinate format\", \"test input\", \"test_output\");\n        // testOp(browser, \"Convert data units\", \"test input\", \"test_output\");\n        // testOp(browser, \"Convert distance\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Convert Image Format\", \"files/Hitchhikers_Guide.jpeg\");\n        // testOp(browser, \"Convert mass\", \"test input\", \"test_output\");\n        // testOp(browser, \"Convert speed\", \"test input\", \"test_output\");\n        // testOp(browser, \"Convert to NATO alphabet\", \"test input\", \"test_output\");\n        // testOp(browser, \"Count occurrences\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Cover Image\", \"files/Hitchhikers_Guide.jpeg\");\n        testOpImage(browser, \"Crop Image\", \"files/Hitchhikers_Guide.jpeg\");\n    // testOp(browser, \"CSS Selector\", \"test input\", \"test output\");\n        // testOp(browser, \"DES Decrypt\", \"test input\", \"test_output\");\n        // testOp(browser, \"DES Encrypt\", \"test input\", \"test_output\");\n        // testOp(browser, \"DNS over HTTPS\", \"test input\", \"test_output\");\n        // testOp(browser, \"Dechunk HTTP response\", \"test input\", \"test_output\");\n        // testOp(browser, \"Decode NetBIOS Name\", \"test input\", \"test_output\");\n        // testOp(browser, \"Decode text\", \"test input\", \"test_output\");\n        // testOp(browser, \"Defang IP Addresses\", \"test input\", \"test_output\");\n        // testOp(browser, \"Defang URL\", \"test input\", \"test_output\");\n        // testOp(browser, \"Derive EVP key\", \"test input\", \"test_output\");\n        // testOp(browser, \"Derive PBKDF2 key\", \"test input\", \"test_output\");\n        // testOp(browser, \"Detect File Type\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Diff\", \"The cat sat on the mat\\n\\nThe mat cat on the sat\", \"ins:first-child\", \"mat\", [\"\\\\n\\\\n\", \"Word\", true, true, false, false]);\n        // testOp(browser, \"Disassemble x86\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Dither Image\", \"files/Hitchhikers_Guide.jpeg\");\n    // testOp(browser, \"Divide\", \"test input\", \"test_output\");\n        // testOp(browser, \"Drop bytes\", \"test input\", \"test_output\");\n        // testOp(browser, \"Encode NetBIOS Name\", \"test input\", \"test_output\");\n        // testOp(browser, \"Encode text\", \"test input\", \"test_output\");\n        // testOp(browser, \"Enigma\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Entropy\", \"test input\", \"\", /Shannon entropy: 2.8464393446710154/);\n    // testOp(browser, \"Escape string\", \"test input\", \"test_output\");\n        // testOp(browser, \"Escape Unicode Characters\", \"test input\", \"test_output\");\n        // testOp(browser, \"Expand alphabet range\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract dates\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract domains\", \"test input\", \"test_output\");\n    // testOp(browser, \"Extract EXIF\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract email addresses\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract file paths\", \"test input\", \"test_output\");\n        testOpFile(browser, \"Extract Files\", \"files/Hitchhikers_Guide.jpeg\", \".card:last-child .collapsed\", \"extracted_at_0x3d38.zlib\");\n        // This test seems unreliable on GitHub Actions, not reproducible locally.\n        // testOpFile(browser, \"Extract ID3\", \"files/mp3example.mp3\", \"tr:last-child td:last-child\", \"Kevin MacLeod\");        // testOp(browser, \"Extract IP addresses\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract LSB\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract MAC addresses\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract RGBA\", \"test input\", \"test_output\");\n        // testOp(browser, \"Extract URLs\", \"test input\", \"test_output\");\n        // testOp(browser, \"Filter\", \"test input\", \"test_output\");\n        // testOp(browser, \"Find / Replace\", \"test input\", \"test_output\");\n        // testOp(browser, \"Fletcher-16 Checksum\", \"test input\", \"test_output\");\n        // testOp(browser, \"Fletcher-32 Checksum\", \"test input\", \"test_output\");\n        // testOp(browser, \"Fletcher-64 Checksum\", \"test input\", \"test_output\");\n        // testOp(browser, \"Fletcher-8 Checksum\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Flip Image\", \"files/Hitchhikers_Guide.jpeg\");\n        // testOp(browser, \"Fork\", \"test input\", \"test_output\");\n        // testOp(browser, \"Format MAC addresses\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Frequency distribution\", \"test input\", \"\", /Number of bytes not represented: 248/);\n        // testOp(browser, \"From BCD\", \"test input\", \"test_output\");\n    // testOp(browser, \"From Base\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Base32\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Base58\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Base62\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Base64\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Base85\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Binary\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Braille\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Case Insensitive Regex\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Charcode\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Decimal\", \"test input\", \"test_output\");\n        // testOp(browser, \"From HTML Entity\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Hex\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Hex Content\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Hexdump\", \"test input\", \"test_output\");\n    // testOp(browser, \"From MessagePack\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Morse Code\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Octal\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Punycode\", \"test input\", \"test_output\");\n        // testOp(browser, \"From Quoted Printable\", \"test input\", \"test_output\");\n    // testOp(browser, \"From UNIX Timestamp\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Fuzzy Match\", \"test input\", \"b:last-child\", \"in\", [\"tein\", 15, 30, 30, 15, -5, -15, -1]);\n        // testOp(browser, \"GOST hash\", \"test input\", \"test_output\");\n        // testOp(browser, \"Generate all hashes\", \"test input\", \"test_output\");\n    // testOp(browser, \"Generate HOTP\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Generate Image\", \"test input\", \"img\", \"\");\n        // testOp(browser, \"Generate Lorem Ipsum\", \"test input\", \"test_output\");\n        // testOp(browser, \"Generate PGP Key Pair\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Generate QR Code\", \"test input\", \"img\", \"\");\n    // testOp(browser, \"Generate TOTP\", \"test input\", \"test_output\");\n        // testOp(browser, \"Generate UUID\", \"test input\", \"test_output\");\n        // testOp(browser, \"Generic Code Beautify\", \"test input\", \"test_output\");\n        // testOp(browser, \"Group IP addresses\", \"test input\", \"test_output\");\n    // testOp(browser, \"Gunzip\", \"test input\", \"test_output\");\n    // testOp(browser, \"Gzip\", \"test input\", \"test_output\");\n        // testOp(browser, \"HAS-160\", \"test input\", \"test_output\");\n        // testOp(browser, \"HMAC\", \"test input\", \"test_output\");\n        // testOp(browser, \"HTML To Text\", \"test input\", \"test_output\");\n        // testOp(browser, \"HTTP request\", \"test input\", \"test_output\");\n        // testOp(browser, \"Hamming Distance\", \"test input\", \"test_output\");\n        // testOp(browser, \"Haversine distance\", \"test input\", \"test_output\");\n        // testOp(browser, \"Head\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Heatmap chart\", \"X Y\\n0 1\\n1 2\", \"svg\", /X/);\n        testOpHtml(browser, \"Hex Density chart\", \"X Y\\n0 1\\n1 2\", \"svg\", /X/);\n        // testOp(browser, \"Hex to Object Identifier\", \"test input\", \"test_output\");\n        // testOp(browser, \"Hex to PEM\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Image Brightness / Contrast\", \"files/Hitchhikers_Guide.jpeg\");\n        testOpImage(browser, \"Image Filter\", \"files/Hitchhikers_Guide.jpeg\");\n        testOpImage(browser, \"Image Hue/Saturation/Lightness\", \"files/Hitchhikers_Guide.jpeg\");\n        testOpImage(browser, \"Image Opacity\", \"files/Hitchhikers_Guide.jpeg\");\n        testOpHtml(browser, \"Index of Coincidence\", \"test input\", \"\", /Index of Coincidence: 0.08333333333333333/);\n        testOpImage(browser, \"Invert Image\", \"files/Hitchhikers_Guide.jpeg\");\n    // testOp(browser, \"JPath expression\", \"test input\", \"test_output\");\n        testOp(browser, \"Jq\", '{\"a\":{\"b\":1}}', '{\"b\":1}', [\".a\"]);\n        testOpHtml(browser, \"JSON Beautify\", \"{a:1}\", \".json-dict .json-literal\", \"1\");\n        // testOp(browser, \"JSON Minify\", \"test input\", \"test_output\");\n    // testOp(browser, \"JSON to CSV\", \"test input\", \"test_output\");\n    // testOp(browser, \"JWT Decode\", \"test input\", \"test_output\");\n    // testOp(browser, \"JWT Sign\", \"test input\", \"test_output\");\n    // testOp(browser, \"JWT Verify\", \"test input\", \"test_output\");\n    // testOp(browser, \"JavaScript Beautify\", \"test input\", \"test_output\");\n    // testOp(browser, \"JavaScript Minify\", \"test input\", \"test_output\");\n    // testOp(browser, \"JavaScript Parser\", \"test input\", \"test_output\");\n        // testOp(browser, \"Jump\", \"test input\", \"test_output\");\n    // testOp(browser, \"Keccak\", \"test input\", \"test_output\");\n        // testOp(browser, \"Label\", \"test input\", \"test_output\");\n    // testOp(browser, \"LM Hash\", \"test input\", \"test output\");\n        // testOp(browser, \"Lorenz\", \"test input\", \"test_output\");\n        // testOp(browser, \"Luhn Checksum\", \"test input\", \"test_output\");\n    // testOp(browser, \"LZ String\", \"test input\", \"test output\");\n    // testOp(browser, \"LZ4 Compress\", \"test input\", \"test output\");\n    // testOp(browser, \"LZ4 Decompress\", \"test input\", \"test output\");\n    // testOp(browser, \"LZMA Compress\", \"test input\", \"test output\");\n    // testOp(browser, \"LZMA Decompress\", \"test input\", \"test output\");\n    // testOp(browser, \"MD2\", \"test input\", \"test_output\");\n    // testOp(browser, \"MD4\", \"test input\", \"test_output\");\n    // testOp(browser, \"MD5\", \"test input\", \"test_output\");\n    // testOp(browser, \"MD6\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Magic\", \"dGVzdF9vdXRwdXQ=\", \"tr:nth-of-type(1) th:nth-of-type(2)\", \"Result snippet\");\n        testOpHtml(browser, \"Magic\", \"dGVzdF9vdXRwdXQ=\", \"tr:nth-of-type(2) td:nth-of-type(2)\", \"test_output\");\n        testOpHtml(browser, \"Magic\", \"dGVzdF9vdXRwdXQ=\", \"tr:nth-of-type(2) td:nth-of-type(1)\", /Base64/);\n        // testOp(browser, \"Mean\", \"test input\", \"test_output\");\n        // testOp(browser, \"Median\", \"test input\", \"test_output\");`\n        // testOp(browser, \"Merge\", \"test input\", \"test_output\");`\n        // testOp(browser, \"Microsoft Script Decoder\", \"test input\", \"test_output\");\n        // testOp(browser, \"Multiple Bombe\", \"test input\", \"test_output\");\n        // testOp(browser, \"Multiply\", \"test input\", \"test_output\");\n        // testOp(browser, \"NOT\", \"test input\", \"test_output\");\n        // testOp(browser, \"Normalise Image\", \"test input\", \"test_output\");\n    // testOp(browser, \"Normalise Unicode\", \"test input\", \"test_output\");\n        // testOp(browser, \"Numberwang\", \"test input\", \"test_output\");\n        // testOp(browser, \"OR\", \"test input\", \"test_output\");\n        // testOp(browser, \"Object Identifier to Hex\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Offset checker\", \"test input\\n\\nbest input\", \".hl5\", \"est input\");\n        testOpFile(browser, \"Optical Character Recognition\", \"files/testocr.png\", false, /This is a lot of 12 point text to test the/, [], 10000);\n        // testOp(browser, \"PEM to Hex\", \"test input\", \"test_output\");\n    // testOp(browser, \"PGP Decrypt\", \"test input\", \"test_output\");\n    // testOp(browser, \"PGP Decrypt and Verify\", \"test input\", \"test_output\");\n    // testOp(browser, \"PGP Encrypt\", \"test input\", \"test_output\");\n    // testOp(browser, \"PGP Encrypt and Sign\", \"test input\", \"test_output\");\n    // testOp(browser, \"PGP Verify\", \"test input\", \"test_output\");\n        // testOp(browser, \"PHP Deserialize\", \"test input\", \"test_output\");\n        // testOp(browser, \"Pad lines\", \"test input\", \"test_output\");\n    // testOp(browser, \"Parse ASN.1 hex string\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Parse colour code\", \"#000\", \".colorpicker-preview\", \"rgb(0, 0, 0)\");\n        testOpHtml(browser, \"Parse DateTime\", \"01/12/2000 13:00:00\", \"\", /Date: Friday 1st December 2000/);\n        // testOp(browser, \"Parse IP range\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Parse IPv4 header\", \"45 c0 00 c4 02 89 00 00 ff 11　1e 8c c0 a8 0c 01 c0 a8 0c 02\", \"tr:last-child td:last-child\", \"192.168.12.2\");\n        // testOp(browser, \"Parse IPv6 address\", \"test input\", \"test_output\");\n    // testOp(browser, \"Parse ObjectID timestamp\", \"test input\", \"test_output\");\n    // testOp(browser, \"Parse QR Code\", \"test input\", \"test_output\");\n        // testOp(browser, \"Parse SSH Host Key\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Parse TCP\", \"c2eb0050a138132e70dc9fb9501804025ea70000\", \"tr:nth-of-type(2) td:last-child\", \"49899\");\n        // testOp(browser, \"Parse TLV\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Parse UDP\", \"04 89 00 35 00 2c 01 01\", \"tr:last-child td:last-child\", \"0x0101\");\n        // testOp(browser, \"Parse UNIX file permissions\", \"test input\", \"test_output\");\n        // testOp(browser, \"Parse URI\", \"test input\", \"test_output\");\n    // testOp(browser, \"Parse User Agent\", \"test input\", \"test_output\");\n        // testOp(browser, \"Parse X.509 certificate\", \"test input\", \"test_output\");\n        testOpFile(browser, \"Play Media\", \"files/mp3example.mp3\", \"audio\", \"\");\n        // testOp(browser, \"Power Set\", \"test input\", \"test_output\");\n    // testOp(browser, \"Protobuf Decode\", \"test input\", \"test_output\");\n        // testOp(browser, \"Pseudo-Random Number Generator\", \"test input\", \"test_output\");\n    // testOp(browser, \"RC2 Decrypt\", \"test input\", \"test_output\");\n    // testOp(browser, \"RC2 Encrypt\", \"test input\", \"test_output\");\n    // testOp(browser, \"RC4\", \"test input\", \"test_output\");\n    // testOp(browser, \"RC4 Drop\", \"test input\", \"test_output\");\n        // testOp(browser, \"RIPEMD\", \"test input\", \"test_output\");\n        // testOp(browser, \"ROT13\", \"test input\", \"test_output\");\n        // testOp(browser, \"ROT47\", \"test input\", \"test_output\");\n        // testOp(browser, \"ROT8000\", \"test input\", \"test_output\");\n        // testOp(browser, \"Rail Fence Cipher Decode\", \"test input\", \"test_output\");\n        // testOp(browser, \"Rail Fence Cipher Encode\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Randomize Colour Palette\", \"files/Hitchhikers_Guide.jpeg\");\n    // testOp(browser, \"Raw Deflate\", \"test input\", \"test_output\");\n    // testOp(browser, \"Raw Inflate\", \"test input\", \"test_output\");\n        // testOp(browser, \"Register\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Regular expression\", \"The cat sat on the mat\", \".hl2:last-child\", \"mat\", [\"User defined\", \".at\", true, true, false, false, false, false, \"Highlight matches\"]);\n        // testOp(browser, \"Remove Diacritics\", \"test input\", \"test_output\");\n        // testOp(browser, \"Remove EXIF\", \"test input\", \"test_output\");\n        // testOp(browser, \"Remove line numbers\", \"test input\", \"test_output\");\n        // testOp(browser, \"Remove null bytes\", \"test input\", \"test_output\");\n        // testOp(browser, \"Remove whitespace\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Render Image\", \"files/Hitchhikers_Guide.jpeg\");\n        testOpHtml(browser, \"Render Markdown\", \"# test input\", \"h1\", \"test input\");\n        testOpImage(browser, \"Resize Image\", \"files/Hitchhikers_Guide.jpeg\");\n        // testOp(browser, \"Return\", \"test input\", \"test_output\");\n        // testOp(browser, \"Reverse\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Rotate Image\", \"files/Hitchhikers_Guide.jpeg\");\n        // testOp(browser, \"Rotate left\", \"test input\", \"test_output\");\n        // testOp(browser, \"Rotate right\", \"test input\", \"test_output\");\n    // testOp(browser, \"Scrypt\", \"test input\", \"test output\");\n    // testOp(browser, \"SHA0\", \"test input\", \"test_output\");\n    // testOp(browser, \"SHA1\", \"test input\", \"test_output\");\n    // testOp(browser, \"SHA2\", \"test input\", \"test_output\");\n    // testOp(browser, \"SHA3\", \"test input\", \"test_output\");\n        // testOp(browser, \"SQL Beautify\", \"test input\", \"test_output\");\n        // testOp(browser, \"SQL Minify\", \"test input\", \"test_output\");\n    // testOp(browser, \"SSDEEP\", \"test input\", \"test_output\");\n        // testOp(browser, \"SUB\", \"test input\", \"test_output\");\n        // testOp(browser, \"Scan for Embedded Files\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Scatter chart\", \"a b\\n1 2\", \"svg\", /a/);\n        // testOp(browser, \"Scrypt\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Series chart\", \"1 2 3\\n4 5 6\", \"svg\", /3/);\n        // testOp(browser, \"Set Difference\", \"test input\", \"test_output\");\n        // testOp(browser, \"Set Intersection\", \"test input\", \"test_output\");\n        // testOp(browser, \"Set Union\", \"test input\", \"test_output\");\n    // testOp(browser, \"Shake\", \"test input\", \"test_output\");\n        testOpImage(browser, \"Sharpen Image\", \"files/Hitchhikers_Guide.jpeg\");\n        testOpHtml(browser, \"Show Base64 offsets\", \"test input\", \"span:nth-last-of-type(2)\", \"B\");\n        testOpHtml(browser, \"Show on map\", \"51.5007° N, 0.1246° W\", \"#presentedMap .leaflet-popup-content\", \"51.5007,-0.1246\");\n        // testOp(browser, \"Sleep\", \"test input\", \"test_output\");\n    // testOp(browser, \"SM3\", \"test input\", \"test output\");\n        // testOp(browser, \"Snefru\", \"test input\", \"test_output\");\n        // testOp(browser, \"Sort\", \"test input\", \"test_output\");\n        // testOp(browser, \"Split\", \"test input\", \"test_output\");\n        // testOpImage(browser, \"Split Colour Channels\", \"files/Hitchhikers_Guide.jpeg\");\n        // testOp(browser, \"Standard Deviation\", \"test input\", \"test_output\");\n        // testOp(browser, \"Streebog\", \"test input\", \"test_output\");\n        // testOp(browser, \"Strings\", \"test input\", \"test_output\");\n        // testOp(browser, \"Strip HTML tags\", \"test input\", \"test_output\");\n        // testOp(browser, \"Strip HTTP headers\", \"test input\", \"test_output\");\n        // testOp(browser, \"Subsection\", \"test input\", \"test_output\");\n        // testOp(browser, \"Substitute\", \"test input\", \"test_output\");\n        // testOp(browser, \"Subtract\", \"test input\", \"test_output\");\n    // testOp(browser, \"Sum\", \"test input\", \"test_output\");\n        // testOp(browser, \"Swap endianness\", \"test input\", \"test_output\");\n        // testOp(browser, \"Symmetric Difference\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"Syntax highlighter\", \"var a = [4,5,6]\", \".hljs-selector-attr\", \"[4,5,6]\");\n        // testOp(browser, \"TCP/IP Checksum\", \"test input\", \"test_output\");\n        // testOp(browser, \"Tail\", \"test input\", \"test_output\");\n        // testOp(browser, \"Take bytes\", \"test input\", \"test_output\");\n        testOp(browser, \"Tar\", \"test input\", /^file\\.txt\\x00{92}/);\n        testOpHtml(browser, \"Text Encoding Brute Force\", \"test input\", \"tr:nth-of-type(4) td:last-child\", /t\\u2400e\\u2400s\\u2400t\\u2400/);\n        // testOp(browser, \"To BCD\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Base\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Base32\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Base58\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Base62\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Base64\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Base85\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Binary\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Braille\", \"test input\", \"test_output\");\n    // testOp(browser, \"To Camel case\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Case Insensitive Regex\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Charcode\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Decimal\", \"test input\", \"test_output\");\n        // testOp(browser, \"To HTML Entity\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Hex\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Hex Content\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Hexdump\", \"test input\", \"test_output\");\n    // testOp(browser, \"To Kebab case\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Lower case\", \"test input\", \"test_output\");\n    // testOp(browser, \"To MessagePack\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Morse Code\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Octal\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Punycode\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Quoted Printable\", \"test input\", \"test_output\");\n    // testOp(browser, \"To Snake case\", \"test input\", \"test_output\");\n        testOpHtml(browser, \"To Table\", \"a,b,c\\n1,2,3\", \"\", /| a | b | c |/);\n        // testOp(browser, \"To UNIX Timestamp\", \"test input\", \"test_output\");\n        // testOp(browser, \"To Upper case\", \"test input\", \"test_output\");\n        // testOp(browser, \"Translate DateTime Format\", \"test input\", \"test_output\");\n        // testOp(browser, \"Triple DES Decrypt\", \"test input\", \"test_output\");\n        // testOp(browser, \"Triple DES Encrypt\", \"test input\", \"test_output\");\n        // testOp(browser, \"Typex\", \"test input\", \"test_output\");\n        // testOp(browser, \"UNIX Timestamp to Windows Filetime\", \"test input\", \"test_output\");\n        // testOp(browser, \"URL Decode\", \"test input\", \"test_output\");\n        // testOp(browser, \"URL Encode\", \"test input\", \"test_output\");\n        // testOp(browser, \"Unescape string\", \"test input\", \"test_output\");\n        // testOp(browser, \"Unescape Unicode Characters\", \"test input\", \"test_output\");\n        // testOp(browser, \"Unique\", \"test input\", \"test_output\");\n        testOpHtml(browser, [\"Tar\", \"Untar\"], \"test input\", \".float-right\", /10 bytes/);\n        testOpHtml(browser, [\"Zip\", \"Unzip\"], \"test input\", \"#files span.float-right\", /10 bytes/);\n        // testOp(browser, \"VarInt Decode\", \"test input\", \"test_output\");\n        // testOp(browser, \"VarInt Encode\", \"test input\", \"test_output\");\n        testOpImage(browser, \"View Bit Plane\", \"files/Hitchhikers_Guide.jpeg\");\n        // testOp(browser, \"Vigenère Decode\", \"test input\", \"test_output\");\n        // testOp(browser, \"Vigenère Encode\", \"test input\", \"test_output\");\n        testOp(browser, \"Whirlpool\", \"test input\", \"8a0ee6885ba241353d17cbbe5f06538a7f04c8c955d376c20d6233fd4dd41aaffd13291447090ce781b5f940da266ed6d02cf8b79d4867065d10bdfc04166f38\");\n        // testOp(browser, \"Windows Filetime to UNIX Timestamp\", \"test input\", \"test_output\");\n        testOp(browser, \"XKCD Random Number\", \"test input\", \"4\");\n    // testOp(browser, \"XML Beautify\", \"test input\", \"test_output\");\n    // testOp(browser, \"XML Minify\", \"test input\", \"test_output\");\n        // testOp(browser, \"XOR\", \"test input\", \"test_output\");\n        // testOp(browser, \"XOR Brute Force\", \"test input\", \"test_output\");\n    // testOp(browser, \"XPath expression\", \"test input\", \"test_output\");\n    // testOp(browser, \"YARA Rules\", \"test input\", \"test_output\");\n        testOp(browser, \"Zip\", \"test input\", /^PK\\u0003\\u0004\\u0014\\u0000{3}/);\n        // testOp(browser, \"Zlib Deflate\", \"test input\", \"test_output\");\n        // testOp(browser, \"Zlib Inflate\", \"test input\", \"test_output\");\n    },\n\n\n    after: browser => {\n        browser.end();\n    }\n};\n\n\n/** @function\n * Clears the current recipe and bakes a new operation.\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|Array<string>} opName - name of operation to be tested, array for multiple ops\n * @param {string} input - input text for test\n * @param {Array<string>|Array<Array<string>>} [args=[]] - arguments, nested if multiple ops\n */\nfunction bakeOp(browser, opName, input, args=[]) {\n    browser.perform(function() {\n        console.log(`Current test: ${opName}`);\n    });\n    utils.loadRecipe(browser, opName, input, args);\n    browser.waitForElementVisible(\"#stale-indicator\", 5000);\n    utils.bake(browser);\n}\n\n/** @function\n * Clears the current recipe and tests a new operation.\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|Array<string>} opName - name of operation to be tested, array for multiple ops\n * @param {string} input - input text\n * @param {string|RegExp} output - expected output\n * @param {Array<string>|Array<Array<string>>} [args=[]] - arguments, nested if multiple ops\n */\nfunction testOp(browser, opName, input, output, args=[]) {\n    bakeOp(browser, opName, input, args);\n    utils.expectOutput(browser, output, true);\n}\n\n/** @function\n * Clears the current recipe and tests a new operation with HTML output.\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|Array<string>} opName - name of operation to be tested array for multiple ops\n * @param {string} input - input text\n * @param {string} cssSelector - CSS selector for HTML output\n * @param {string|RegExp} output - expected output\n * @param {Array<string>|Array<Array<string>>} [args=[]] - arguments, nested if multiple ops\n */\nfunction testOpHtml(browser, opName, input, cssSelector, output, args=[]) {\n    bakeOp(browser, opName, input, args);\n\n    if (typeof output === \"string\") {\n        browser.expect.element(\"#output-html \" + cssSelector).text.that.equals(output);\n    } else if (output instanceof RegExp) {\n        browser.expect.element(\"#output-html \" + cssSelector).text.that.matches(output);\n    }\n}\n\n/** @function\n * Clears the current recipe and tests a new Image-based operation.\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|Array<string>} opName - name of operation to be tested array for multiple ops\n * @param {string} filename - filename of image file from samples directory\n * @param {Array<string>|Array<Array<string>>} [args=[]] - arguments, nested if multiple ops\n */\nfunction testOpImage(browser, opName, filename, args=[]) {\n    browser.perform(function() {\n        console.log(`Current test: ${opName}`);\n    });\n    utils.loadRecipe(browser, opName, \"\", args);\n    utils.uploadFile(browser, filename);\n    browser.waitForElementVisible(\"#stale-indicator\", 5000);\n    utils.bake(browser);\n\n    browser\n        .waitForElementVisible(\"#output-html img\")\n        .expect.element(\"#output-html img\").to.have.css(\"width\").which.matches(/^[^0]\\d*px/);\n}\n\n/** @function\n * Clears the current recipe and tests a new File-based operation.\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|Array<string>} opName - name of operation to be tested array for multiple ops\n * @param {string} filename - filename of file from samples directory\n * @param {string|boolean} cssSelector - CSS selector for HTML output or false for normal text output\n * @param {string|RegExp} output - expected output\n * @param {Array<string>|Array<Array<string>>} [args=[]] - arguments, nested if multiple ops\n * @param {number} [waitWindow=1000] - The number of milliseconds to wait for the output to be correct\n */\nfunction testOpFile(browser, opName, filename, cssSelector, output, args=[], waitWindow=1000) {\n    browser.perform(function() {\n        console.log(`Current test: ${opName}`);\n    });\n    utils.loadRecipe(browser, opName, \"\", args);\n    utils.uploadFile(browser, filename);\n    browser.pause(100).waitForElementVisible(\"#stale-indicator\", 5000);\n    utils.bake(browser);\n\n    if (!cssSelector) {\n        // Text output\n        utils.expectOutput(browser, output, true, waitWindow);\n    } else if (typeof output === \"string\") {\n        // HTML output - string match\n        browser.expect.element(\"#output-html \" + cssSelector).text.that.equals(output);\n    } else if (output instanceof RegExp) {\n        // HTML output - RegEx match\n        browser.expect.element(\"#output-html \" + cssSelector).text.that.matches(output);\n    }\n}\n"
  },
  {
    "path": "tests/browser/browserUtils.js",
    "content": "/**\n * Utility functions for browser tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\n/** @function\n * Clears the recipe and input\n *\n * @param {Browser} browser - Nightwatch client\n */\nfunction clear(browser) {\n    browser\n        .useCss()\n        .click(\"#clr-recipe\")\n        .click(\"#clr-io\")\n        .waitForElementNotPresent(\"#rec-list li.operation\")\n        .expect.element(\"#input-text .cm-content\").text.that.equals(\"\");\n}\n\n/** @function\n * Sets the input to the desired string\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string} input - The text to populate the input with\n * @param {boolean} [type=true] - Whether to type the characters in by using sendKeys,\n *      or to set the value of the editor directly (useful for special characters)\n */\nfunction setInput(browser, input, type=true) {\n    clear(browser);\n    if (type) {\n        browser\n            .useCss()\n            .sendKeys(\"#input-text .cm-content\", input)\n            .pause(100);\n    } else {\n        browser.execute(text => {\n            window.app.setInput(text);\n        }, [input]);\n        browser.pause(100);\n    }\n    expectInput(browser, input);\n}\n\n/** @function\n * Triggers a bake\n *\n * @param {Browser} browser - Nightwatch client\n */\nfunction bake(browser) {\n    browser\n        // Ensure we're not currently busy\n        .waitForElementNotVisible(\"#output-loader\", 5000)\n        .expect.element(\"#bake span\").text.to.equal(\"BAKE!\");\n\n    browser\n        .click(\"#bake\")\n        .waitForElementNotVisible(\"#stale-indicator\", 5000)\n        .waitForElementNotVisible(\"#output-loader\", 5000);\n}\n\n/** @function\n * Sets the character encoding in the input or output\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string} io - Either \"input\" or \"output\"\n * @param {string} enc - The encoding to be set\n */\nfunction setChrEnc(browser, io, enc) {\n    io = `#${io}-text`;\n    browser\n        .useCss()\n        .waitForElementNotVisible(\"#snackbar-container\", 6000)\n        .click(io + \" .chr-enc-value\")\n        .waitForElementVisible(io + \" .chr-enc-select .cm-status-bar-select-scroll\")\n        .click(\"link text\", enc)\n        .waitForElementNotVisible(io + \" .chr-enc-select .cm-status-bar-select-scroll\")\n        .expect.element(io + \" .chr-enc-value\").text.that.equals(enc);\n}\n\n/** @function\n * Sets the end of line sequence in the input or output\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string} io - Either \"input\" or \"output\"\n * @param {string} eol - The sequence to set\n */\nfunction setEOLSeq(browser, io, eol) {\n    io = `#${io}-text`;\n    browser\n        .useCss()\n        .waitForElementNotVisible(\"#snackbar-container\", 6000)\n        .click(io + \" .eol-value\")\n        .waitForElementVisible(io + \" .eol-select .cm-status-bar-select-content\")\n        .click(`${io} .cm-status-bar-select-content a[data-val=${eol}]`)\n        .waitForElementNotVisible(io + \" .eol-select .cm-status-bar-select-content\")\n        .expect.element(io + \" .eol-value\").text.that.equals(eol);\n}\n\n/** @function\n * Copies whatever is currently selected\n *\n * @param {Browser} browser - Nightwatch client\n */\nfunction copy(browser) {\n    browser.perform(function() {\n        const actions = this.actions({async: true});\n\n        // Ctrl + Ins used as this works on Windows, Linux and Mac\n        return actions\n            .keyDown(browser.Keys.CONTROL)\n            .keyDown(browser.Keys.INSERT)\n            .keyUp(browser.Keys.INSERT)\n            .keyUp(browser.Keys.CONTROL);\n    });\n}\n\n/** @function\n * Pastes into the target element\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string} el - Target element selector\n */\nfunction paste(browser, el) {\n    browser\n        .click(el)\n        .perform(function() {\n            const actions = this.actions({async: true});\n\n            // Shift + Ins used as this works on Windows, Linux and Mac\n            return actions\n                .keyDown(browser.Keys.SHIFT)\n                .keyDown(browser.Keys.INSERT)\n                .keyUp(browser.Keys.INSERT)\n                .keyUp(browser.Keys.SHIFT);\n        })\n        .pause(100);\n}\n\n/** @function\n * Loads a recipe and input\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|Array<string>} opName - name of operation to be loaded, array for multiple ops\n * @param {string} input - input text for test\n * @param {Array<string>|Array<Array<string>>} args - arguments, nested if multiple ops\n */\nfunction loadRecipe(browser, opName, input, args) {\n    let recipeConfig;\n\n    if (typeof(opName) === \"string\") {\n        recipeConfig = JSON.stringify([{\n            \"op\": opName,\n            \"args\": args\n        }]);\n    } else if (opName instanceof Array) {\n        recipeConfig = JSON.stringify(\n            opName.map((op, i) => {\n                return {\n                    op: op,\n                    args: args.length ? args[i] : []\n                };\n            })\n        );\n    } else {\n        throw new Error(\"Invalid operation type. Must be string or array of strings. Received: \" + typeof(opName));\n    }\n\n    setInput(browser, input, false);\n    browser\n        .urlHash(\"recipe=\" + recipeConfig)\n        .waitForElementPresent(\"#rec-list li.operation\");\n}\n\n/** @function\n * Tests whether the output matches a given value\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|RegExp} expected - The expected output value\n * @param {boolean} [waitNotNull=false] - Wait for the output to not be empty before testing the value\n * @param {number} [waitWindow=1000] - The number of milliseconds to wait for the output to be correct\n */\nfunction expectOutput(browser, expected, waitNotNull=false, waitWindow=1000) {\n    if (waitNotNull && expected !== \"\") {\n        browser.waitUntil(async function() {\n            const output = await this.execute(function() {\n                return window.app.manager.output.outputEditorView.state.doc.toString();\n            });\n            return output.length;\n        }, waitWindow);\n    }\n\n    browser.execute(expected => {\n        return window.app.manager.output.outputEditorView.state.doc.toString();\n    }, [expected], function({value}) {\n        if (expected instanceof RegExp) {\n            browser.expect(value).match(expected);\n        } else {\n            browser.expect(value).to.be.equal(expected);\n        }\n    });\n}\n\n/** @function\n * Tests whether the input matches a given value\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string|RegExp} expected - The expected input value\n */\nfunction expectInput(browser, expected) {\n    browser.execute(expected => {\n        return window.app.manager.input.inputEditorView.state.doc.toString();\n    }, [expected], function({value}) {\n        if (expected instanceof RegExp) {\n            browser.expect(value).match(expected);\n        } else {\n            browser.expect(value).to.be.equal(expected);\n        }\n    });\n}\n\n/** @function\n * Uploads a file using the #open-file input\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string} filename - A path to a file in the samples directory\n */\nfunction uploadFile(browser, filename) {\n    const filepath = require(\"path\").resolve(__dirname + \"/../samples/\" + filename);\n\n    // The file input cannot be interacted with by nightwatch while it is hidden,\n    // so we temporarily expose it for the purposes of this test.\n    browser.execute(() => {\n        document.getElementById(\"open-file\").style.display = \"block\";\n    });\n    browser\n        .pause(100)\n        .setValue(\"#open-file\", filepath)\n        .pause(100);\n    browser.execute(() => {\n        document.getElementById(\"open-file\").style.display = \"none\";\n    });\n    browser.waitForElementVisible(\"#input-text .cm-file-details\");\n}\n\n/** @function\n * Uploads a folder using the #open-folder input\n *\n * @param {Browser} browser - Nightwatch client\n * @param {string} foldername - A path to a folder in the samples directory\n */\nfunction uploadFolder(browser, foldername) {\n    const folderpath = require(\"path\").resolve(__dirname + \"/../samples/\" + foldername);\n\n    // The folder input cannot be interacted with by nightwatch while it is hidden,\n    // so we temporarily expose it for the purposes of this test.\n    browser.execute(() => {\n        document.getElementById(\"open-folder\").style.display = \"block\";\n    });\n    browser\n        .pause(100)\n        .setValue(\"#open-folder\", folderpath)\n        .pause(500);\n    browser.execute(() => {\n        document.getElementById(\"open-folder\").style.display = \"none\";\n    });\n    browser.waitForElementVisible(\"#input-text .cm-file-details\");\n}\n\n\nmodule.exports = {\n    clear: clear,\n    setInput: setInput,\n    bake: bake,\n    setChrEnc: setChrEnc,\n    setEOLSeq: setEOLSeq,\n    copy: copy,\n    paste: paste,\n    loadRecipe: loadRecipe,\n    expectOutput: expectOutput,\n    expectInput: expectInput,\n    uploadFile: uploadFile,\n    uploadFolder: uploadFolder\n};\n"
  },
  {
    "path": "tests/lib/TestRegister.mjs",
    "content": "/**\n * TestRegister.js\n *\n * This is so individual files can register their tests in one place, and\n * ensure that they will get run by the frontend.\n *\n * @author tlwr [toby@toby.codes]\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport Chef from \"../../src/core/Chef.mjs\";\nimport Utils from \"../../src/core/Utils.mjs\";\nimport cliProgress from \"cli-progress\";\nimport log from \"loglevel\";\n\n/**\n * Object to store and run the list of tests.\n *\n * @class\n * @constructor\n */\nclass TestRegister {\n\n    /**\n     * initialise with no tests\n     */\n    constructor() {\n        this.tests = [];\n        this.apiTests = [];\n    }\n\n    /**\n     * Add a list of tests to the register.\n     *\n     * @param {Object[]} tests\n     */\n    addTests(tests) {\n        this.tests = this.tests.concat(tests);\n    }\n\n    /**\n     * Add a list of api tests to the register\n     * @param {Object[]} tests\n     */\n    addApiTests(tests) {\n        this.apiTests = this.apiTests.concat(tests);\n    }\n\n    /**\n     * Runs all the tests in the register.\n     */\n    async runTests () {\n        // Turn off logging to avoid messy errors\n        log.setLevel(\"silent\", false);\n\n        const progBar = new cliProgress.SingleBar({\n            format: formatter,\n            stopOnComplete: true\n        }, cliProgress.Presets.shades_classic);\n        const testResults = [];\n\n        console.log(\"Running operation tests...\");\n        progBar.start(this.tests.length, 0, {\n            msg: \"Setting up\"\n        });\n\n        for (const test of this.tests) {\n            progBar.update(testResults.length, {\n                msg: test.name\n            });\n\n            const chef = new Chef();\n            const result = await chef.bake(\n                test.input,\n                test.recipeConfig,\n                { returnType: \"string\" }\n            );\n\n            const ret = {\n                test: test,\n                status: null,\n                output: null,\n                duration: result.duration\n            };\n\n            if (result.error) {\n                if (test.expectedError) {\n                    if (result.error.displayStr === test.expectedOutput) {\n                        ret.status = \"passing\";\n                    } else {\n                        ret.status = \"failing\";\n                        ret.output = [\n                            \"Expected\",\n                            \"\\t\" + test.expectedOutput.replace(/\\n/g, \"\\n\\t\"),\n                            \"Received\",\n                            \"\\t\" + result.error.displayStr.replace(/\\n/g, \"\\n\\t\"),\n                        ].join(\"\\n\");\n                    }\n                } else {\n                    ret.status = \"erroring\";\n                    ret.output = result.error.displayStr;\n                }\n            } else {\n                if (test.expectedError) {\n                    ret.status = \"failing\";\n                    ret.output = \"Expected an error but did not receive one.\";\n                } else if (result.result === test.expectedOutput) {\n                    ret.status = \"passing\";\n                } else if (\"expectedMatch\" in test && test.expectedMatch.test(result.result)) {\n                    ret.status = \"passing\";\n                } else if (\"unexpectedMatch\" in test && !test.unexpectedMatch.test(result.result)) {\n                    ret.status = \"passing\";\n                } else {\n                    ret.status = \"failing\";\n                    const expected = test.expectedOutput ? test.expectedOutput :\n                        test.expectedMatch ? test.expectedMatch.toString() :\n                            test.unexpectedMatch ? \"to not find \" + test.unexpectedMatch.toString() :\n                                \"unknown\";\n                    ret.output = [\n                        \"Expected\",\n                        \"\\t\" + expected.replace(/\\n/g, \"\\n\\t\"),\n                        \"Received\",\n                        \"\\t\" + result.result.replace(/\\n/g, \"\\n\\t\"),\n                    ].join(\"\\n\");\n                }\n            }\n\n            testResults.push(ret);\n            progBar.increment();\n        }\n\n        // Turn logging back on\n        log.setLevel(\"info\", false);\n\n        return testResults;\n    }\n\n    /**\n     * Run all api related tests and wrap results in report format\n     */\n    async runApiTests() {\n        const progBar = new cliProgress.SingleBar({\n            format: formatter,\n            stopOnComplete: true\n        }, cliProgress.Presets.shades_classic);\n        const testResults = [];\n\n        console.log(\"Running Node API tests...\");\n        progBar.start(this.apiTests.length, 0, {\n            msg: \"Setting up\"\n        });\n\n        global.TESTING = true;\n        for (const test of this.apiTests) {\n            progBar.update(testResults.length, {\n                msg: test.name\n            });\n\n            const result = {\n                test: test,\n                status: null,\n                output: null\n            };\n            try {\n                await test.run();\n                result.status = \"passing\";\n            } catch (e) {\n                result.status = \"erroring\";\n                result.output = `${e.message}\\nError: ${e.stack}`;\n            }\n\n            testResults.push(result);\n            progBar.increment();\n        }\n\n        return testResults;\n    }\n}\n\n\n/**\n * Formatter for the progress bar\n *\n * @param {Object} options\n * @param {Object} params\n * @param {Object} payload\n * @returns {string}\n */\nfunction formatter(options, params, payload) {\n    const bar = options.barCompleteString.substr(0, Math.round(params.progress * options.barsize)) +\n        options.barIncompleteString.substr(0, Math.round((1-params.progress) * options.barsize));\n\n    const percentage = Math.floor(params.progress * 100),\n        duration = Math.floor((Date.now() - params.startTime) / 1000);\n\n    let testName = payload.msg ? payload.msg : \"\";\n    if (params.value >= params.total) testName = \"Tests completed\";\n    testName = Utils.truncate(testName, 25).padEnd(25, \" \");\n\n    return `${testName} ${bar} ${params.value}/${params.total} | ${percentage}% | Duration: ${duration}s`;\n}\n\n// Export an instance to make a singleton\nexport default new TestRegister();\n"
  },
  {
    "path": "tests/lib/utils.mjs",
    "content": "/**\n * Utils for test suite\n *\n * @author d98762625@gmail.com\n * @author tlwr [toby@toby.codes]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\n\n/**\n * Helper function to convert a status to an icon.\n *\n * @param {string} status\n * @returns {string}\n */\nfunction statusToIcon(status) {\n    return {\n        erroring: \"🔥\",\n        failing: \"❌\",\n        passing: \"✔️️\",\n    }[status] || \"?\";\n}\n\n/**\n * Counts test statuses.\n *\n * @param {Object} testStatus\n * @param {Object} testResult\n */\nfunction handleTestResult(testStatus, testResult) {\n    testStatus.allTestsPassing = testStatus.allTestsPassing && testResult.status === \"passing\";\n    testStatus.counts[testResult.status] = (testStatus.counts[testResult.status] || 0) + 1;\n    testStatus.counts.total += 1;\n\n    if (testResult.duration > 2000) {\n        console.log(`'${testResult.test.name}' took ${(testResult.duration / 1000).toFixed(1)}s to complete`);\n    }\n}\n\n/**\n * Log each test result, count tests and failures.\n *\n * @param {Object} testStatus - object describing test run data\n * @param {Object[]} results - results from TestRegister\n */\nexport function logTestReport(testStatus, results) {\n    results.forEach(r => handleTestResult(testStatus, r));\n\n    console.log();\n    for (const testStatusCount in testStatus.counts) {\n        const count = testStatus.counts[testStatusCount];\n        if (count > 0) {\n            console.log(testStatusCount.toUpperCase() + \"\\t\" + count);\n        }\n    }\n    console.log();\n\n    // Print error messages for tests that didn't pass\n    results.filter(res => res.status !== \"passing\").forEach(testResult => {\n        console.log([\n            statusToIcon(testResult.status),\n            testResult.test.name\n        ].join(\"  \"));\n\n        if (testResult.output) {\n            console.log(\n                testResult.output\n                    .trim()\n                    .replace(/^/, \"\\t\")\n                    .replace(/\\n/g, \"\\n\\t\")\n            );\n        }\n    });\n    console.log();\n\n    process.exit(testStatus.allTestsPassing ? 0 : 1);\n}\n\n/**\n * Fail if the process takes longer than 60 seconds.\n */\nexport function setLongTestFailure() {\n    const timeLimit = 120;\n    setTimeout(function() {\n        console.log(`Tests took longer than ${timeLimit} seconds to run, returning.`);\n        process.exit(1);\n    }, timeLimit * 1000);\n}\n"
  },
  {
    "path": "tests/node/assertionHandler.mjs",
    "content": "/**\n * assertionHandler.mjs\n *\n * Pair native node assertions with a description for\n * the benefit of the TestRegister.\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n/* eslint no-console: 0 */\n\n\n/**\n * Print useful stack on error\n */\nconst wrapRun = (run) => async () => {\n    try {\n        await run();\n    } catch (e) {\n        console.dir(e);\n        throw e;\n    }\n};\n\n\n/**\n * it - wrapper for assertions to provide a helpful description\n * to the TestRegister\n * @namespace ApiTests\n * @param {String} description - The description of the test\n * @param {Function} assertion - The test\n *\n * @example\n * // One assertion\n * it(\"should run one assertion\", () => assert.equal(1,1))\n *\n * @example\n * // multiple assertions\n * it(\"should handle multiple assertions\", () => {\n *   assert.equal(1,1)\n *   assert.notEqual(3,4)\n * })\n *\n * @example\n * // async assertions\n * it(\"should handle async\", async () => {\n *      let r = await asyncFunc()\n *      assert(r)\n * })\n */\nexport function it(name, run) {\n    return {\n        name: `Node API: ${name}`,\n        run: wrapRun(run),\n    };\n}\n\nexport default it;\n"
  },
  {
    "path": "tests/node/consumers/cjs-consumer.js",
    "content": "/**\n * Tests to ensure that a consuming app can use CJS require\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nconst assert = require(\"assert\");\n\nrequire(\"cyberchef\").then(chef => {\n\n    const d = chef.bake(\"Testing, 1 2 3\", [\n        chef.toHex,\n        chef.reverse,\n        {\n            op: chef.unique,\n            args: {\n                delimiter: \"Space\",\n            }\n        },\n        {\n            op: chef.multiply,\n            args: {\n                delimiter: \"Space\",\n            }\n        }\n    ]);\n\n    assert.equal(d.value, \"630957449041920\");\n\n});\n"
  },
  {
    "path": "tests/node/consumers/esm-consumer.mjs",
    "content": "/**\n * Tests to ensure that a consuming app can use ESM imports\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport assert from \"assert\";\nimport chef from \"cyberchef\";\nimport { bake, toHex, reverse, unique, multiply } from \"cyberchef\";\n\nconst a = bake(\"Testing, 1 2 3\", [\n    toHex,\n    reverse,\n    {\n        op: unique,\n        args: {\n            delimiter: \"Space\",\n        }\n    },\n    {\n        op: multiply,\n        args: {\n            delimiter: \"Space\",\n        }\n    }\n]);\n\nassert.equal(a.value, \"630957449041920\");\n\nconst b = chef.bake(\"Testing, 1 2 3\", [\n    chef.toHex,\n    chef.reverse,\n    {\n        op: chef.unique,\n        args: {\n            delimiter: \"Space\",\n        }\n    },\n    {\n        op: chef.multiply,\n        args: {\n            delimiter: \"Space\",\n        }\n    }\n]);\n\nassert.equal(b.value, \"630957449041920\");\n"
  },
  {
    "path": "tests/node/index.mjs",
    "content": "/* eslint no-console: 0 */\n\n/**\n * Node API Test Runner\n *\n * @author d98762625 [d98762625@gmail.com]\n * @author tlwr [toby@toby.codes]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport {\n    setLongTestFailure,\n    logTestReport,\n} from \"../lib/utils.mjs\";\n\nimport TestRegister from \"../lib/TestRegister.mjs\";\nimport \"./tests/nodeApi.mjs\";\nimport \"./tests/operations.mjs\";\nimport \"./tests/File.mjs\";\nimport \"./tests/Dish.mjs\";\nimport \"./tests/NodeDish.mjs\";\nimport \"./tests/Utils.mjs\";\nimport \"./tests/Categories.mjs\";\nimport \"./tests/lib/BigIntUtils.mjs\";\n\nconst testStatus = {\n    allTestsPassing: true,\n    counts: {\n        total: 0,\n    }\n};\n\nsetLongTestFailure();\n\nconst logOpsTestReport = logTestReport.bind(null, testStatus);\n\n(async function() {\n    const results = await TestRegister.runApiTests();\n    logOpsTestReport(results);\n})();\n"
  },
  {
    "path": "tests/node/tests/Categories.mjs",
    "content": "import TestRegister from \"../../lib/TestRegister.mjs\";\nimport Categories from \"../../../src/core/config/Categories.json\" assert {type: \"json\"};\nimport OperationConfig from \"../../../src/core/config/OperationConfig.json\" assert {type: \"json\"};\nimport it from \"../assertionHandler.mjs\";\nimport assert from \"assert\";\n\nTestRegister.addApiTests([\n    it(\"Categories: operations should be in a category\", () => {\n        const catOps = [];\n        Categories.forEach(cat => {\n            catOps.push(...cat.ops);\n        });\n\n        for (const op in OperationConfig) {\n            assert(catOps.includes(op), `'${op}' operation is not present in any category`);\n        }\n    }),\n\n]);\n"
  },
  {
    "path": "tests/node/tests/Dish.mjs",
    "content": "import TestRegister from \"../../lib/TestRegister.mjs\";\nimport Dish from \"../../../src/core/Dish.mjs\";\nimport it from \"../../node/assertionHandler.mjs\";\nimport assert from \"assert\";\n\nTestRegister.addApiTests([\n    it(\"Dish - presentAs: should exist\", () => {\n        const dish = new Dish();\n        assert(dish.presentAs);\n    }),\n\n]);\n"
  },
  {
    "path": "tests/node/tests/File.mjs",
    "content": "import assert from \"assert\";\nimport it from \"../assertionHandler.mjs\";\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport File from \"../../../src/node/File.mjs\";\nimport {zip, Dish} from \"../../../src/node/index.mjs\";\n\nTestRegister.addApiTests([\n    it(\"File: should exist\", () => {\n        assert(File);\n    }),\n\n    it(\"File: Should have same properties as DOM File object\", () => {\n        const uint8Array = new Uint8Array(Buffer.from(\"hello\"));\n        const file = new File([uint8Array], \"name.txt\");\n        assert.equal(file.name, \"name.txt\");\n        assert(typeof file.lastModified, \"number\");\n        assert(file.lastModifiedDate instanceof Date);\n        assert.equal(file.size, uint8Array.length);\n        assert.equal(file.type, \"application/unknown\");\n    }),\n\n    it(\"File: Should determine the type of a file\", () => {\n        const zipped = zip(\"hello\");\n        const file = new File([zipped.value]);\n        assert(file);\n        assert.strictEqual(file.type, \"application/zip\");\n    }),\n\n    it(\"File: unknown type should have a type of application/unknown\", () => {\n        const uint8Array = new Uint8Array(Buffer.from(\"hello\"));\n        const file =  new File([uint8Array], \"sample.txt\");\n        assert.strictEqual(file.type, \"application/unknown\");\n    }),\n\n    it(\"File: should be able to make a dish from it\", () => {\n        const uint8Array = new Uint8Array(Buffer.from(\"hello\"));\n        const file =  new File([uint8Array], \"sample.txt\");\n        try {\n            const dish = new Dish(file, 7);\n            assert.ok(dish.valid());\n        } catch (e) {\n            assert.fail(e.message);\n        }\n    }),\n\n    it(\"File: should allow dish to translate to ArrayBuffer\", () => {\n        const uint8Array = new Uint8Array(Buffer.from(\"hello\"));\n        const file =  new File([uint8Array], \"sample.txt\");\n        try {\n            const dish = new Dish(file, 7);\n            assert.ok(dish.value);\n\n            dish.get(4);\n            assert.strictEqual(dish.type, 4);\n            assert.ok(dish.valid());\n\n        } catch (e) {\n            assert.fail(e.message);\n        }\n    }),\n\n    it(\"File: should allow dish to translate from ArrayBuffer to File\", () => {\n        const uint8Array = new Uint8Array(Buffer.from(\"hello\"));\n        const file =  new File([uint8Array], \"sample.txt\");\n        try {\n            const dish = new Dish(file, 7);\n            assert.ok(dish.value);\n\n            // translate to ArrayBuffer\n            dish.get(4);\n            assert.ok(dish.valid());\n\n            // translate back to File\n            dish.get(7);\n            assert.ok(dish.valid());\n\n        } catch (e) {\n            assert.fail(e.message);\n        }\n    })\n\n]);\n"
  },
  {
    "path": "tests/node/tests/NodeDish.mjs",
    "content": "import assert from \"assert\";\nimport it from \"../assertionHandler.mjs\";\nimport fs from \"fs\";\n\nimport BigNumber from \"bignumber.js\";\n\nimport { Dish, toBase32, SHA3 } from \"../../../src/node/index.mjs\";\nimport File from \"../../../src/node/File.mjs\";\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addApiTests([\n    it(\"Composable Dish: Should have top level Dish object\", () => {\n        assert.ok(Dish);\n    }),\n\n    it(\"Composable Dish: Should construct empty dish object\", () => {\n        const dish = new Dish();\n        assert.strictEqual(dish.value.byteLength, new ArrayBuffer(0).byteLength);\n        assert.strictEqual(dish.type, 4);\n    }),\n\n    it(\"Composable Dish: constructed dish should have apply prototype functions\", () => {\n        const dish = new Dish();\n        assert.ok(dish.apply);\n        assert.throws(() => dish.someInvalidFunction());\n    }),\n\n    it(\"Composable Dish: composed function returns another dish\", () => {\n        const result = new Dish(\"some input\").apply(toBase32);\n        assert.ok(result instanceof Dish);\n    }),\n\n\n    it(\"Composable dish: infers type from input if needed\", () => {\n        const dish = new Dish(\"string input\");\n        assert.strictEqual(dish.type, 1);\n\n        const numberDish = new Dish(333);\n        assert.strictEqual(numberDish.type, 2);\n\n        const arrayBufferDish = new Dish(Buffer.from(\"some buffer input\").buffer);\n        assert.strictEqual(arrayBufferDish.type, 4);\n\n        const byteArrayDish = new Dish(Buffer.from(\"some buffer input\"));\n        assert.strictEqual(byteArrayDish.type, 0);\n\n        const JSONDish = new Dish({key: \"value\"});\n        assert.strictEqual(JSONDish.type, 6);\n    }),\n\n    it(\"Composable dish: Buffer type dishes should be converted to strings\", () => {\n        fs.writeFileSync(\"test.txt\", \"abc\");\n        const dish = new Dish(fs.readFileSync(\"test.txt\"));\n        assert.strictEqual(dish.type, 0);\n        fs.unlinkSync(\"test.txt\");\n    }),\n\n    it(\"Composable Dish: apply should allow set of arguments for operation\", () => {\n        const result = new Dish(\"input\").apply(SHA3, {size: \"256\"});\n        assert.strictEqual(result.toString(), \"7640cc9b7e3662b2250a43d1757e318bb29fb4860276ac4373b67b1650d6d3e3\");\n    }),\n\n    it(\"Composable Dish: apply functions can be chained\", () => {\n        const result = new Dish(\"input\").apply(toBase32).apply(SHA3, {size: \"224\"});\n        assert.strictEqual(result.toString(), \"493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0\");\n    }),\n\n    it(\"Dish translation: ArrayBuffer to ArrayBuffer\", () => {\n        const dish = new Dish(new ArrayBuffer(10), 4);\n        dish.get(\"array buffer\");\n        assert.strictEqual(dish.value.byteLength, 10);\n        assert.strictEqual(dish.type, 4);\n    }),\n\n    it(\"Dish translation: ArrayBuffer and String\", () => {\n        const dish = new Dish(\"some string\", 1);\n        dish.get(\"array buffer\");\n\n        assert.strictEqual(dish.type, 4);\n        assert.deepStrictEqual(dish.value, new Uint8Array([0x73, 0x6f, 0x6d, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67]).buffer);\n        assert.deepEqual(dish.value.byteLength, 11);\n\n        dish.get(\"string\");\n        assert.strictEqual(dish.type, 1);\n        assert.strictEqual(dish.value, \"some string\");\n    }),\n\n    it(\"Dish translation: ArrayBuffer and number\", () => {\n        const dish = new Dish(100, 2);\n        dish.get(4);\n\n        assert.strictEqual(dish.type, 4);\n        assert.deepStrictEqual(dish.value, new Uint8Array([0x31, 0x30, 0x30]).buffer);\n        assert.strictEqual(dish.value.byteLength, 3);\n\n        // Check the data in ArrayBuffer represents 100 as a string.\n        const view = new DataView(dish.value, 0);\n        assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2)), \"100\");\n\n        dish.get(\"number\");\n        assert.strictEqual(dish.type, 2);\n        assert.strictEqual(dish.value, 100);\n    }),\n\n    it(\"Dish translation: ArrayBuffer and byte array\", () => {\n        const dish = new Dish(new Uint8Array([1, 2, 3]), 0);\n        dish.get(4);\n\n        // Check intermediate value\n        const check = new Uint8Array(dish.value);\n        assert.deepEqual(check, new Uint8Array([1, 2, 3]));\n\n        // Check converts back OK\n        dish.get(0);\n        assert.deepEqual(dish.value, [1, 2, 3]);\n    }),\n\n    it(\"Dish translation: ArrayBuffer and HTML\", () => {\n        const html = `<!DOCTYPE html>\n<html>\n    <head>\n    <meta charset=\"utf-8\">\n    </head>\n    <body>\n    <a href=\"https://github.com\">Click here</a>\n    <script src=\"script.js\"></script>\n    </body>\n</html>`.replace(/\\n|\\s{4}/g, \"\"); // remove newlines, tabs\n\n        const dish = new Dish(html, Dish.HTML);\n        dish.get(4);\n\n        dish.get(3);\n        assert.strictEqual(dish.value, \"Click here\");\n    }),\n\n    it(\"Dish translation: ArrayBuffer and BigNumber\", () => {\n        const number = BigNumber(4001);\n        const dish = new Dish(number, Dish.BIG_NUMBER);\n\n        dish.get(Dish.ARRAY_BUFFER);\n        assert.deepStrictEqual(dish.value, new Uint8Array([0x34, 0x30, 0x30, 0x31]).buffer);\n        assert.strictEqual(dish.value.byteLength, 4);\n\n        // Check the data in ArrayBuffer represents 4001 as a string.\n        const view = new DataView(dish.value, 0);\n        assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)), \"4001\");\n\n        dish.get(5);\n        assert.deepStrictEqual(dish.value, number);\n    }),\n\n    it(\"Dish translation: ArrayBuffer and JSON\", () => {\n        const jsonString = \"{\\\"a\\\": 123455, \\\"b\\\": { \\\"aa\\\": [1,2,3]}}\";\n        const dish = new Dish(JSON.parse(jsonString), Dish.JSON);\n\n        dish.get(Dish.ARRAY_BUFFER);\n        dish.get(Dish.JSON);\n\n        assert.deepStrictEqual(dish.value, JSON.parse(jsonString));\n    }),\n\n    it(\"Dish translation: ArrayBuffer and File\", () => {\n        const file = new File(\"abcd\", \"unknown\");\n        const dish = new Dish(file, Dish.FILE);\n\n        dish.get(Dish.ARRAY_BUFFER);\n        assert.deepStrictEqual(dish.value, new Uint8Array([0x61, 0x62, 0x63, 0x64]).buffer);\n        assert.strictEqual(dish.value.byteLength, 4);\n\n        // Check the data in ArrayBuffer represents \"abcd\"\n        const view = new DataView(dish.value, 0);\n        assert.strictEqual(String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)), \"abcd\");\n\n        dish.get(Dish.FILE);\n\n        assert.deepStrictEqual(dish.value.data, file.data);\n        assert.strictEqual(dish.value.name, file.name);\n        assert.strictEqual(dish.value.type, file.type);\n        // Do not test lastModified\n    }),\n\n    it(\"Dish translation: ArrayBuffer and ListFile\", () => {\n        const file1 = new File(\"abcde\", \"unknown\");\n        const file2 = new File(\"fghijk\", \"unknown\");\n\n        const dish = new Dish([file1, file2], Dish.LIST_FILE);\n\n        dish.get(Dish.ARRAY_BUFFER);\n        assert.deepStrictEqual(dish.value, [new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]), new Uint8Array([0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b])]);\n        assert.strictEqual(dish.value.length, 2);\n\n        dish.get(Dish.LIST_FILE);\n        const dataArray = new Uint8Array(dish.value[0].data);\n        // cant store chars in a Uint8Array, so make it a normal one.\n        const actual = Array.prototype.slice.call(dataArray).map(c => String.fromCharCode(c)).join(\"\");\n        assert.strictEqual(actual, \"abcdefghijk\");\n    }),\n]);\n"
  },
  {
    "path": "tests/node/tests/Utils.mjs",
    "content": "import TestRegister from \"../../lib/TestRegister.mjs\";\nimport Utils from \"../../../src/core/Utils.mjs\";\nimport it from \"../assertionHandler.mjs\";\nimport assert from \"assert\";\n\nTestRegister.addApiTests([\n    it(\"Utils: should parse six backslashes correctly\", () => {\n        assert.equal(Utils.parseEscapedChars(\"\\\\\\\\\\\\\\\\\\\\\\\\\"), \"\\\\\\\\\\\\\");\n    }),\n\n    it(\"Utils: should parse escaped quotes correctly\", () => {\n        assert.equal(Utils.parseEscapedChars(\"\\\\'\"), \"'\");\n    }),\n\n    it(\"Utils: should parse escaped quotes and backslashes correctly\", () => {\n        assert.equal(Utils.parseEscapedChars(\"\\\\\\\\'\"), \"\\\\'\");\n    }),\n\n    it(\"Utils: should parse escaped quotes and escaped backslashes correctly\", () => {\n        assert.equal(Utils.parseEscapedChars(\"\\\\\\\\\\\\'\"), \"\\\\'\");\n    }),\n\n    it(\"Utils: should replace delete character\", () => {\n        assert.equal(\n            Utils.printable(\"\\x7e\\x7f\\x80\\xa7\", false, true),\n            \"\\x7e...\",\n        );\n    }),\n]);\n"
  },
  {
    "path": "tests/node/tests/lib/BigIntUtils.mjs",
    "content": "import TestRegister from \"../../../lib/TestRegister.mjs\";\nimport { parseBigInt, egcd, modPow } from \"../../../../src/core/lib/BigIntUtils.mjs\";\nimport it from \"../../assertionHandler.mjs\";\nimport assert from \"assert\";\n\nTestRegister.addApiTests([\n    // ===== parseBigInt tests =====\n    it(\"BigIntUtils: parseBigInt - decimal number\", () => {\n        const value = parseBigInt(\"1\", \"test value\");\n        assert.deepStrictEqual(value, BigInt(\"1\"));\n    }),\n\n    it(\"BigIntUtils: parseBigInt - large decimal\", () => {\n        const value = parseBigInt(\"123456789012345678901234567890\", \"test value\");\n        assert.deepStrictEqual(value, BigInt(\"123456789012345678901234567890\"));\n    }),\n\n    it(\"BigIntUtils: parseBigInt - hexadecimal lowercase\", () => {\n        const value = parseBigInt(\"0xff\", \"test value\");\n        assert.deepStrictEqual(value, BigInt(\"255\"));\n    }),\n\n    it(\"BigIntUtils: parseBigInt - hexadecimal uppercase\", () => {\n        const value = parseBigInt(\"0xFF\", \"test value\");\n        assert.deepStrictEqual(value, BigInt(\"255\"));\n    }),\n\n    it(\"BigIntUtils: parseBigInt - large hexadecimal\", () => {\n        const value = parseBigInt(\"0x123456789ABCDEF\", \"test value\");\n        assert.deepStrictEqual(value, BigInt(\"0x123456789ABCDEF\"));\n    }),\n\n    it(\"BigIntUtils: parseBigInt - whitespace trimming\", () => {\n        const value = parseBigInt(\"  42  \", \"test value\");\n        assert.deepStrictEqual(value, BigInt(\"42\"));\n    }),\n\n    it(\"BigIntUtils: parseBigInt - invalid input (text)\", () => {\n        assert.throws(() => parseBigInt(\"test\", \"test value\"), {\n            name: \"Error\",\n            message: \"test value must be decimal or hex (0x...)\"\n        });\n    }),\n\n    it(\"BigIntUtils: parseBigInt - invalid input (hex without prefix)\", () => {\n        assert.throws(() => parseBigInt(\"FF\", \"test value\"), {\n            name: \"Error\",\n            message: \"test value must be decimal or hex (0x...)\"\n        });\n    }),\n\n    it(\"BigIntUtils: parseBigInt - invalid input (mixed)\", () => {\n        assert.throws(() => parseBigInt(\"12abc\", \"test value\"), {\n            name: \"Error\",\n            message: \"test value must be decimal or hex (0x...)\"\n        });\n    }),\n\n    // ===== egcd tests =====\n    it(\"BigIntUtils: egcd - basic coprime\", () => {\n        const a = BigInt(\"36\");\n        const b = BigInt(\"48\");\n        const gcd = BigInt(\"12\");\n        const bezout1 = BigInt(\"-1\");\n        const bezout2 = BigInt(\"1\");\n        assert.deepStrictEqual(egcd(a, b), [gcd, bezout1, bezout2]);\n    }),\n\n    it(\"BigIntUtils: egcd - coprime numbers\", () => {\n        const [g, x, y] = egcd(BigInt(\"3\"), BigInt(\"11\"));\n        assert.strictEqual(g, BigInt(\"1\"));\n        // Verify Bézout identity: a*x + b*y = gcd\n        assert.strictEqual(BigInt(\"3\") * x + BigInt(\"11\") * y, g);\n    }),\n\n    it(\"BigIntUtils: egcd - non-coprime numbers\", () => {\n        const [g, x, y] = egcd(BigInt(\"240\"), BigInt(\"46\"));\n        assert.strictEqual(g, BigInt(\"2\"));\n        // Verify Bézout identity\n        assert.strictEqual(BigInt(\"240\") * x + BigInt(\"46\") * y, g);\n    }),\n\n    it(\"BigIntUtils: egcd - with zero\", () => {\n        const [g, x, y] = egcd(BigInt(\"17\"), BigInt(\"0\"));\n        assert.strictEqual(g, BigInt(\"17\"));\n        assert.strictEqual(x, BigInt(\"1\"));\n        assert.strictEqual(y, BigInt(\"0\"));\n    }),\n\n    it(\"BigIntUtils: egcd - identical numbers\", () => {\n        const [g, x, y] = egcd(BigInt(\"42\"), BigInt(\"42\"));\n        assert.strictEqual(g, BigInt(\"42\"));\n        // Verify Bézout identity\n        assert.strictEqual(BigInt(\"42\") * x + BigInt(\"42\") * y, g);\n    }),\n\n    it(\"BigIntUtils: egcd - large numbers\", () => {\n        const a = BigInt(\"123456789012345678901234567890\");\n        const b = BigInt(\"987654321098765432109876543210\");\n        const [g, x, y] = egcd(a, b);\n        // Verify Bézout identity\n        assert.strictEqual(a * x + b * y, g);\n    }),\n\n    // ===== modPow tests =====\n    it(\"BigIntUtils: modPow - basic\", () => {\n        // 2^10 mod 1000 = 1024 mod 1000 = 24\n        const result = modPow(BigInt(\"2\"), BigInt(\"10\"), BigInt(\"1000\"));\n        assert.strictEqual(result, BigInt(\"24\"));\n    }),\n\n    it(\"BigIntUtils: modPow - RSA-like example\", () => {\n        // Common RSA public exponent\n        const base = BigInt(\"123456789\");\n        const exp = BigInt(\"65537\");\n        const mod = BigInt(\"999999999999\");\n        const result = modPow(base, exp, mod);\n        // Result should be less than modulus\n        assert(result < mod);\n        assert(result >= BigInt(\"0\"));\n    }),\n\n    it(\"BigIntUtils: modPow - exponent zero\", () => {\n        // Any number^0 = 1\n        const result = modPow(BigInt(\"999\"), BigInt(\"0\"), BigInt(\"100\"));\n        assert.strictEqual(result, BigInt(\"1\"));\n    }),\n\n    it(\"BigIntUtils: modPow - base zero\", () => {\n        // 0^n = 0\n        const result = modPow(BigInt(\"0\"), BigInt(\"5\"), BigInt(\"100\"));\n        assert.strictEqual(result, BigInt(\"0\"));\n    }),\n\n    it(\"BigIntUtils: modPow - large exponent\", () => {\n        // Test with very large exponent (efficient algorithm should handle this)\n        const result = modPow(BigInt(\"3\"), BigInt(\"1000000\"), BigInt(\"1000000007\"));\n        assert(result >= BigInt(\"0\"));\n        assert(result < BigInt(\"1000000007\"));\n    }),\n\n    it(\"BigIntUtils: modPow - modular inverse verification\", () => {\n        // If a*x . 1 (mod m), then modPow(a, 1, m) * x . 1 (mod m)\n        const a = BigInt(\"3\");\n        const m = BigInt(\"11\");\n        const x = BigInt(\"4\"); // inverse of 3 mod 11\n        const result = modPow(a, BigInt(\"1\"), m) * x % m;\n        assert.strictEqual(result, BigInt(\"1\"));\n    }),\n]);\n"
  },
  {
    "path": "tests/node/tests/nodeApi.mjs",
    "content": "/* eslint no-console: 0 */\n\n/**\n * nodeApi.js\n *\n * Test node api utilities\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport assert from \"assert\";\nimport it from \"../assertionHandler.mjs\";\nimport chef from \"../../../src/node/index.mjs\";\nimport { OperationError, ExcludedOperationError } from \"../../../src/core/errors/index.mjs\";\nimport NodeDish from \"../../../src/node/NodeDish.mjs\";\n\nimport { toBase32, magic} from \"../../../src/node/index.mjs\";\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addApiTests([\n    it(\"should have some operations\", () => {\n        assert(chef);\n        assert(chef.toBase32);\n        assert(chef.setUnion);\n        assert(!chef.randomFunction);\n    }),\n\n    it(\"should export other functions at top level\", () => {\n        assert(toBase32);\n    }),\n\n    it(\"should be synchronous\", () => {\n        try {\n            const result = chef.toBase32(\"input\");\n            assert.notEqual(\"something\", result);\n        } catch (e) {\n            // shouldnt reach here\n            assert(false);\n        }\n\n        try {\n            const fail = chef.setUnion(\"1\");\n            // shouldnt get here\n            assert(!fail || false);\n        } catch (e) {\n            assert(true);\n        }\n    }),\n\n    it(\"should not catch Errors\", () => {\n        try {\n            chef.setUnion(\"1\");\n            assert(false);\n        } catch (e) {\n            assert(e instanceof OperationError);\n        }\n    }),\n\n    it(\"should accept arguments in object format for operations\", () => {\n        const result = chef.setUnion(\"1 2 3 4:3 4 5 6\", {\n            itemDelimiter: \" \",\n            sampleDelimiter: \":\"\n        });\n\n        assert.equal(result.value, \"1 2 3 4 5 6\");\n    }),\n\n    it(\"should accept just some of the optional arguments being overriden\", () => {\n        const result = chef.setIntersection(\"1 2 3 4 5\\\\n\\\\n3 4 5\", {\n            itemDelimiter: \" \",\n        });\n\n        assert.equal(result.value, \"3 4 5\");\n    }),\n\n    it(\"should accept no override arguments and just use the default values\", () => {\n        const result = chef.powerSet(\"1,2,3\");\n        assert.equal(result.value, \"\\n3\\n2\\n1\\n2,3\\n1,3\\n1,2\\n1,2,3\\n\");\n    }),\n\n    it(\"should return an object with a .to method\", () => {\n        const result = chef.toBase32(\"input\");\n        assert(result.to);\n        assert.equal(result.to(\"string\"), \"NFXHA5LU\");\n    }),\n\n    it(\"should return an object with a .get method\", () => {\n        const result = chef.toBase32(\"input\");\n        assert(result.get);\n        assert.equal(result.get(\"string\"), \"NFXHA5LU\");\n    }),\n\n    it(\"should return a NodeDish\", async () => {\n        const result = chef.toBase32(\"input\");\n        assert(result instanceof NodeDish);\n    }),\n\n    it(\"should coerce to a string as you expect\", () => {\n        const result = chef.fromBase32(chef.toBase32(\"something\"));\n        assert.equal(String(result), \"something\");\n        // This kind of coercion uses toValue\n        assert.equal(\"\"+result, \"NaN\");\n    }),\n\n    it(\"should coerce to a number as you expect\", () => {\n        const result = chef.fromBase32(chef.toBase32(\"32\"));\n        assert.equal(3 + result, 35);\n    }),\n\n    it(\"chef.help: should exist\", () => {\n        assert(chef.help);\n    }),\n\n    it(\"chef.help: should describe a operation\", () => {\n        const result = chef.help(\"tripleDESDecrypt\");\n        assert.strictEqual(result[0].name, \"Triple DES Decrypt\");\n        assert.strictEqual(result[0].module, \"Ciphers\");\n        assert.strictEqual(result[0].inputType, \"string\");\n        assert.strictEqual(result[0].outputType, \"string\");\n        assert.strictEqual(result[0].description, \"Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.\");\n        assert.strictEqual(result[0].args.length, 5);\n    }),\n\n    it(\"chef.help: null for invalid operation\", () => {\n        const result = chef.help(\"some invalid function name\");\n        assert.strictEqual(result, null);\n    }),\n\n    it(\"chef.help: takes a wrapped operation as input\", () => {\n        const result = chef.help(chef.toBase32);\n        assert.strictEqual(result[0].name, \"To Base32\");\n        assert.strictEqual(result[0].module, \"Default\");\n    }),\n\n    it(\"chef.help: returns multiple results\", () => {\n        const result = chef.help(\"base 64\");\n        assert.strictEqual(result.length, 13);\n    }),\n\n    it(\"chef.help: looks in description for matches too\", () => {\n        // string only in one operation's description.\n        const result = chef.help(\"Converts a unit of data to another format.\");\n        assert.strictEqual(result.length, 1);\n        assert.strictEqual(result[0].name, \"Convert data units\");\n    }),\n\n    it(\"chef.help: lists name matches before desc matches\", () => {\n        const result = chef.help(\"Checksum\");\n        assert.ok(result[0].name.includes(\"Checksum\"));\n        assert.ok(result[1].name.includes(\"Checksum\"));\n        assert.strictEqual(result[result.length - 1].name.includes(\"Checksum\"), false);\n        assert.ok(result[result.length - 1].description.includes(\"checksum\"));\n    }),\n\n    it(\"chef.help: exact name match only returns one result\", () => {\n        const result = chef.help(\"MD5\");\n        assert.strictEqual(result.length, 1);\n        assert.strictEqual(result[0].name, \"MD5\");\n    }),\n\n    it(\"chef.help: exact match ignores whitespace\", () => {\n        const result = chef.help(\"tobase64\");\n        assert.strictEqual(result.length, 1);\n        assert.strictEqual(result[0].name, \"To Base64\");\n    }),\n\n    it(\"chef.bake: should exist\", () => {\n        assert(chef.bake);\n    }),\n\n    it(\"chef.bake: should return NodeDish\", () => {\n        const result = chef.bake(\"input\", \"to base 64\");\n        assert(result instanceof NodeDish);\n    }),\n\n    it(\"chef.bake: should take an input and an op name and perform it\", () => {\n        const result = chef.bake(\"some input\", \"to base 32\");\n        assert.strictEqual(result.toString(), \"ONXW2ZJANFXHA5LU\");\n    }),\n\n    it(\"chef.bake: should complain if recipe isnt a valid object\", () => {\n        assert.throws(() => chef.bake(\"some input\", 3264), {\n            name: \"TypeError\",\n            message: \"Recipe can only contain function names or functions\"\n        });\n    }),\n\n    it(\"chef.bake: Should complain if string op is invalid\", () => {\n        assert.throws(() => chef.bake(\"some input\", \"not a valid operation\"), {\n            name: \"TypeError\",\n            message: \"Couldn't find an operation with name 'not a valid operation'.\"\n        });\n    }),\n\n    it(\"chef.bake: Should take an input and an operation and perform it\", () => {\n        const result = chef.bake(\"https://google.com/search?q=help\", chef.parseURI);\n        assert.strictEqual(result.toString(), \"Protocol:\\thttps:\\nHostname:\\tgoogle.com\\nPath name:\\t/search\\nArguments:\\n\\tq = help\\n\");\n    }),\n\n    it(\"chef.bake: Should complain if an invalid operation is inputted\", () => {\n        assert.throws(() => chef.bake(\"https://google.com/search?q=help\", () => {}), {\n            name: \"TypeError\",\n            message: \"Inputted function not a Chef operation.\"\n        });\n    }),\n\n    it(\"chef.bake: accepts an array of operation names and performs them all in order\", () => {\n        const result = chef.bake(\"https://google.com/search?q=that's a complicated question\", [\"URL encode\", \"URL decode\", \"Parse URI\"]);\n        assert.strictEqual(result.toString(), \"Protocol:\\thttps:\\nHostname:\\tgoogle.com\\nPath name:\\t/search\\nArguments:\\n\\tq = that's a complicated question\\n\");\n    }),\n\n    it(\"chef.bake: forgiving with operation names\", () =>{\n        const result = chef.bake(\"https://google.com/search?q=that's a complicated question\", [\"urlencode\", \"url decode\", \"parseURI\"]);\n        assert.strictEqual(result.toString(), \"Protocol:\\thttps:\\nHostname:\\tgoogle.com\\nPath name:\\t/search\\nArguments:\\n\\tq = that's a complicated question\\n\");\n    }),\n\n    it(\"chef.bake: forgiving with operation names\", () =>{\n        const result = chef.bake(\"hello\", [\"to base 64\"]);\n        assert.strictEqual(result.toString(), \"aGVsbG8=\");\n    }),\n\n    it(\"chef.bake: if recipe is empty array, return input as dish\", () => {\n        const result = chef.bake(\"some input\", []);\n        assert.strictEqual(result.toString(), \"some input\");\n        assert(result instanceof NodeDish, \"Result is not instance of NodeDish\");\n    }),\n\n    it(\"chef.bake: accepts an array of operations as recipe\", () => {\n        const result = chef.bake(\"https://google.com/search?q=that's a complicated question\", [chef.URLEncode, chef.URLDecode, chef.parseURI]);\n        assert.strictEqual(result.toString(), \"Protocol:\\thttps:\\nHostname:\\tgoogle.com\\nPath name:\\t/search\\nArguments:\\n\\tq = that's a complicated question\\n\");\n    }),\n\n    it(\"should complain if an invalid operation is inputted as part of array\", () => {\n        assert.throws(() => chef.bake(\"something\", [() => {}]), {\n            name: \"TypeError\",\n            message: \"Inputted function not a Chef operation.\"\n        });\n    }),\n\n    it(\"chef.bake: should take single JSON object describing op and args OBJ\", () => {\n        const result = chef.bake(\"some input\", {\n            op: chef.toHex,\n            args: {\n                Delimiter: \"Colon\"\n            }\n        });\n        assert.strictEqual(result.toString(), \"73:6f:6d:65:20:69:6e:70:75:74\");\n    }),\n\n    it(\"chef.bake: should take single JSON object desribing op with optional args\", () => {\n        const result = chef.bake(\"some input\", {\n            op: chef.toHex,\n        });\n        assert.strictEqual(result.toString(), \"73 6f 6d 65 20 69 6e 70 75 74\");\n    }),\n\n    it(\"chef.bake: should take single JSON object describing op and args ARRAY\", () => {\n        const result = chef.bake(\"some input\", {\n            op: chef.toHex,\n            args: [\"Colon\"]\n        });\n        assert.strictEqual(result.toString(), \"73:6f:6d:65:20:69:6e:70:75:74\");\n    }),\n\n    it(\"chef.bake: should error if op in JSON is not chef op\", () => {\n        assert.throws(() => chef.bake(\"some input\", {\n            op: () => {},\n            args: [\"Colon\"],\n        }), {\n            name: \"TypeError\",\n            message: \"Inputted function not a Chef operation.\"\n        });\n    }),\n\n    it(\"chef.bake: should take multiple ops in JSON object form, some ops by string\", () => {\n        const result = chef.bake(\"some input\", [\n            {\n                op: chef.toHex,\n                args: [\"Colon\"]\n            },\n            {\n                op: \"to octal\",\n                args: {\n                    delimiter: \"Semi-colon\",\n                }\n            }\n        ]);\n        assert.strictEqual(result.toString(), \"67;63;72;66;146;72;66;144;72;66;65;72;62;60;72;66;71;72;66;145;72;67;60;72;67;65;72;67;64\");\n    }),\n\n    it(\"chef.bake: should take multiple ops in JSON object form, some without args\", () => {\n        const result = chef.bake(\"some input\", [\n            {\n                op: chef.toHex,\n            },\n            {\n                op: \"to octal\",\n                args: {\n                    delimiter: \"Semi-colon\",\n                }\n            }\n        ]);\n        assert.strictEqual(result.toString(), \"67;63;40;66;146;40;66;144;40;66;65;40;62;60;40;66;71;40;66;145;40;67;60;40;67;65;40;67;64\");\n    }),\n\n    it(\"chef.bake: should handle op with multiple args\", () => {\n        const result = chef.bake(\"some input\", {\n            op: \"to morse code\",\n            args: {\n                formatOptions: \"Dash/Dot\",\n                wordDelimiter: \"Comma\",\n                letterDelimiter: \"Backslash\",\n            }\n        });\n        assert.strictEqual(result.toString(), \"DotDotDot\\\\DashDashDash\\\\DashDash\\\\Dot,DotDot\\\\DashDot\\\\DotDashDashDot\\\\DotDotDash\\\\Dash\");\n    }),\n\n    it(\"chef.bake: should take compact JSON format from Chef Website as recipe\", () => {\n        const result = chef.bake(\"some input\", [{\"op\": \"To Morse Code\", \"args\": [\"Dash/Dot\", \"Backslash\", \"Comma\"]}, {\"op\": \"Hex to PEM\", \"args\": [\"SOMETHING\"]}, {\"op\": \"To Snake case\", \"args\": [false]}]);\n        assert.strictEqual(result.toString(), \"begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something\");\n    }),\n\n    it(\"chef.bake: should accept Clean JSON format from Chef website as recipe\", () => {\n        const result = chef.bake(\"some input\", [\n            { \"op\": \"To Morse Code\",\n                \"args\": [\"Dash/Dot\", \"Backslash\", \"Comma\"] },\n            { \"op\": \"Hex to PEM\",\n                \"args\": [\"SOMETHING\"] },\n            { \"op\": \"To Snake case\",\n                \"args\": [false] }\n        ]);\n        assert.strictEqual(result.toString(), \"begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something\");\n    }),\n\n    it(\"chef.bake: should accept Clean JSON format from Chef website - args optional\", () => {\n        const result = chef.bake(\"some input\", [\n            { \"op\": \"To Morse Code\" },\n            { \"op\": \"Hex to PEM\",\n                \"args\": [\"SOMETHING\"] },\n            { \"op\": \"To Snake case\",\n                \"args\": [false] }\n        ]);\n        assert.strictEqual(result.toString(), \"begin_something_aaaaaaaaaaaaaa_end_something\");\n    }),\n\n    it(\"chef.bake: should accept operation names from Chef Website which contain forward slash\", () => {\n        const result = chef.bake(\"I'll have the test salmon\", [\n            { \"op\": \"Find / Replace\",\n                \"args\": [{ \"option\": \"Regex\", \"string\": \"test\" }, \"good\", true, false, true, false]}\n        ]);\n        assert.strictEqual(result.toString(), \"I'll have the good salmon\");\n    }),\n\n    it(\"chef.bake: should accept operation names from Chef Website which contain a hyphen\", () => {\n        const result = chef.bake(\"I'll have the test salmon\", [\n            { \"op\": \"Adler-32 Checksum\",\n                \"args\": [] }\n        ]);\n        assert.strictEqual(result.toString(), \"6e4208f8\");\n    }),\n\n    it(\"chef.bake: should accept operation names from Chef Website which contain a period\", () => {\n        const result = chef.bake(\"30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f\", [\n            { \"op\": \"Parse ASN.1 hex string\",\n                \"args\": [0, 32] }\n        ]);\n        assert.strictEqual(result.toString(), `SEQUENCE\n  INTEGER 05\n  IA5String 'Anybody there?'\n`);\n    }),\n\n    it(\"Excluded operations: throw a sensible error when you try and call one\", () => {\n        try {\n            chef.fork();\n        } catch (e) {\n            assert.strictEqual(e.type, \"ExcludedOperationError\");\n            assert.strictEqual(e.message, \"Sorry, the Fork operation is not available in the Node.js version of CyberChef.\");\n        }\n    }),\n\n    it(\"chef.bake: cannot accept flowControl operations in recipe\", () => {\n        assert.throws(() => chef.bake(\"some input\", \"magic\"), {\n            name: \"TypeError\",\n            message: \"flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API\"\n        });\n        assert.throws(() => chef.bake(\"some input\", magic), {\n            name: \"TypeError\",\n            message: \"flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API\"\n        });\n        assert.throws(() => chef.bake(\"some input\", [\"to base 64\", \"magic\"]), {\n            name: \"TypeError\",\n            message: \"flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API\"\n        });\n    }),\n\n    it(\"Excluded operations: throw a sensible error when you try and call one\", () => {\n        assert.throws(chef.fork,\n            (err) => {\n                assert(err instanceof ExcludedOperationError);\n                assert.deepEqual(err.message, \"Sorry, the Fork operation is not available in the Node.js version of CyberChef.\");\n                return true;\n            },\n            \"Unexpected error type\"\n        );\n        assert.throws(chef.javaScriptBeautify,\n            (err) => {\n                assert(err instanceof ExcludedOperationError);\n                assert.deepEqual(err.message, \"Sorry, the JavaScriptBeautify operation is not available in the Node.js version of CyberChef.\");\n                return true;\n            },\n            \"Unexpected error type\"\n        );\n    }),\n\n    it(\"Operation arguments: should be accessible from operation object if op has array arg\", () => {\n        assert.ok(chef.toCharcode.args);\n        assert.deepEqual(chef.unzip.args, {\n            password: {\n                type: \"binaryString\",\n                value: \"\",\n            },\n            verifyResult: {\n                type: \"boolean\",\n                value: false,\n            }\n        });\n    }),\n\n    it(\"Operation arguments: should have key for each argument in operation\", () => {\n        assert.ok(chef.convertDistance.args.inputUnits);\n        assert.ok(chef.convertDistance.args.outputUnits);\n\n        assert.strictEqual(chef.bitShiftRight.args.amount.type, \"number\");\n        assert.strictEqual(chef.bitShiftRight.args.amount.value, 1);\n        assert.strictEqual(chef.bitShiftRight.args.type.type, \"option\");\n        assert.ok(Array.isArray(chef.bitShiftRight.args.type.options));\n\n    }),\n\n    it(\"Operation arguments: should list all options excluding subheadings\", () => {\n        // First element (subheading) removed\n        assert.equal(chef.convertDistance.args.inputUnits.options[0], \"Nanometres (nm)\");\n        assert.equal(chef.defangURL.args.process.options[1], \"Only full URLs\");\n    }),\n\n]);\n"
  },
  {
    "path": "tests/node/tests/operations.mjs",
    "content": "/* eslint no-console: 0 */\n\n/**\n * nodeApi.js\n *\n * Test node api operations\n *\n * Aim of these tests is to ensure each arg type is\n * handled correctly by the wrapper.\n *\n * Generally just checking operations that use external dependencies to ensure\n * it behaves as expected in Node.\n *\n * @author d98762625 [d98762625@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport assert from \"assert\";\nimport it from \"../assertionHandler.mjs\";\nimport fs from \"fs\";\n\nimport {\n    addLineNumbers,\n    adler32Checksum,\n    AESDecrypt,\n    affineCipherDecode,\n    affineCipherEncode,\n    bifidCipherEncode,\n    bitShiftRight,\n    cartesianProduct,\n    CSSMinify,\n    toBase64,\n    toHex\n} from \"../../../src/node/index.mjs\";\nimport chef from \"../../../src/node/index.mjs\";\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport File from \"../../../src/node/File.mjs\";\n\nglobal.File = File;\n\nTestRegister.addApiTests([\n\n    it(\"ADD: toggleString argument\", () => {\n        const result = chef.ADD(\"sample input\", {\n            key: {\n                string: \"some key\",\n                option: \"utf8\"\n            }\n        });\n        assert.equal(result.toString(), \"\\xe6\\xd0\\xda\\xd5\\x8c\\xd0\\x85\\xe2\\xe1\\xdf\\xe2\\xd9\");\n    }),\n\n\n    it(\"ADD: default option toggleString argument\", () => {\n        const result = chef.ADD(3, {\n            key: \"4\",\n        });\n        assert.strictEqual(result.toString(), \"7\");\n    }),\n\n    it(\"addLineNumbers: No arguments\", () => {\n        const result = addLineNumbers(\"sample input\");\n        assert.equal(result.toString(), \"1 sample input\");\n    }),\n\n    it(\"adler32Checksum: No args\", () => {\n        const result = adler32Checksum(\"sample input\");\n        assert.equal(result.toString(), \"1f2304d3\");\n    }),\n\n    it(\"AES decrypt: toggleString and option\", () => {\n        const result = AESDecrypt(\"4a123af235a507bbc9d5871721d61b98504d569a9a5a7847e2d78315fec7\", {\n            key: {\n                string: \"some longer key1\",\n                option: \"utf8\",\n            },\n            iv: {\n                string: \"some iv some iv1\",\n                option: \"utf8\",\n            },\n            mode: \"OFB\",\n        });\n        assert.equal(result.toString(), \"a slightly longer sampleinput?\");\n    }),\n\n    it(\"AffineCipherDecode: number input\", () => {\n        const result = affineCipherDecode(\"some input\", {\n            a: 7,\n            b: 4\n        });\n        assert.strictEqual(result.toString(), \"cuqa ifjgr\");\n    }),\n\n    it(\"affineCipherEncode: number input\", () => {\n        const result = affineCipherEncode(\"some input\", {\n            a: 11,\n            b: 6\n        });\n        assert.strictEqual(result.toString(), \"weiy qtpsh\");\n    }),\n\n    it(\"analyzeHash\", () => {\n        const result = chef.analyseHash(chef.MD5(\"some input\"));\n        const expected = `Hash length: 32\nByte length: 16\nBit length:  128\n\nBased on the length, this hash could have been generated by one of the following hashing functions:\nMD5\nMD4\nMD2\nHAVAL-128\nRIPEMD-128\nSnefru\nTiger-128`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"AND\", () => {\n        const result = chef.AND(\"Scot-free\", {\n            key: {\n                string: \"Raining Cats and Dogs\",\n                option: \"utf8\",\n            }\n        });\n        assert.strictEqual(result.toString(), \"Raid)fb A\");\n    }),\n\n    it(\"atBash Cipher\", () => {\n        const result = chef.atbashCipher(\"Happy as a Clam\");\n        assert.strictEqual(result.toString(), \"Szkkb zh z Xozn\");\n\n    }),\n\n    it(\"Bcrypt\", async () => {\n        const result = await chef.bcrypt(\"Put a Sock In It\");\n        const strResult = result.toString();\n        assert.equal(strResult.length, 60);\n        assert.equal(strResult.slice(0, 7), \"$2a$10$\");\n    }),\n\n    it(\"bcryptCompare\", async() => {\n        const result = await chef.bcryptCompare(\"Put a Sock In It\", {\n            hash: \"$2a$10$2rT4a3XnIecBsd1H33dMTuyYE1HJ1n9F.V2rjQtAH73rh1qvOf/ae\",\n        });\n        assert.strictEqual(result.toString(), \"Match: Put a Sock In It\");\n    }),\n\n    it(\"Bcrypt Parse\", async () => {\n        const result = await chef.bcryptParse(\"$2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6\");\n        const expected = `Rounds: 10\nSalt: $2a$10$ODeP1.6fMsb.ENk2ngPUCO\nPassword hash: 7qTGVPyHA9TqDVcyupyed8FjsiF65L6\nFull hash: $2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"bifid cipher decode\", () => {\n        const result = chef.bifidCipherDecode(\"Vhef Qnte Ke Xfhz Mxon Bmgf\", {\n            keyword: \"Alpha\",\n        });\n        assert.strictEqual(result.toString(), \"What Goes Up Must Come Down\");\n    }),\n\n    it(\"bifid cipher encode: string option\", () => {\n        const result = bifidCipherEncode(\"some input\", {\n            keyword: \"mykeyword\",\n        });\n        assert.strictEqual(result.toString(), \"nmhs zmsdo\");\n    }),\n\n    it(\"bit shift left\", () => {\n        const result = chef.bitShiftLeft(\"Keep Your Eyes Peeled\");\n        assert.strictEqual(result.toString(), \"ÊÊà@²Þêä@òÊæ@ ÊÊØÊÈ\");\n    }),\n\n    it(\"bitShiftRight: number and option\", () => {\n        const result = bitShiftRight(\"some bits to shift\", {\n            type: \"Arithmetic shift\",\n            amount: 1,\n        });\n        assert.strictEqual(result.toString(), \"9762\\u001014:9\\u0010:7\\u00109443:\");\n    }),\n\n    it(\"Blowfish encrypt\", () => {\n        const result = chef.blowfishEncrypt(\"Fool's Gold\", {\n            key: {\n                string: \"0011223344556677\",\n                option: \"hex\",\n            },\n            iv: {\n                string: \"exparrot\",\n                option: \"utf8\"\n            },\n            mode: \"CBC\"\n        });\n        assert.strictEqual(result.toString(), \"55a2838980078ffe1722b08d5fa1d481\");\n    }),\n\n    it(\"Blowfish decrypt\", () => {\n        const result = chef.blowfishDecrypt(\"55a2838980078ffe1722b08d5fa1d481\", {\n            key: {\n                string: \"0011223344556677\",\n                option: \"hex\",\n            },\n            iv: {\n                string: \"exparrot\",\n                option: \"utf8\",\n            },\n            mode: \"CBC\"\n        });\n        assert.strictEqual(result.toString(), \"Fool's Gold\");\n    }),\n\n    it(\"BSON Serialise / Deserialise\", () => {\n        const result = chef.BSONDeserialise(chef.BSONSerialise(\"{\\\"phrase\\\": \\\"Mouth-watering\\\"}\"));\n        assert.strictEqual(result.toString(), `{\n  \"phrase\": \"Mouth-watering\"\n}`);\n    }),\n\n    it(\"Bzip2 Decompress\", async () => {\n        const result = await chef.bzip2Decompress(chef.fromBase64(\"QlpoOTFBWSZTWUdQlt0AAAIVgEAAAQAmJAwAIAAxBkxA0A2pTL6U2CozxdyRThQkEdQlt0A=\"));\n        assert.strictEqual(result.toString(), \"Fit as a Fiddle\");\n    }),\n\n    it(\"cartesianProduct: binary string\", () => {\n        const result = cartesianProduct(\"1:2\\\\n\\\\n3:4\", {\n            itemDelimiter: \":\",\n        });\n        assert.strictEqual(result.toString(), \"(1,3):(1,4):(2,3):(2,4)\");\n    }),\n\n    it(\"Change IP format\", () => {\n        const result = chef.changeIPFormat(\"172.20.23.54\", {\n            inputFormat: \"Dotted Decimal\",\n            outputFormat: \"Hex\",\n        });\n        assert.strictEqual(result.toString(), \"ac141736\");\n    }),\n\n    it(\"Chi square\", () => {\n        const result = chef.chiSquare(\"Burst Your Bubble\");\n        assert.strictEqual(result.toString(), \"433.55399816176475\");\n    }),\n\n    it(\"Compare CTPH Hashes\", () => {\n        const result = chef.compareCTPHHashes(\"1234\\n3456\");\n        assert.strictEqual(result.toString(), \"0\");\n    }),\n\n    it(\"Compare SSDEEPHashes\", () => {\n        const result = chef.compareCTPHHashes(\"1234\\n3456\");\n        assert.strictEqual(result.toString(), \"0\");\n    }),\n\n    it(\"Convert area\", () => {\n        const result = chef.convertArea(\"12345\", {\n            inputUnits: \"Square metre (sq m)\",\n            outputUnits: \"Isle of Wight\"\n        });\n        assert.strictEqual(result.toString(), \"0.00003248684210526316\");\n    }),\n\n    it(\"Convert data units\", () => {\n        const result = chef.convertDataUnits(\"12345\", {\n            inputUnits: \"Bits (b)\",\n            outputUnits: \"Kilobytes (KB)\",\n        });\n        assert.strictEqual(result.toString(), \"1.543125\");\n    }),\n\n    it(\"Convert distance\", () => {\n        const result = chef.convertDistance(\"1234567\", {\n            inputUnits: \"Nanometres (nm)\",\n            outputUnits: \"Furlongs (fur)\",\n        });\n        assert.strictEqual(result.toString(), \"0.00000613699494949495\");\n    }),\n\n    it(\"Convert mass\", () => {\n        const result = chef.convertMass(\"123\", {\n            inputUnits: \"Earth mass (M⊕)\",\n            outputUnits: \"Great Pyramid of Giza (6,000,000 tonnes)\",\n        });\n        assert.strictEqual(result.toString(), \"122429895000000000\");\n    }),\n\n    it(\"Convert speed\", () => {\n        const result = chef.convertSpeed(\"123\", {\n            inputUnits: \"Lunar escape velocity\",\n            outputUnits: \"Jet airliner cruising speed\",\n        });\n        assert.strictEqual(result.toString(), \"1168.5\");\n    }),\n\n    it(\"Count occurrences\", () => {\n        const result = chef.countOccurrences(\"Talk the Talk\", {\n            searchString: {\n                string: \"Tal\",\n                option: \"Simple string\",\n            }\n        });\n        assert.strictEqual(result.toString(), \"2\");\n    }),\n\n    it(\"CSS Beautify\", () => {\n        const result = chef.CSSBeautify(\"header {color:black;padding:3rem;}\");\n        const expected = `header {\n\\\\tcolor:black;\n\\\\tpadding:3rem;\n}\n`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"CSS minify: boolean\", () => {\n        const input = `header {\n// comment\nwidth: 100%;\ncolor: white;\n}`;\n        const result = CSSMinify(input, {\n            preserveComments: true,\n        });\n        assert.strictEqual(result.toString(), \"header {// comment width: 100%;color: white;}\");\n    }),\n\n    it(\"CSS Selector\", () => {\n        const result = chef.CSSSelector(\"<html><header><h1>Hello</h1></header></html>\", {\n            cssSelector: \"h1\",\n        });\n        assert.strictEqual(result.toString(), \"<h1>Hello</h1>\");\n    }),\n\n    it(\"CTPH\", () => {\n        const result = chef.CTPH(\"If You Can't Stand the Heat, Get Out of the Kitchen\");\n        assert.strictEqual(result.toString(), \"A:+EgFgBKAA0V0UFfClEs6:+Qk0gUFse\");\n    }),\n\n    it(\"Decode NetBIOS Name\", () => {\n        assert.strictEqual(chef.decodeNetBIOSName(\"EBGMGMCAEHHCGFGFGLCAFEGPCAENGFCA\").toString(), \"All Greek To Me\");\n    }),\n\n    it(\"Decode text\", () => {\n        const encoded = chef.encodeText(\"Ugly Duckling\", {\n            encoding: \"UTF-16LE (1200)\",\n        });\n        const result = chef.decodeText(encoded, {\n            encoding: \"UTF-16LE (1200)\",\n        });\n        assert.strictEqual(result.toString(), \"Ugly Duckling\");\n    }),\n\n    it(\"Derive EVP Key\", () => {\n        const result = chef.deriveEVPKey(\"\", {\n            passphrase: {\n                string: \"46 6c 65 61 20 4d 61 72 6b 65 74\",\n                option: \"Hex\",\n            },\n            salt: {\n                string: \"Market\",\n                option: \"utf8\",\n            },\n        });\n        assert.strictEqual(result.toString(), \"4930d5d200e80f18c96b5550d13c6af8\");\n    }),\n\n    it(\"Derive PBKDF2 Key\", () => {\n        const result = chef.derivePBKDF2Key(\"\", {\n            passphrase: {\n                string: \"Jack of All Trades Master of None\",\n                option: \"utf8\",\n            },\n            keySize: 256,\n            iterations: 2,\n            hashingFunction: \"md5\",\n            salt: {\n                string: \"fruit\",\n                option: \"utf8\"\n            }\n        });\n        assert.strictEqual(result.toString(), \"728a885b209e8b19cbd7430ca32608ff09190f7ccb7ded204e1d4c50f87c47bf\");\n    }),\n\n    it(\"DES Decrypt\", () => {\n        const result = chef.DESDecrypt(\"713081c66db781c323965ba8f166fd8c230c3bb48504a913\", {\n            key: {\n                string: \"onetwoth\",\n                option: \"utf8\",\n            },\n            iv: {\n                string: \"threetwo\",\n                option: \"utf8\",\n            },\n            mode: \"ECB\",\n        });\n        assert.strictEqual(result.toString(), \"Put a Sock In It\");\n    }),\n\n    it(\"DES Encrypt\", () => {\n        const result = chef.DESEncrypt(\"Put a Sock In It\", {\n            key: {\n                string: \"onetwoth\",\n                option: \"utf8\",\n            },\n            iv: {\n                string: \"threetwo\",\n                option: \"utf8\",\n            },\n            mode: \"ECB\",\n        });\n        assert.strictEqual(result.toString(), \"713081c66db781c323965ba8f166fd8c230c3bb48504a913\");\n    }),\n\n    it(\"Diff\", () => {\n        const result = chef.diff(\"one two\\\\n\\\\none two three\");\n        assert.strictEqual(result.toString(), \"one two three\");\n    }),\n\n    it(\"Disassemble x86\", () => {\n        const result = chef.disassembleX86(chef.toBase64(\"one two three\"));\n        const expected = `0000000000000000 0000                            ADD BYTE PTR [RAX],AL\\r\n0000000000000002 0B250000000B                    OR ESP,DWORD PTR [000000000B000008]\\r\n`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"Divide\", () => {\n        assert.strictEqual(chef.divide(\"4\\n7\").toString(), \"0.57142857142857142857\");\n    }),\n\n    it(\"Drop bytes\", () => {\n        assert.strictEqual(chef.dropBytes(\"There's No I in Team\").toString(), \"'s No I in Team\");\n    }),\n\n    it(\"Entropy\", () => {\n        const result = chef.entropy(\"Ride Him, Cowboy!\");\n        assert.strictEqual(result.toString(), \"3.734521664779752\");\n    }),\n\n    it(\"Escape string\", () => {\n        const result = chef.escapeString(\"Know the Ropes\", {\n            escapeLevel: \"Everything\",\n            JSONCompatible: false,\n            ES6Compatible: true,\n            uppercaseHex: true,\n        });\n        assert.strictEqual(result.toString(), \"\\\\x4B\\\\x6E\\\\x6F\\\\x77\\\\x20\\\\x74\\\\x68\\\\x65\\\\x20\\\\x52\\\\x6F\\\\x70\\\\x65\\\\x73\");\n    }),\n\n    it(\"Escape unicode characters\", () => {\n        assert.strictEqual(chef.escapeUnicodeCharacters(\"σου\").toString(), \"\\\\u03C3\\\\u03BF\\\\u03C5\");\n    }),\n\n    it(\"Expand alphabet range\", () => {\n        assert.strictEqual(\n            chef.expandAlphabetRange(\"Fight Fire With Fire\", {delimiter: \"t\"}).toString(),\n            \"Ftitgthttt tFtitrtet tWtitttht tFtitrte\");\n    }),\n\n    it(\"Extract dates\", () => {\n        assert.strictEqual(chef.extractDates(\"Don't Look a Gift Horse In The Mouth 01/02/1992\").toString(), \"01/02/1992\");\n    }),\n\n    it(\"Filter\", () => {\n        const result = chef.filter(\n            `I Smell a Rat\nEvery Cloud Has a Silver Lining\nTop Drawer`, {\n                regex: \"Every\",\n            });\n        const expected = \"Every Cloud Has a Silver Lining\";\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"Find / Replace\", () => {\n        assert.strictEqual(\n            chef.findReplace(\n                \"Curiosity Killed The Cat\",\n                {\n                    find: {\n                        string: \"l\",\n                        option: \"Regex\",\n                    },\n                    replace: \"s\",\n                }).toString(),\n            \"Curiosity Kissed The Cat\");\n    }),\n\n    it(\"Fletcher8 Checksum\", () => {\n        assert.strictEqual(chef.fletcher8Checksum(\"Keep Your Eyes Peeled\").toString(), \"48\");\n    }),\n\n    it(\"Format MAC addresses\", () => {\n        const result = chef.formatMACAddresses(\"00-01-02-03-04-05\");\n        const expected = `000102030405\n000102030405\n00-01-02-03-04-05\n00-01-02-03-04-05\n00:01:02:03:04:05\n00:01:02:03:04:05\n`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"Frequency distribution\", () => {\n        const result = chef.frequencyDistribution(\"Don't Count Your Chickens Before They Hatch\");\n        const expected = \"{\\\"dataLength\\\":43,\\\"percentages\\\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13.953488372093023,0,0,0,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2.3255813953488373,4.651162790697675,2.3255813953488373,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,0,0,0,0,2.3255813953488373,0,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,2.3255813953488373,0,4.651162790697675,0,9.30232558139535,2.3255813953488373,0,6.976744186046512,2.3255813953488373,0,2.3255813953488373,0,0,6.976744186046512,9.30232558139535,0,0,4.651162790697675,2.3255813953488373,6.976744186046512,4.651162790697675,0,0,0,2.3255813953488373,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\\\"distribution\\\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,4,1,0,3,1,0,1,0,0,3,4,0,0,2,1,3,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\\\"bytesRepresented\\\":22}\";\n        // Whacky formatting, but the data is all there\n        assert.strictEqual(result.toString().replace(/\\r?\\n|\\r|\\s/g, \"\"), expected);\n    }),\n\n    it(\"From base\", () => {\n        assert.strictEqual(chef.fromBase(\"11\", {radix: 13}).toString(), \"14\");\n    }),\n\n    it(\"From BCD\", () => {\n        assert.strictEqual(chef.fromBCD(\"1143\", { inputFormat: \"Raw\", scheme: \"7 4 2 1\"}).toString(), \"31313433\");\n    }),\n\n    it(\"From binary\", () => {\n        assert.strictEqual(chef.fromBinary(\"010101011100101101011010\").toString(), \"UËZ\");\n    }),\n\n    it(\"From Charcode\", () => {\n        assert.strictEqual(chef.fromCharcode(\"4c 6f 6e 67 20 49 6e 20 54 68 65 20 54 6f 6f 74 68 0a\").toString(), \"Long In The Tooth\\n\");\n    }),\n\n    it(\"From decimal\", () => {\n        assert.strictEqual(chef.fromDecimal(\"72 101 108 108 111\").toString(), \"Hello\");\n    }),\n\n    it(\"From hex\", () => {\n        assert.strictEqual(chef.fromHex(\"52 69 6e 67 20 41 6e 79 20 42 65 6c 6c 73 3f\").toString(), \"Ring Any Bells?\");\n    }),\n\n    it(\"From hex content\", () => {\n        assert.strictEqual(chef.fromHexContent(\"foo|3d|bar\").toString(), \"foo=bar\");\n    }),\n\n    it(\"To and From hex dump\", () => {\n        assert.strictEqual(chef.fromHexdump(chef.toHexdump(\"Elephant in the Room\")).toString(), \"Elephant in the Room\");\n    }),\n\n    it(\"From HTML entity\", () => {\n        assert.strictEqual(chef.fromHTMLEntity(\"&amp;\").toString(), \"&\");\n    }),\n\n    it(\"To and From morse code\", () => {\n        assert.strictEqual(chef.fromMorseCode(chef.toMorseCode(\"Put a Sock In It\")).toString(), \"PUT A SOCK IN IT\");\n    }),\n\n    it(\"From octal\", () => {\n        assert.strictEqual(chef.fromOctal(\"113 156 157 167 40 164 150 145 40 122 157 160 145 163\").toString(), \"Know the Ropes\");\n    }),\n\n    it(\"To, From punycode\", () => {\n        assert.strictEqual(chef.fromPunycode(chef.toPunycode(\"münchen\")).toString(), \"münchen\");\n    }),\n\n    it(\"From unix timestamp\", () => {\n        assert.strictEqual(chef.fromUNIXTimestamp(\"978346800\").toString(), \"Mon 1 January 2001 11:00:00 UTC\");\n    }),\n\n    it(\"Generate HOTP\", () => {\n        const result = chef.generateHOTP(\"JBSWY3DPEHPK3PXP\", {\n        });\n        const expected = `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0\n\nPassword: 282760`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"Generate PGP Key Pair\", async () => {\n        const result = await chef.generatePGPKeyPair(\"Back To the Drawing Board\", {\n            keyType: \"ECC-256\",\n        });\n        assert.strictEqual(result.toString().substr(0, 37), \"-----BEGIN PGP PRIVATE KEY BLOCK-----\");\n    }),\n\n    ...[1, 3, 4, 5, 6, 7].map(version => it(`Generate UUID v${version}`, () => {\n        const result = chef.generateUUID(\"\", { \"version\": `v${version}` }).toString();\n        assert.ok(result);\n        assert.strictEqual(result.length, 36);\n    })),\n\n    ...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => {\n        const uuid = chef.generateUUID(\"\", { \"version\": `v${version}` }).toString();\n        const result = chef.analyseUUID(uuid).toString();\n        const expected = `UUID version: ${version}`;\n        assert.strictEqual(result, expected);\n    })),\n\n    it(\"Generate UUID using defaults\", () => {\n        const uuid = chef.generateUUID();\n        assert.ok(uuid);\n\n        const analysis = chef.analyseUUID(uuid).toString();\n        assert.strictEqual(analysis, \"UUID version: 4\");\n    }),\n\n    it(\"Gzip, Gunzip\", () => {\n        assert.strictEqual(chef.gunzip(chef.gzip(\"Down To The Wire\")).toString(), \"Down To The Wire\");\n    }),\n\n    it(\"Hex to Object Identifier\", () => {\n        assert.strictEqual(\n            chef.hexToObjectIdentifier(chef.toHex(\"You Can't Teach an Old Dog New Tricks\")).toString(),\n            \"2.9.111.117.32.67.97.110.39.116.32.84.101.97.99.104.32.97.110.32.79.108.100.32.68.111.103.32.78.101.119.32.84.114.105.99.107.115\");\n    }),\n\n    it(\"Hex to PEM\", () => {\n        const result = chef.hexToPEM(chef.toHex(\"Yada Yada\"));\n        const expected = `-----BEGIN CERTIFICATE-----\\r\nWWFkYSBZYWRh\\r\n-----END CERTIFICATE-----\\r\\n`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"HMAC\", () => {\n        assert.strictEqual(chef.HMAC(\"On Cloud Nine\", {key: \"idea\"}).toString(), \"e15c268b4ee755c9e52db094ed50add7\");\n    }),\n\n    it(\"JPathExpression\", () => {\n        assert.strictEqual(chef.JPathExpression(\"{\\\"key\\\" : \\\"value\\\"}\", {query: \"$.key\"}).toString(), \"\\\"value\\\"\");\n    }),\n\n    it(\"JSON Beautify\", () => {\n        assert.strictEqual(\n            chef.JSONBeautify(\"{\\\"key\\\" : \\\"value\\\"}\").toString(),\n            `{\n    \"key\": \"value\"\n}`);\n    }),\n\n    it(\"Keccak\", () => {\n        assert.strictEqual(chef.keccak(\"Flea Market\").toString(), \"c2a06880b19e453ee5440e8bd4c2024bedc15a6630096aa3f609acfd2b8f15f27cd293e1cc73933e81432269129ce954a6138889ce87831179d55dcff1cc7587\");\n    }),\n\n    it(\"LZNT1 Decompress\", () => {\n        assert.strictEqual(chef.LZNT1Decompress(\"\\x1a\\xb0\\x00compress\\x00edtestda\\x04ta\\x07\\x88alot\").toString(), \"compressedtestdatacompressedalot\");\n    }),\n\n    it(\"MD6\", () => {\n        assert.strictEqual(chef.MD6(\"Head Over Heels\", {key: \"arty\"}).toString(), \"d8f7fe4931fbaa37316f76283d5f615f50ddd54afdc794b61da522556aee99ad\");\n    }),\n\n    it(\"Parse ASN.1 Hex string\", () => {\n        assert.strictEqual(chef.parseASN1HexString(chef.toHex(\"Mouth-watering\")).toString(), \"UNKNOWN(77) 7574682d7761746572696e67\\n\");\n    }),\n\n    it(\"Parse DateTime\", () => {\n        const result = chef.parseDateTime(\"06/07/2001 01:59:30\");\n        const expected = `Date: Friday 6th July 2001\nTime: 01:59:30\nPeriod: AM\nTimezone: UTC\nUTC offset: +0000\n\nDaylight Saving Time: false\nLeap year: false\nDays in this month: 31\n\nDay of year: 187\nWeek number: 27\nQuarter: 3`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"Parse IPV6 address\", () => {\n        const result = chef.parseIPv6Address(\"2001:0db8:85a3:0000:0000:8a2e:0370:7334\");\n        const expected = `Longhand:  2001:0db8:85a3:0000:0000:8a2e:0370:7334\nShorthand: 2001:db8:85a3::8a2e:370:7334\n\nThis is a documentation IPv6 address. This range should be used whenever an example IPv6 address is given or to model networking scenarios. Corresponds to 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4.\nDocumentation range: 2001:db8::/32`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"Parse URI\", () => {\n        const result = chef.parseURI(\"https://www.google.co.uk/search?q=almonds\");\n        const expected = `Protocol:\thttps:\nHostname:\twww.google.co.uk\nPath name:\t/search\nArguments:\n\\tq = almonds\n`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"Parse user agent\", () => {\n        const result = chef.parseUserAgent(\"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 \");\n        const expected = `Browser\n    Name: Firefox\n    Version: 47.0\nDevice\n    Model: unknown\n    Type: unknown\n    Vendor: unknown\nEngine\n    Name: Gecko\n    Version: 47.0\nOS\n    Name: Windows\n    Version: 7\nCPU\n    Architecture: amd64`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"PGP Encrypt and decrypt\", async () => {\n        const pbkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: Keybase OpenPGP v2.1.3\nComment: https://keybase.io/crypto\n\nxo0EXZtlowEEAKUqTFownTmqgXWu2KDrtyNYtFck7a16WM5QD95bFoAFFdnlwZ45\n6Vw8G8LCzHdyRXYp/JF1GknDrAd7nIRE+SuSz2yVK5nlOCfO1HFcg2Ov7e7/pBwd\nqawx9GUIsCKd/6NxwDuT4YqarLFsuwljRC/eQiibO+ejnhoiKcU69sTNABEBAAHN\nAMK0BBMBCgAeBQJdm2WjAhsvAwsJBwMVCggCHgECF4ADFgIBAhkBAAoJEGS79V2S\n7D0owtMD/RT+o4BQJ8NSQBDgkYf42uOOu1Ud6GuN89nX6n20yAZbmqQ8CHnHY+Qc\nl6ft4HnbIaNrI3arp/C2C+cwFypmt1BKyFEJUXO7ft3i/IxnjpCorDyAMCDckDvq\numa1LWtUHLb5s/ZuGMSHnhuji74IRWuIofNPdf7bCZW1GMbW9jNUzo0EXZtlowEE\nAL38zaNkPmUVQaowP696fayBo18Nxs0yOzC4+0TYv1B/k5aUb0Air2h+o/Xw4E42\nJh9gVdPSvhOAEqdV0UDe71wxa4cfAVMDY9v8ta81MWunChj3ISUk1oIQylTJNsY/\nb4KWOrLaOtBD9dyFGCzss5vLVdqdMjVIW2Cz0hb6IYG7ABEBAAHCwIMEGAEKAA8F\nAl2bZaMFCQ8JnAACGy4AqAkQZLv1XZLsPSidIAQZAQoABgUCXZtlowAKCRA16MU2\nu2hFTX+JBACZ27xk0Afny2jjSoRzqLMrhzE7DBGcg2QqecMdNre12hVompAWcS4l\nNFmPShKRi6UT8Zb38nD43vwfqwZImn60dOPqqAep3YF/Axm1u5HJb0aMEsb8O9jV\nsVmNJv9jVTzPdlTGFQjuaeJfk5lwxB+5/O9NcgDhPgRAk9xb4FrT+xzmA/4tD11C\nAdcITUkTZT4ZOo2418DGeaiaEqWcIkZeQG4Vh5TMj4QtZDwsYQhXPl5Zj1zKIN/1\ngRrKC+ztaQoDG8pJXTTtc9inRU++dhMqnRGrPcz0VfVXFaiH7PUCy+4WpP6r5Bs5\nYQ9ESHo+FsmIvDzU3e/PD0SfXfO4vqBrFYN8986NBF2bZaMBBADJafe0w9diaCNx\n3A7e8MqjbNrhrLkD2cPxXspCATX3SuI19d2+hMiHZfKTyadBTIa+ICxvqoxwxyZD\nraHSY3CWVZd1V4KB5mqf+3Zj5riLeGU0dtXwi/5c0bdUhBUgHiAMhi75p05jYih5\nKsNxPcK9hEwPu7B+QeHURMiIgojTGQARAQABwsCDBBgBCgAPBQJdm2WjBQkDwmcA\nAhsuAKgJEGS79V2S7D0onSAEGQEKAAYFAl2bZaMACgkQzdkMJSM5Bqg2rwP/Ue28\nm3Fdfgh5JxouZ3Dm2KUDhZL95B+vdMk72acdoU7SRjlyDT8cApRqYx+MIXb8WrPN\n1xCZnOM4zXeWIM0CAPQ1e/sCrK58L+P+eVngNmrW9epKtZ4L6hx+dqqja9vPZGQK\nCsFAhA6A1gWB++OLk9Y6H23tWIdKEXMeAX7492zDYgP+OSPS79EWAqXL8SvmDrbl\nWI5eiM6X5hAMrOjQqzXhatD7eP41N/FC3SfhyhX7hFbagO7MJG2AS5bmSvcuCdcN\nwDwXd94B+7bfYgJIRKbr272yDwkyzGn+zmxzvMUt6ak5PNzfmadvhMZvIfDftswp\nGYpXIUU0GObOgP2tpCGTErs=\n=m++F\n-----END PGP PUBLIC KEY BLOCK-----`;\n        const privateKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: Keybase OpenPGP v2.1.3\nComment: https://keybase.io/crypto\n\nxcEYBF2bZaMBBAClKkxaMJ05qoF1rtig67cjWLRXJO2teljOUA/eWxaABRXZ5cGe\nOelcPBvCwsx3ckV2KfyRdRpJw6wHe5yERPkrks9slSuZ5TgnztRxXINjr+3u/6Qc\nHamsMfRlCLAinf+jccA7k+GKmqyxbLsJY0Qv3kIomzvno54aIinFOvbEzQARAQAB\nAAP7BXVS5aN3/AkNqIvOiUQ7nqrr9s9NHYUOvJllFNucxZP6x2MyQAjjlJKV9kdF\ncOhxXDjXVHVIGPT4UUeoAgUHg6K0K5WLmmNaO1w7ayf9737OrhrQFblQNqh4J9BV\noP/cArJ5+j/4IGKGYuWy3kTpvtabedlWq99E9PYrDJHD8E8CANDjnboIRgmAwHwi\nZKqc5rNXIBl7fJgFdf96cWiMF/7j2nJuarJGJRQUGxDaBi5zZSTZnwfVJZrDboyb\nJCahLTMCAMpqP0wTM4Qs95HhJUBmAdBhqxXjiAMtMDnn0ue8qAtv4JRjPkfxXUsC\n4J4PExw6eMU7BCGInel5B6+jdpvURf8B/3koVTHTxyBR/OTpP8XiwOwreb/SleIS\nJMYiXx6akUoPtACfXyBYM0fqCNCq38ZYhNM89oJbu1Rm5LJHe0m0DY6d4c0AwrQE\nEwEKAB4FAl2bZaMCGy8DCwkHAxUKCAIeAQIXgAMWAgECGQEACgkQZLv1XZLsPSjC\n0wP9FP6jgFAnw1JAEOCRh/ja4467VR3oa43z2dfqfbTIBluapDwIecdj5ByXp+3g\nedsho2sjdqun8LYL5zAXKma3UErIUQlRc7t+3eL8jGeOkKisPIAwINyQO+q6ZrUt\na1Qctvmz9m4YxIeeG6OLvghFa4ih8091/tsJlbUYxtb2M1THwRgEXZtlowEEAL38\nzaNkPmUVQaowP696fayBo18Nxs0yOzC4+0TYv1B/k5aUb0Air2h+o/Xw4E42Jh9g\nVdPSvhOAEqdV0UDe71wxa4cfAVMDY9v8ta81MWunChj3ISUk1oIQylTJNsY/b4KW\nOrLaOtBD9dyFGCzss5vLVdqdMjVIW2Cz0hb6IYG7ABEBAAEAA/4xkx7hrM2vOL26\nt/5WPsM+WVGVAxZGAv549zvxuhEp4zBS0Ya6GJLm1GzaRzFwlyaZd1zN+ibJFdlI\nOtdwcvvIAqNBsJMcjl2eaVtWK/PYvwqS7mVfojK8zUsKKNFIL6z/JKv7gmXzGuKV\nS5aYUOUMQI3mliTuqQpfLewhYBtOeQIA42jDWJfxjWiejV6QSNmBYhLeOwi/CFrd\nYE6obpXqX0V3vVOqB1rw/VHfabkWBmdOu55muw9kCLYOR89HNF6NrwIA1d+cTU7p\neFgSUm/u1esS1ucAoxdOPZ7pkLv9+NLQNvjLThmOHCFXyTZr4aoHtnqSG8PcUAWs\nhyQ35+WpKWA7tQH9GqDFogK+8GjzgVl+vCEnaTV7H/69tS93m9z06hFRs4iEZwWC\n4oCUNqOFj6IFyiBf2cM0pmMX0ODLnIG5SDVfWaIFwsCDBBgBCgAPBQJdm2WjBQkP\nCZwAAhsuAKgJEGS79V2S7D0onSAEGQEKAAYFAl2bZaMACgkQNejFNrtoRU1/iQQA\nmdu8ZNAH58to40qEc6izK4cxOwwRnINkKnnDHTa3tdoVaJqQFnEuJTRZj0oSkYul\nE/GW9/Jw+N78H6sGSJp+tHTj6qgHqd2BfwMZtbuRyW9GjBLG/DvY1bFZjSb/Y1U8\nz3ZUxhUI7mniX5OZcMQfufzvTXIA4T4EQJPcW+Ba0/sc5gP+LQ9dQgHXCE1JE2U+\nGTqNuNfAxnmomhKlnCJGXkBuFYeUzI+ELWQ8LGEIVz5eWY9cyiDf9YEaygvs7WkK\nAxvKSV007XPYp0VPvnYTKp0Rqz3M9FX1VxWoh+z1AsvuFqT+q+QbOWEPREh6PhbJ\niLw81N3vzw9En13zuL6gaxWDfPfHwRgEXZtlowEEAMlp97TD12JoI3HcDt7wyqNs\n2uGsuQPZw/FeykIBNfdK4jX13b6EyIdl8pPJp0FMhr4gLG+qjHDHJkOtodJjcJZV\nl3VXgoHmap/7dmPmuIt4ZTR21fCL/lzRt1SEFSAeIAyGLvmnTmNiKHkqw3E9wr2E\nTA+7sH5B4dREyIiCiNMZABEBAAEAA/wJeGeSwtCaSm48OM4kMms8wu4JxW7PnQon\nC79z2g25CnbXda+O+TxajXMZ+tXX7qq5PtcICxteZCbK8NuWgmF1QqWWhS2ZLbAV\n5edTc0vw8FSDwiAeiHyKa5Hs4B3uJaB54uADPyOYHPfX/NhEOfNAleDgVoa1Toqf\nR50lFsGOVwIA/cetzK3+NTZ5W+V8DGShxv4u5qAhhGZRb0GA3TPAoshVjHWY34i1\nKivtI3/tLLNTaVSVblG2VVoydKelRhsjGwIAyy0E1KI5O2EhLsVsDwx9NtO4SmUG\nREZt/LRYp1p5+nsarfeCVKQ4qQ6eqdK71Z7tEICT0JXqgSjQsKYVdscR2wH9GiyR\nLuHX3Nnh+M8lUv36ZM5XrWEypRFQaNYssRzPpqU4f9oViSPxdADonxehDP4ICmFr\nvqT+etEmjr9dzp4ZSKLswsCDBBgBCgAPBQJdm2WjBQkDwmcAAhsuAKgJEGS79V2S\n7D0onSAEGQEKAAYFAl2bZaMACgkQzdkMJSM5Bqg2rwP/Ue28m3Fdfgh5JxouZ3Dm\n2KUDhZL95B+vdMk72acdoU7SRjlyDT8cApRqYx+MIXb8WrPN1xCZnOM4zXeWIM0C\nAPQ1e/sCrK58L+P+eVngNmrW9epKtZ4L6hx+dqqja9vPZGQKCsFAhA6A1gWB++OL\nk9Y6H23tWIdKEXMeAX7492zDYgP+OSPS79EWAqXL8SvmDrblWI5eiM6X5hAMrOjQ\nqzXhatD7eP41N/FC3SfhyhX7hFbagO7MJG2AS5bmSvcuCdcNwDwXd94B+7bfYgJI\nRKbr272yDwkyzGn+zmxzvMUt6ak5PNzfmadvhMZvIfDftswpGYpXIUU0GObOgP2t\npCGTErs=\n=Ya+/\n-----END PGP PRIVATE KEY BLOCK-----`;\n\n        const message = \"A Fool and His Money are Soon Parted\";\n\n        const encrypted = await chef.PGPEncrypt(message, {\n            publicKeyOfRecipient: pbkey,\n        });\n        const result = await chef.PGPDecrypt(encrypted, {\n            privateKeyOfRecipient: privateKey,\n        });\n\n        assert.strictEqual(result.toString(), message);\n    }),\n\n    it(\"Raw deflate\", () => {\n        assert.strictEqual(chef.rawInflate(chef.rawDeflate(\"Like Father Like Son\", { compressionType: \"Fixed Huffman Coding\"})).toString(), \"Like Father Like Son\");\n    }),\n\n    it(\"RC4\", () => {\n        assert.strictEqual(\n            chef.RC4(\"Go Out On a Limb\", {passphrase: {string: \"Under Your Nose\", option: \"UTF8\"}, inputFormat: \"UTF8\", outputFormat: \"Hex\"}).toString(),\n            \"7d17e60d9bc94b7f4095851c729e69a2\");\n    }),\n\n    it(\"RC4 Drop\", () => {\n        assert.strictEqual(\n            chef.RC4Drop(\"Go Out On a Limb\", {passphrase: {string: \"Under Your Nose\", option: \"UTF8\"}, inputFormat: \"UTF8\", outputFormat: \"Hex\"}).toString(),\n            \"b85cb1c4ed6bed8f260ab92829bba942\");\n    }),\n\n    it(\"Regular Expression\", () => {\n        assert.strictEqual(chef.regularExpression(\"Wouldn't Harm a Fly\", {regex: \"\\\\'[a-z]\"}).toString(), \"Wouldn't Harm a Fly\");\n    }),\n\n    it(\"Remove EXIF\", () => {\n        const result = chef.removeEXIF(fs.readFileSync(\"tests/node/sampleData/pic.jpg\"));\n        assert.strictEqual(result.toString().length, 4582);\n    }),\n\n    it(\"Scan for embedded files\", () => {\n        const result = chef.scanForEmbeddedFiles(fs.readFileSync(\"src/web/static/images/cook_male-32x32.png\"));\n        const expected = \"Scanning data for 'magic bytes' which may indicate embedded files.\";\n        assert.ok(result.toString().indexOf(expected) === 0);\n    }),\n\n    it(\"Scrypt\", () => {\n        assert.strictEqual(\n            chef.scrypt(\"Playing For Keeps\", {salt: {string: \"salty\", option: \"Hex\"}}).toString(),\n            \"5446b6d86d88515894a163201765bceed0bc39610b1506cdc4d939ffc638bc46e051bce756e2865165d89d955a43a7eb5504502567dea8bfc9e7d49aaa894c07\");\n    }),\n\n    it(\"SHA3\", () => {\n        assert.strictEqual(\n            chef.SHA3(\"benign gravel\").toString(),\n            \"2b1e36e0dbe151a89887be08da3bad141908cce62327f678161bcf058627e87abe57e3c5fce6581678714e6705a207acbd5c1f37f7a812280bc2cc558f00bed9\");\n    }),\n\n    it(\"Shake\", () => {\n        assert.strictEqual(\n            chef.shake(\"murderous bloodshed\").toString(),\n            \"b79b3bb88099330bc6a15122f8dfaededf57a33b51c748d5a94e8122ff18d21e12f83412926b7e4a77a85ba6f36aa4841685e78296036337175e40096b5ac000\");\n    }),\n\n    it(\"Snefru\", () => {\n        assert.strictEqual(\n            chef.snefru(\"demeaning milestone\", {size: 256, rounds: 8}).toString(),\n            \"a671b48770fe073ce49e9259cc2f47d345a53712639f8ae23c5ad3fec19540a5\");\n    }),\n\n    it(\"SQL Beautify\", () => {\n        const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F FROM STATS;`);\n        const expected =\n`SELECT\n  MONTH,\n  ID,\n  RAIN_I,\n  TEMP_F\nFROM\n  STATS;`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"SSDEEP\", () => {\n        assert.strictEqual(\n            chef.SSDEEP(\"shotgun tyranny snugly\").toString(),\n            \"3:DLIXzMQCJc:XERKc\");\n    }),\n\n    it(\"strings\", () => {\n        const result = chef.strings(\"smothering ampersand abreast\", {displayTotal: true});\n        const expected = `Total found: 1\n\nsmothering ampersand abreast`;\n        assert.strictEqual(result.toString(), expected);\n    }),\n\n    it(\"toBase64: editableOption\", () => {\n        const result = toBase64(\"some input\", {\n            alphabet: {\n                value: \"0-9A-W+/a-zXYZ=\"\n            },\n        });\n        assert.strictEqual(result.toString(), \"StXkPI1gRe1sT0==\");\n    }),\n\n    it(\"toBase64: editableOptions key is value\", () => {\n        const result = toBase64(\"some input\", {\n            alphabet: \"0-9A-W+/a-zXYZ=\",\n        });\n        assert.strictEqual(result.toString(), \"StXkPI1gRe1sT0==\");\n    }),\n\n    it(\"toBase64: editableOptions default\", () => {\n        const result = toBase64(\"some input\");\n        assert.strictEqual(result.toString(), \"c29tZSBpbnB1dA==\");\n    }),\n\n    it(\"To BCD\", () => {\n        assert.strictEqual(chef.toBCD(\"443\").toString(), \"0100 0100 0011\");\n    }),\n\n    it(\"To CamelCase\", () => {\n        assert.strictEqual(chef.toCamelCase(\"Quickest Wheel\").toString(), \"quickestWheel\");\n    }),\n\n    it(\"toHex: accepts args\", () => {\n        const result = toHex(\"some input\", {\n            delimiter: \"Colon\",\n        });\n        assert.strictEqual(result.toString(), \"73:6f:6d:65:20:69:6e:70:75:74\");\n    }),\n\n    it(\"To Kebab case\", () => {\n        assert.strictEqual(chef.toKebabCase(\"Elfin Gold\").toString(), \"elfin-gold\");\n    }),\n\n    it(\"To punycode\", () => {\n        assert.strictEqual(chef.toPunycode(\"♠ ♣ ♥ ♦ ← ↑ ‍ →\").toString(), \"       -m06cw7klao368lfb3aq\");\n    }),\n\n    it(\"to snake case\", () => {\n        assert.strictEqual(chef.toSnakeCase(\"Abhorrent Grass\").value, \"abhorrent_grass\");\n    }),\n\n    it(\"to unix timestamp\", () => {\n        assert.strictEqual(chef.toUNIXTimestamp(\"2001-04-01\").toString(), \"986083200 (Sun 1 April 2001 00:00:00 UTC)\");\n    }),\n\n    it(\"Translate DateTime format\", () => {\n        assert.strictEqual(chef.translateDateTimeFormat(\"01/04/1999 22:33:01\").toString(), \"Thursday 1st April 1999 22:33:01 +00:00 UTC\");\n    }),\n\n    it(\"Triple DES encrypt / decrypt\", () => {\n        assert.strictEqual(\n            chef.tripleDESDecrypt(\n                chef.tripleDESEncrypt(\"Destroy Money\", {\n                    key: {string: \"30 31 2f 30 34 2f 31 39 39 39 20 32 32 3a 33 33 3a 30 3130 31 2f 30 34\", option: \"Hex\"},\n                    iv: {string: \"00 00 00 00 00 00 00 00\", option: \"Hex\"}}),\n                {\n                    key: {string: \"30 31 2f 30 34 2f 31 39 39 39 20 32 32 3a 33 33 3a 30 3130 31 2f 30 34\", option: \"Hex\"},\n                    iv: {string: \"00 00 00 00 00 00 00 00\", option: \"Hex\"}\n                }).toString(),\n            \"Destroy Money\");\n    }),\n\n    it(\"UNIX Timestamp to Windows Filetime\", () => {\n        assert.strictEqual(chef.UNIXTimestampToWindowsFiletime(\"2020735\").toString(), \"116464943350000000\");\n    }),\n\n    it(\"XML Beautify\", () => {\n        assert.strictEqual(\n            chef.XMLBeautify(\"<contact-info><company>abc</company></contact-info>\").toString(),\n            `<contact-info>\n\\\\t<company>abc</company>\n</contact-info>`);\n    }),\n\n    it(\"XOR: toggleString with default option\", () => {\n        assert.strictEqual(chef.XOR(\"fe023da5\", {\n            key: \"73 6f 6d 65\"\n        }).toString(),\n        \"\\u0015\\n]W@\\u000b\\fP\");\n    }),\n\n    it(\"XOR: toggleString with custom option\", () => {\n        assert.strictEqual(chef.XOR(\"fe023da5\", {\n            key: {\n                string: \"73 6f 6d 65\",\n                option: \"utf8\",\n            }\n        }).toString(),\n        \"QV\\u0010\\u0004UDWQ\");\n    }),\n\n    it(\"XPath expression\", () => {\n        assert.strictEqual(\n            chef.XPathExpression(\"<contact-info><company>abc</company></contact-info>\", {xPath: \"contact-info/company\"}).toString(),\n            \"<company>abc</company>\");\n    }),\n\n    it(\"Zlib deflate / inflate\", () => {\n        assert.strictEqual(chef.zlibInflate(chef.zlibDeflate(\"cut homer wile rooky grits dizen\")).toString(), \"cut homer wile rooky grits dizen\");\n    }),\n\n    it(\"extract EXIF\", () => {\n        assert.strictEqual(\n            chef.extractEXIF(fs.readFileSync(\"tests/node/sampleData/pic.jpg\")).toString(),\n            `Found 7 tags.\n\nOrientation: 1\nXResolution: 72\nYResolution: 72\nResolutionUnit: 2\nColorSpace: 1\nExifImageWidth: 57\nExifImageHeight: 57`);\n    }),\n\n    it(\"Tar\", () => {\n        const tarred = chef.tar(\"some file content\", {\n            filename: \"test.txt\"\n        });\n        assert.strictEqual(tarred.type, 7);\n        assert.strictEqual(tarred.value.size, 2048);\n        assert.strictEqual(tarred.value.data.toString().substr(0, 8), \"test.txt\");\n    }),\n\n    it(\"Untar\", () => {\n        const tarred = chef.tar(\"some file content\", {\n            filename: \"filename.txt\",\n        });\n        const untarred = chef.untar(tarred);\n        assert.strictEqual(untarred.type, 8);\n        assert.strictEqual(untarred.value.length, 1);\n        assert.strictEqual(untarred.value[0].name, \"filename.txt\");\n        assert.strictEqual(untarred.value[0].data.toString(), \"some file content\");\n    }),\n\n    it(\"Zip\", () => {\n        const zipped = chef.zip(\"some file content\", {\n            filename: \"sample.zip\",\n            comment: \"added\",\n            operatingSystem: \"Unix\",\n        });\n\n        assert.strictEqual(zipped.type, 7);\n        assert.ok(zipped.value.data.toString().includes(\"sample.zip\"));\n        assert.ok(zipped.value.data.toString().includes(\"added\"));\n    }),\n\n    it(\"Unzip\", () => {\n        const zipped = chef.zip(\"some file content\", {\n            filename: \"zipped.zip\",\n            comment: \"zippy\",\n        });\n        const unzipped = chef.unzip(zipped);\n\n        assert.equal(unzipped.type, 8);\n        assert.equal(unzipped.value[0].data, \"some file content\");\n        assert.equal(unzipped.value[0].name, \"zipped.zip\");\n    }),\n\n    it(\"Unzip with password\", () => {\n        const zipped = chef.zip(\"some content\", {\n            password: \"abcd\",\n        });\n        const unzipped = chef.unzip(zipped, {\n            password: \"abcd\",\n        });\n\n        assert.equal(unzipped.value[0].data, \"some content\");\n    }),\n\n    it(\"YARA Rule Matching\", async () => {\n        const input = \"foobar foobar bar foo foobar\";\n        const output = \"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\";\n\n        const res = await chef.YARARules(input, {\n            rules: \"rule foo {strings: $re1 = /foo/ condition: $re1} rule bar {strings: $re1 = /bar/ condition: $re1}\",\n            showStrings: true,\n            showStringLengths: true,\n            showMetadata: true\n        });\n\n        assert.equal(output, res.value);\n    }),\n\n    it(\"performs MAGIC\", async () => {\n        const input = \"WUagwsiae6mP8gNtCCLUFpCpCB26RmBDoDD8PacdAmzAzBVjkK2QstFXaKhpC6iUS7RHqXrJtFisoRSgoJ4whjm1arm864qaNq4RcfUmLHrcsAaZc5TXCYifNdgS83gDeejGX46gaiMyuBV6EskHt1scgJ88x2tNSotQDwbGY1mmCob2ARGFvCKYNqiN9ipMq1ZU1mgkdbNuGcb76aRtYWhCGUc8g93UJudhb8htsheZnwTpgqhx83SVJSZXMXUjJT2zmpC7uXWtumqokbdSi88YtkWDAc1Toouh2oH4D4ddmNKJWUDpMwmngUmK14xwmomccPQE9hM172APnSqwxdKQ172RkcAsysnmj5gGtRmVNNh2s359wr6mS2QRP\";\n        const depth = 1;\n\n        const res = await chef.magic(input, {\n            depth,\n        });\n\n        // assert against the structure of the output, rather than the values.\n        assert.strictEqual(res.value.length, depth + 1);\n        res.value.forEach(row => {\n            assert.ok(row.recipe);\n            assert.ok(row.data);\n            assert.ok(row.languageScores);\n            assert.ok(Object.prototype.hasOwnProperty.call(row, \"fileType\")); // Can be null, so cannot just use ok\n            assert.ok(row.entropy);\n            assert.ok(row.matchingOps);\n            assert.ok(Object.prototype.hasOwnProperty.call(row, \"useful\"));\n            assert.ok(Object.prototype.hasOwnProperty.call(row, \"matchesCrib\"));\n\n            row.recipe.forEach(item => {\n                assert.ok(Object.prototype.hasOwnProperty.call(item, \"op\"),  `No 'op' property in item ${item}`);\n                assert.strictEqual(typeof item.op, \"string\");\n                assert.ok(Object.prototype.hasOwnProperty.call(item, \"args\"),  `No 'args' property in item ${item}`);\n                assert.ok(Array.isArray(item.args));\n            });\n\n            row.languageScores.forEach(score => {\n                assert.ok(Object.prototype.hasOwnProperty.call(score, \"lang\"), `No 'lang' property in languageScore ${score}`);\n                assert.strictEqual(typeof score.lang, \"string\");\n                assert.ok(Object.prototype.hasOwnProperty.call(score, \"score\"),  `No 'score' property in languageScore ${score}`);\n                assert.strictEqual(typeof score.score, \"number\");\n                assert.ok(Object.prototype.hasOwnProperty.call(score, \"probability\"),  `No 'probability' property in languageScore ${score}`);\n                assert.strictEqual(typeof score.probability, \"number\");\n            });\n\n            row.matchingOps.forEach(op => {\n                assert.ok(Object.prototype.hasOwnProperty.call(op, \"op\"), `No 'op' property in matchingOp ${JSON.stringify(op)}`);\n                assert.strictEqual(typeof op.op, \"string\");\n                assert.ok(Object.prototype.hasOwnProperty.call(op, \"pattern\"), `No 'pattern' property in matchingOp ${JSON.stringify(op)}`);\n                assert.ok(op.pattern instanceof RegExp);\n                assert.ok(Object.prototype.hasOwnProperty.call(op, \"args\"), `No 'args' property in matchingOp ${JSON.stringify(op)}`);\n                assert.ok(Array.isArray(op.args));\n                assert.ok(Object.prototype.hasOwnProperty.call(op, \"useful\"), `No 'useful' property in matchingOp ${JSON.stringify(op)}`);\n                assert.ifError(op.useful); // Expect this to be undefined\n                assert.ok(Object.prototype.hasOwnProperty.call(op, \"entropyRange\"), `No 'entropyRange' property in matchingOp ${JSON.stringify(op)}`);\n                assert.ifError(op.entropyRange); // Expect this to be undefined\n                assert.ok(Object.prototype.hasOwnProperty.call(op, \"output\"), `No 'output' property in matchingOp ${JSON.stringify(op)}`);\n                assert.ifError(op.output); // Expect this to be undefined\n            });\n        });\n\n    }),\n\n\n]);\n\n"
  },
  {
    "path": "tests/operations/index.mjs",
    "content": "/* eslint no-console: 0 */\n\n/**\n * Test Runner\n *\n * For running the tests in the test register.\n *\n * @author tlwr [toby@toby.codes]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport { setLongTestFailure, logTestReport } from \"../lib/utils.mjs\";\n\nimport TestRegister from \"../lib/TestRegister.mjs\";\nimport \"./tests/A1Z26CipherDecode.mjs\";\nimport \"./tests/AESKeyWrap.mjs\";\nimport \"./tests/AlternatingCaps.mjs\";\nimport \"./tests/AvroToJSON.mjs\";\nimport \"./tests/BaconCipher.mjs\";\nimport \"./tests/Base32.mjs\";\nimport \"./tests/Base45.mjs\";\nimport \"./tests/Base58.mjs\";\nimport \"./tests/Base62.mjs\";\nimport \"./tests/Base64.mjs\";\nimport \"./tests/Base85.mjs\";\nimport \"./tests/Base92.mjs\";\nimport \"./tests/BCD.mjs\";\nimport \"./tests/Bech32.mjs\";\nimport \"./tests/BitwiseOp.mjs\";\nimport \"./tests/BLAKE2b.mjs\";\nimport \"./tests/BLAKE2s.mjs\";\nimport \"./tests/BLAKE3.mjs\";\nimport \"./tests/Bombe.mjs\";\nimport \"./tests/BSON.mjs\";\nimport \"./tests/ByteRepr.mjs\";\nimport \"./tests/CaesarBoxCipher.mjs\";\nimport \"./tests/CaretMdecode.mjs\";\nimport \"./tests/CartesianProduct.mjs\";\nimport \"./tests/CBORDecode.mjs\";\nimport \"./tests/CBOREncode.mjs\";\nimport \"./tests/CetaceanCipherDecode.mjs\";\nimport \"./tests/CetaceanCipherEncode.mjs\";\nimport \"./tests/ChaCha.mjs\";\nimport \"./tests/ChangeIPFormat.mjs\";\nimport \"./tests/CharEnc.mjs\";\nimport \"./tests/Charts.mjs\";\nimport \"./tests/Ciphers.mjs\";\nimport \"./tests/CipherSaber2.mjs\";\nimport \"./tests/CMAC.mjs\";\nimport \"./tests/Code.mjs\";\nimport \"./tests/Colossus.mjs\";\nimport \"./tests/Comment.mjs\";\nimport \"./tests/Compress.mjs\";\nimport \"./tests/ConditionalJump.mjs\";\nimport \"./tests/ConvertCoordinateFormat.mjs\";\nimport \"./tests/ConvertLeetSpeak.mjs\";\nimport \"./tests/ConvertToNATOAlphabet.mjs\";\nimport \"./tests/CRCChecksum.mjs\";\nimport \"./tests/Crypt.mjs\";\nimport \"./tests/CSV.mjs\";\nimport \"./tests/DateTime.mjs\";\nimport \"./tests/DefangIP.mjs\";\nimport \"./tests/DisassembleARM.mjs\";\nimport \"./tests/DropNthBytes.mjs\";\nimport \"./tests/ECDSA.mjs\";\nimport \"./tests/ELFInfo.mjs\";\nimport \"./tests/Enigma.mjs\";\nimport \"./tests/ExtractAudioMetadata.mjs\";\nimport \"./tests/ExtractEmailAddresses.mjs\";\nimport \"./tests/ExtractHashes.mjs\";\nimport \"./tests/ExtractIPAddresses.mjs\";\nimport \"./tests/Float.mjs\";\nimport \"./tests/FileTree.mjs\";\nimport \"./tests/FletcherChecksum.mjs\";\nimport \"./tests/Fork.mjs\";\nimport \"./tests/FromDecimal.mjs\";\nimport \"./tests/GenerateAllChecksums.mjs\";\nimport \"./tests/GenerateAllHashes.mjs\";\nimport \"./tests/GenerateDeBruijnSequence.mjs\";\nimport \"./tests/GenerateQRCode.mjs\";\nimport \"./tests/GetAllCasings.mjs\";\nimport \"./tests/GOST.mjs\";\nimport \"./tests/Gunzip.mjs\";\nimport \"./tests/Gzip.mjs\";\nimport \"./tests/Hash.mjs\";\nimport \"./tests/HASSH.mjs\";\nimport \"./tests/HaversineDistance.mjs\";\nimport \"./tests/Hex.mjs\";\nimport \"./tests/Hexdump.mjs\";\nimport \"./tests/HKDF.mjs\";\nimport \"./tests/Image.mjs\";\nimport \"./tests/IndexOfCoincidence.mjs\";\nimport \"./tests/JA3Fingerprint.mjs\";\nimport \"./tests/JA4.mjs\";\nimport \"./tests/JA3SFingerprint.mjs\";\nimport \"./tests/Jsonata.mjs\";\nimport \"./tests/JSONBeautify.mjs\";\nimport \"./tests/JSONMinify.mjs\";\nimport \"./tests/JSONtoCSV.mjs\";\nimport \"./tests/Jump.mjs\";\nimport \"./tests/JWK.mjs\";\nimport \"./tests/JWTDecode.mjs\";\nimport \"./tests/JWTSign.mjs\";\nimport \"./tests/JWTVerify.mjs\";\nimport \"./tests/LevenshteinDistance.mjs\";\nimport \"./tests/Lorenz.mjs\";\nimport \"./tests/LS47.mjs\";\nimport \"./tests/LuhnChecksum.mjs\";\nimport \"./tests/LZNT1Decompress.mjs\";\nimport \"./tests/LZString.mjs\";\nimport \"./tests/Magic.mjs\";\nimport \"./tests/Media.mjs\";\nimport \"./tests/MIMEDecoding.mjs\";\nimport \"./tests/Modhex.mjs\";\nimport \"./tests/MorseCode.mjs\";\nimport \"./tests/MS.mjs\";\nimport \"./tests/MultipleBombe.mjs\";\nimport \"./tests/MurmurHash3.mjs\";\nimport \"./tests/NetBIOS.mjs\";\nimport \"./tests/NormaliseUnicode.mjs\";\nimport \"./tests/NTLM.mjs\";\nimport \"./tests/OTP.mjs\";\nimport \"./tests/ParseIPRange.mjs\";\nimport \"./tests/ParseObjectIDTimestamp.mjs\";\nimport \"./tests/ParseQRCode.mjs\";\nimport \"./tests/ParseSSHHostKey.mjs\";\nimport \"./tests/ParseTCP.mjs\";\nimport \"./tests/ParseTLSRecord.mjs\";\nimport \"./tests/ParseTLV.mjs\";\nimport \"./tests/ParseUDP.mjs\";\nimport \"./tests/PEMtoHex.mjs\";\nimport \"./tests/PGP.mjs\";\nimport \"./tests/PHP.mjs\";\nimport \"./tests/PHPSerialize.mjs\";\nimport \"./tests/PowerSet.mjs\";\nimport \"./tests/Protobuf.mjs\";\nimport \"./tests/PubKeyFromCert.mjs\";\nimport \"./tests/PubKeyFromPrivKey.mjs\";\nimport \"./tests/Rabbit.mjs\";\nimport \"./tests/RAKE.mjs\";\nimport \"./tests/Regex.mjs\";\nimport \"./tests/Register.mjs\";\nimport \"./tests/RisonEncodeDecode.mjs\";\nimport \"./tests/Rotate.mjs\";\nimport \"./tests/RSA.mjs\";\nimport \"./tests/Salsa20.mjs\";\nimport \"./tests/XSalsa20.mjs\";\nimport \"./tests/SeqUtils.mjs\";\nimport \"./tests/SetDifference.mjs\";\nimport \"./tests/SetIntersection.mjs\";\nimport \"./tests/SetUnion.mjs\";\nimport \"./tests/Shuffle.mjs\";\nimport \"./tests/SIGABA.mjs\";\nimport \"./tests/SM2.mjs\";\nimport \"./tests/SM4.mjs\";\nimport \"./tests/RC6.mjs\";\n// import \"./tests/SplitColourChannels.mjs\"; // Cannot test operations that use the File type yet\nimport \"./tests/SQLBeautify.mjs\";\nimport \"./tests/StrUtils.mjs\";\nimport \"./tests/StripIPv4Header.mjs\";\nimport \"./tests/StripTCPHeader.mjs\";\nimport \"./tests/StripUDPHeader.mjs\";\nimport \"./tests/Subsection.mjs\";\nimport \"./tests/SwapCase.mjs\";\nimport \"./tests/SymmetricDifference.mjs\";\nimport \"./tests/TakeNthBytes.mjs\";\nimport \"./tests/Template.mjs\";\nimport \"./tests/TextEncodingBruteForce.mjs\";\nimport \"./tests/TextIntegerConverter.mjs\";\nimport \"./tests/ToFromInsensitiveRegex.mjs\";\nimport \"./tests/TranslateDateTimeFormat.mjs\";\nimport \"./tests/Typex.mjs\";\nimport \"./tests/UnescapeString.mjs\";\nimport \"./tests/Unicode.mjs\";\nimport \"./tests/URLEncodeDecode.mjs\";\nimport \"./tests/RSA.mjs\";\nimport \"./tests/CBOREncode.mjs\";\nimport \"./tests/CBORDecode.mjs\";\nimport \"./tests/JA3Fingerprint.mjs\";\nimport \"./tests/JA3SFingerprint.mjs\";\nimport \"./tests/HASSH.mjs\";\nimport \"./tests/JSONtoYAML.mjs\";\n\n// Cannot test operations that use the File type yet\n// import \"./tests/SplitColourChannels.mjs\";\nimport \"./tests/YARA.mjs\";\nimport \"./tests/ParseCSR.mjs\";\nimport \"./tests/XXTEA.mjs\";\n\nconst testStatus = {\n    allTestsPassing: true,\n    counts: {\n        total: 0,\n    },\n};\n\nsetLongTestFailure();\n\nconst logOpsTestReport = logTestReport.bind(null, testStatus);\n\n(async function () {\n    const results = await TestRegister.runTests();\n    logOpsTestReport(results);\n})();\n"
  },
  {
    "path": "tests/operations/tests/A1Z26CipherDecode.mjs",
    "content": "/**\n * A1Z26 Cipher Decode tests\n *\n * @author brick-pixel\n * @copyright Crown Copyright 2026\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"A1Z26 Cipher Decode: basic decode\",\n        \"input\": \"8 5 12 12 15\",\n        \"expectedOutput\": \"hello\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"A1Z26 Cipher Decode\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        \"name\": \"A1Z26 Cipher Decode: empty input returns empty string\",\n        \"input\": \"\",\n        \"expectedOutput\": \"\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"A1Z26 Cipher Decode\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/AESKeyWrap.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"AES Key Wrap: RFC Test Vector, 128-bit data, 128-bit KEK\",\n        \"input\": \"00112233445566778899aabbccddeeff\",\n        \"expectedOutput\": \"1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: RFC Test Vector, 128-bit data, 192-bit KEK\",\n        \"input\": \"00112233445566778899aabbccddeeff\",\n        \"expectedOutput\": \"96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f1011121314151617\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: RFC Test Vector, 128-bit data, 256-bit KEK\",\n        \"input\": \"00112233445566778899aabbccddeeff\",\n        \"expectedOutput\": \"64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: RFC Test Vector, 192-bit data, 192-bit KEK\",\n        \"input\": \"00112233445566778899aabbccddeeff0001020304050607\",\n        \"expectedOutput\": \"031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f1011121314151617\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: RFC Test Vector, 192-bit data, 256-bit KEK\",\n        \"input\": \"00112233445566778899aabbccddeeff0001020304050607\",\n        \"expectedOutput\": \"a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: RFC Test Vector, 256-bit data, 256-bit KEK\",\n        \"input\": \"00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f\",\n        \"expectedOutput\": \"28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: RFC Test Vector, 128-bit data, 128-bit KEK\",\n        \"input\": \"1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5\",\n        \"expectedOutput\": \"00112233445566778899aabbccddeeff\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: RFC Test Vector, 128-bit data, 192-bit KEK\",\n        \"input\": \"96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d\",\n        \"expectedOutput\": \"00112233445566778899aabbccddeeff\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f1011121314151617\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: RFC Test Vector, 128-bit data, 256-bit KEK\",\n        \"input\": \"64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7\",\n        \"expectedOutput\": \"00112233445566778899aabbccddeeff\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: RFC Test Vector, 192-bit data, 192-bit KEK\",\n        \"input\": \"031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2\",\n        \"expectedOutput\": \"00112233445566778899aabbccddeeff0001020304050607\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f1011121314151617\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: RFC Test Vector, 192-bit data, 256-bit KEK\",\n        \"input\": \"a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1\",\n        \"expectedOutput\": \"00112233445566778899aabbccddeeff0001020304050607\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: RFC Test Vector, 256-bit data, 256-bit KEK\",\n        \"input\": \"28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21\",\n        \"expectedOutput\": \"00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: invalid KEK length\",\n        \"input\": \"00112233445566778899aabbccddeeff\",\n        \"expectedOutput\": \"KEK must be either 16, 24, or 32 bytes (currently 10 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00010203040506070809\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: invalid IV length\",\n        \"input\": \"00112233445566778899aabbccddeeff\",\n        \"expectedOutput\": \"IV must be 8 bytes (currently 6 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: input length not multiple of 8\",\n        \"input\": \"00112233445566778899aabbccddeeff0102\",\n        \"expectedOutput\": \"input must be 8n (n>=2) bytes (currently 18 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Wrap: input too short\",\n        \"input\": \"0011223344556677\",\n        \"expectedOutput\": \"input must be 8n (n>=2) bytes (currently 8 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Wrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: invalid KEK length\",\n        \"input\": \"1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5\",\n        \"expectedOutput\": \"KEK must be either 16, 24, or 32 bytes (currently 10 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00010203040506070809\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: invalid IV length\",\n        \"input\": \"1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5\",\n        \"expectedOutput\": \"IV must be 8 bytes (currently 6 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: input length not multiple of 8\",\n        \"input\": \"1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5e621\",\n        \"expectedOutput\": \"input must be 8n (n>=3) bytes (currently 26 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: input too short\",\n        \"input\": \"1fa68b0a8112b447aef34bd8fb5a7b82\",\n        \"expectedOutput\": \"input must be 8n (n>=3) bytes (currently 16 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"AES Key Unwrap: corrupted input\",\n        \"input\": \"1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe6\",\n        \"expectedOutput\": \"IV mismatch\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"AES Key Unwrap\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c0d0e0f\"},\n                    {\"option\": \"Hex\", \"string\": \"a6a6a6a6a6a6a6a6\"},\n                    \"Hex\", \"Hex\"\n                ],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/AlternatingCaps.mjs",
    "content": "/* @author sw5678\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"AlternatingCaps: Basic Example\",\n        \"input\": \"Hello, world!\",\n        \"expectedOutput\": \"hElLo, WoRlD!\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Alternating Caps\",\n                \"args\": []\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/AvroToJSON.mjs",
    "content": "/**\n * Avro to JSON tests.\n *\n * @author jarrodconnolly [jarrod@nestedquotes.ca]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Avro to JSON: no input (force JSON true)\",\n        input: \"\",\n        expectedOutput: \"Please provide an input.\",\n        recipeConfig: [\n            {\n                op: \"Avro to JSON\",\n                args: [true]\n            }\n        ],\n    },\n    {\n        name: \"Avro to JSON: no input (force JSON false)\",\n        input: \"\",\n        expectedOutput: \"Please provide an input.\",\n        recipeConfig: [\n            {\n                op: \"Avro to JSON\",\n                args: [false]\n            }\n        ],\n    },\n    {\n        name: \"Avro to JSON: small (force JSON true)\",\n        input: \"\\x4f\\x62\\x6a\\x01\\x04\\x16\\x61\\x76\\x72\\x6f\\x2e\\x73\\x63\\x68\\x65\\x6d\\x61\\x96\\x01\\x7b\\x22\\x74\\x79\\x70\\x65\\x22\\x3a\\x22\\x72\\x65\" +\n            \"\\x63\\x6f\\x72\\x64\\x22\\x2c\\x22\\x6e\\x61\\x6d\\x65\\x22\\x3a\\x22\\x73\\x6d\\x61\\x6c\\x6c\\x22\\x2c\\x22\\x66\\x69\\x65\\x6c\\x64\\x73\\x22\\x3a\" +\n            \"\\x5b\\x7b\\x22\\x6e\\x61\\x6d\\x65\\x22\\x3a\\x22\\x6e\\x61\\x6d\\x65\\x22\\x2c\\x22\\x74\\x79\\x70\\x65\\x22\\x3a\\x22\\x73\\x74\\x72\\x69\\x6e\\x67\" +\n            \"\\x22\\x7d\\x5d\\x7d\\x14\\x61\\x76\\x72\\x6f\\x2e\\x63\\x6f\\x64\\x65\\x63\\x08\\x6e\\x75\\x6c\\x6c\\x00\\x4e\\x02\\x47\\x63\\x2e\\x37\\x02\\xe5\\xb7\" +\n            \"\\x5c\\xda\\xb9\\xa6\\x2f\\x15\\x41\\x02\\x0e\\x0c\\x6d\\x79\\x6e\\x61\\x6d\\x65\\x4e\\x02\\x47\\x63\\x2e\\x37\\x02\\xe5\\xb7\\x5c\\xda\\xb9\\xa6\\x2f\" +\n            \"\\x15\\x41\",\n        expectedOutput: \"{\\n    \\\"name\\\": \\\"myname\\\"\\n}\",\n        recipeConfig: [\n            {\n                op: \"Avro to JSON\",\n                args: [true]\n            }\n        ],\n    },\n    {\n        name: \"Avro to JSON: small (force JSON false)\",\n        input: \"\\x4f\\x62\\x6a\\x01\\x04\\x16\\x61\\x76\\x72\\x6f\\x2e\\x73\\x63\\x68\\x65\\x6d\\x61\\x96\\x01\\x7b\\x22\\x74\\x79\\x70\\x65\\x22\\x3a\\x22\\x72\\x65\" +\n            \"\\x63\\x6f\\x72\\x64\\x22\\x2c\\x22\\x6e\\x61\\x6d\\x65\\x22\\x3a\\x22\\x73\\x6d\\x61\\x6c\\x6c\\x22\\x2c\\x22\\x66\\x69\\x65\\x6c\\x64\\x73\\x22\\x3a\" +\n            \"\\x5b\\x7b\\x22\\x6e\\x61\\x6d\\x65\\x22\\x3a\\x22\\x6e\\x61\\x6d\\x65\\x22\\x2c\\x22\\x74\\x79\\x70\\x65\\x22\\x3a\\x22\\x73\\x74\\x72\\x69\\x6e\\x67\" +\n            \"\\x22\\x7d\\x5d\\x7d\\x14\\x61\\x76\\x72\\x6f\\x2e\\x63\\x6f\\x64\\x65\\x63\\x08\\x6e\\x75\\x6c\\x6c\\x00\\x4e\\x02\\x47\\x63\\x2e\\x37\\x02\\xe5\\xb7\" +\n            \"\\x5c\\xda\\xb9\\xa6\\x2f\\x15\\x41\\x02\\x0e\\x0c\\x6d\\x79\\x6e\\x61\\x6d\\x65\\x4e\\x02\\x47\\x63\\x2e\\x37\\x02\\xe5\\xb7\\x5c\\xda\\xb9\\xa6\\x2f\" +\n            \"\\x15\\x41\",\n        expectedOutput: \"{\\\"name\\\":\\\"myname\\\"}\\n\",\n        recipeConfig: [\n            {\n                op: \"Avro to JSON\",\n                args: [false]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/BCD.mjs",
    "content": "/**\n * BCD tests\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"To BCD: default 0\",\n        input: \"0\",\n        expectedOutput: \"0000\",\n        recipeConfig: [\n            {\n                \"op\": \"To BCD\",\n                \"args\": [\"8 4 2 1\", true, false, \"Nibbles\"]\n            }\n        ]\n    },\n    {\n        name: \"To BCD: unpacked nibbles\",\n        input: \"1234567890\",\n        expectedOutput: \"0000 0001 0000 0010 0000 0011 0000 0100 0000 0101 0000 0110 0000 0111 0000 1000 0000 1001 0000 0000\",\n        recipeConfig: [\n            {\n                \"op\": \"To BCD\",\n                \"args\": [\"8 4 2 1\", false, false, \"Nibbles\"]\n            }\n        ]\n    },\n    {\n        name: \"To BCD: packed, signed bytes\",\n        input: \"1234567890\",\n        expectedOutput: \"00000001 00100011 01000101 01100111 10001001 00001100\",\n        recipeConfig: [\n            {\n                \"op\": \"To BCD\",\n                \"args\": [\"8 4 2 1\", true, true, \"Bytes\"]\n            }\n        ]\n    },\n    {\n        name: \"To BCD: packed, signed nibbles, 8 4 -2 -1\",\n        input: \"-1234567890\",\n        expectedOutput: \"0000 0111 0110 0101 0100 1011 1010 1001 1000 1111 0000 1101\",\n        recipeConfig: [\n            {\n                \"op\": \"To BCD\",\n                \"args\": [\"8 4 -2 -1\", true, true, \"Nibbles\"]\n            }\n        ]\n    },\n    {\n        name: \"From BCD: default 0\",\n        input: \"0000\",\n        expectedOutput: \"0\",\n        recipeConfig: [\n            {\n                \"op\": \"From BCD\",\n                \"args\": [\"8 4 2 1\", true, false, \"Nibbles\"]\n            }\n        ]\n    },\n    {\n        name: \"From BCD: packed, signed bytes\",\n        input: \"00000001 00100011 01000101 01100111 10001001 00001101\",\n        expectedOutput: \"-1234567890\",\n        recipeConfig: [\n            {\n                \"op\": \"From BCD\",\n                \"args\": [\"8 4 2 1\", true, true, \"Bytes\"]\n            }\n        ]\n    },\n    {\n        name: \"From BCD: Excess-3, unpacked, unsigned\",\n        input: \"00000100 00000101 00000110 00000111 00001000 00001001 00001010 00001011 00001100 00000011\",\n        expectedOutput: \"1234567890\",\n        recipeConfig: [\n            {\n                \"op\": \"From BCD\",\n                \"args\": [\"Excess-3\", false, false, \"Nibbles\"]\n            }\n        ]\n    },\n    {\n        name: \"BCD: raw 4 2 2 1, packed, signed\",\n        input: \"1234567890\",\n        expectedOutput: \"1234567890\",\n        recipeConfig: [\n            {\n                \"op\": \"To BCD\",\n                \"args\": [\"4 2 2 1\", true, true, \"Raw\"]\n            },\n            {\n                \"op\": \"From BCD\",\n                \"args\": [\"4 2 2 1\", true, true, \"Raw\"]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/BLAKE2b.mjs",
    "content": "/**\n * BitwiseOp tests\n *\n * @author h345983745\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"BLAKE2b: 512 - Hello World\",\n        input: \"Hello World\",\n        expectedOutput: \"4386a08a265111c9896f56456e2cb61a64239115c4784cf438e36cc851221972da3fb0115f73cd02486254001f878ab1fd126aac69844ef1c1ca152379d0a9bd\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2b\",\n                \"args\": [\"512\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    },\n    {\n        name: \"BLAKE2b: 384 - Hello World\",\n        input: \"Hello World\",\n        expectedOutput: \"4d388e82ca8f866e606b6f6f0be910abd62ad6e98c0adfc27cf35acf948986d5c5b9c18b6f47261e1e679eb98edf8e2d\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2b\",\n                \"args\": [\"384\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    },\n    {\n        name: \"BLAKE2b: 256 - Hello World\",\n        input: \"Hello World\",\n        expectedOutput: \"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2b\",\n                \"args\": [\"256\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    },\n    {\n        name: \"BLAKE2b: 160 - Hello World\",\n        input: \"Hello World\",\n        expectedOutput: \"6a8489e6fd6e51fae12ab271ec7fc8134dd5d737\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2b\",\n                \"args\": [\"160\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    },\n    {\n        name: \"BLAKE2b: Key Test\",\n        input: \"message data\",\n        expectedOutput: \"3d363ff7401e02026f4a4687d4863ced\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2b\",\n                \"args\": [\"128\", \"Hex\", {string: \"pseudorandom key\", option: \"UTF8\"}] }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/BLAKE2s.mjs",
    "content": "/**\n * BitwiseOp tests\n *\n * @author h345983745\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"BLAKE2s: 256 - Hello World\",\n        input: \"Hello World\",\n        expectedOutput: \"7706af019148849e516f95ba630307a2018bb7bf03803eca5ed7ed2c3c013513\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2s\",\n                \"args\": [\"256\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    },\n    {\n        name: \"BLAKE2s: 160 - Hello World\",\n        input: \"Hello World\",\n        expectedOutput: \"0e4fcfc2ee0097ac1d72d70b595a39e09a3c7c7e\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2s\",\n                \"args\": [\"160\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    },\n    {\n        name: \"BLAKE2s: 128 - Hello World\",\n        input: \"Hello World\",\n        expectedOutput: \"9964ee6f36126626bf864363edfa96f6\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2s\",\n                \"args\": [\"128\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    },\n    {\n        name: \"BLAKE2s: Key Test\",\n        input: \"Hello World\",\n        expectedOutput: \"9964ee6f36126626bf864363edfa96f6\",\n        recipeConfig: [\n            { \"op\": \"BLAKE2s\",\n                \"args\": [\"128\", \"Hex\", {string: \"\", option: \"UTF8\"}] }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/BLAKE3.mjs",
    "content": "/**\n * BLAKE3 tests.\n * @author xumptex [xumptex@outlook.fr]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"BLAKE3: 8 - Hello world\",\n        input: \"Hello world\",\n        expectedOutput: \"e7e6fb7d2869d109\",\n        recipeConfig: [\n            { \"op\": \"BLAKE3\",\n                \"args\": [8, \"\"] }\n        ]\n    },\n    {\n        name: \"BLAKE3: 16 - Hello world 2\",\n        input: \"Hello world 2\",\n        expectedOutput: \"2a3df5fe5f0d3fcdd995fc203c7f7c52\",\n        recipeConfig: [\n            { \"op\": \"BLAKE3\",\n                \"args\": [16, \"\"] }\n        ]\n    },\n    {\n        name: \"BLAKE3: 32 - Hello world\",\n        input: \"Hello world\",\n        expectedOutput: \"e7e6fb7d2869d109b62cdb1227208d4016cdaa0af6603d95223c6a698137d945\",\n        recipeConfig: [\n            { \"op\": \"BLAKE3\",\n                \"args\": [32, \"\"] }\n        ]\n    },\n    {\n        name: \"BLAKE3: Key Test\",\n        input: \"Hello world\",\n        expectedOutput: \"59dd23ac9d025690\",\n        recipeConfig: [\n            { \"op\": \"BLAKE3\",\n                \"args\": [8, \"ThiskeyisexactlythirtytwoBytesLo\"] }\n        ]\n    },\n    {\n        name: \"BLAKE3: Key Test 2\",\n        input: \"Hello world\",\n        expectedOutput: \"c8302c9634c1da42\",\n        recipeConfig: [\n            { \"op\": \"BLAKE3\",\n                \"args\": [8, \"ThiskeyisexactlythirtytwoByteslo\"] }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/BSON.mjs",
    "content": "/**\n * BSON tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"BSON serialise: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"BSON serialise\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"BSON serialise: basic\",\n        input: \"{\\\"hello\\\":\\\"world\\\"}\",\n        expectedOutput: \"\\x16\\x00\\x00\\x00\\x02hello\\x00\\x06\\x00\\x00\\x00world\\x00\\x00\",\n        recipeConfig: [\n            {\n                op: \"BSON serialise\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"BSON deserialise: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"BSON deserialise\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"BSON deserialise: basic\",\n        input: \"\\x16\\x00\\x00\\x00\\x02hello\\x00\\x06\\x00\\x00\\x00world\\x00\\x00\",\n        expectedOutput: \"{\\n  \\\"hello\\\": \\\"world\\\"\\n}\",\n        recipeConfig: [\n            {\n                op: \"BSON deserialise\",\n                args: [],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/BaconCipher.mjs",
    "content": "/**\n * Bacon Cipher tests.\n *\n * @author Karsten Silkenbäumer [github.com/kassi]\n * @copyright Karsten Silkenbäumer 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport { BACON_ALPHABETS, BACON_TRANSLATIONS } from \"../../../src/core/lib/Bacon.mjs\";\n\nconst alphabets = Object.keys(BACON_ALPHABETS);\nconst translations = BACON_TRANSLATIONS;\n\nTestRegister.addTests([\n    {\n        name: \"Bacon Decode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[0], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet 0/1\",\n        input: \"00011 00100 00010 01101 00011 01000 01100 00110 00001 00000 00010 01101 01100 10100 01101 10000 01001 10001\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[0], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet 0/1 inverse\",\n        input: \"11100 11011 11101 10010 11100 10111 10011 11001 11110 11111 11101 10010 10011 01011 10010 01111 10110 01110\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[0], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet A/B lower case\",\n        input: \"aaabb aabaa aaaba abbab aaabb abaaa abbaa aabba aaaab aaaaa aaaba abbab abbaa babaa abbab baaaa abaab baaab\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[1], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet A/B lower case inverse\",\n        input: \"bbbaa bbabb bbbab baaba bbbaa babbb baabb bbaab bbbba bbbbb bbbab baaba baabb ababb baaba abbbb babba abbba\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[1], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet A/B upper case\",\n        input: \"AAABB AABAA AAABA ABBAB AAABB ABAAA ABBAA AABBA AAAAB AAAAA AAABA ABBAB ABBAA BABAA ABBAB BAAAA ABAAB BAAAB\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[1], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet A/B upper case inverse\",\n        input: \"BBBAA BBABB BBBAB BAABA BBBAA BABBB BAABB BBAAB BBBBA BBBBB BBBAB BAABA BAABB ABABB BAABA ABBBB BABBA ABBBA\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[1], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet case code\",\n        input: \"thiS IsaN exampLe oF ThE bacON cIpher WIth upPPercasE letters tRanSLaTiNG to OnEs anD LoWErcase To zERoes. KS\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[2], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet case code inverse\",\n        input: \"THIs iS An EXAMPlE Of tHe BACon CiPHER wiTH UPppERCASe LETTERS TrANslAtIng TO oNeS ANd lOweRCASE tO ZerOES. ks\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[2], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet case code\",\n        input: \"A little example of the Bacon Cipher to be decoded. It is a working example and shorter than my others, but it anyways works tremendously. And just that's important, correct?\",\n        expectedOutput: \"DECODE\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[3], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: reduced alphabet case code inverse\",\n        input: \"Well, there's now another example which will be not only strange to read but sound weird for everyone not knowing what the thing is about. Nevertheless, works great out of the box.\",\n        expectedOutput: \"DECODE\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[0], translations[3], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet 0/1\",\n        input: \"00011 00100 00010 01110 00011 01000 01101 00110 00001 00000 00010 01110 01101 10110 01110 10001 01010 10010\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[0], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet 0/1 inverse\",\n        input: \"11100 11011 11101 10001 11100 10111 10010 11001 11110 11111 11101 10001 10010 01001 10001 01110 10101 01101\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[0], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet A/B lower case\",\n        input: \"aaabb aabaa aaaba abbba aaabb abaaa abbab aabba aaaab aaaaa aaaba abbba abbab babba abbba baaab ababa baaba\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[1], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet A/B lower case inverse\",\n        input: \"bbbaa bbabb bbbab baaab bbbaa babbb baaba bbaab bbbba bbbbb bbbab baaab baaba abaab baaab abbba babab abbab\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[1], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet A/B upper case\",\n        input: \"AAABB AABAA AAABA ABBBA AAABB ABAAA ABBAB AABBA AAAAB AAAAA AAABA ABBBA ABBAB BABBA ABBBA BAAAB ABABA BAABA\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[1], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet A/B upper case inverse\",\n        input: \"BBBAA BBABB BBBAB BAAAB BBBAA BABBB BAABA BBAAB BBBBA BBBBB BBBAB BAAAB BAABA ABAAB BAAAB ABBBA BABAB ABBAB\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[1], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet case code\",\n        input: \"thiS IsaN exampLe oF THe bacON cIpher WItH upPPercasE letters tRanSLAtiNG tO OnES anD LOwErcaSe To ZeRoeS. kS\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[2], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet case code inverse\",\n        input: \"THIs iSAn EXAMPlE Of thE BACon CiPHER wiTh UPppERCASe LETTERS TrANslaTIng To zEroES and LoWERcAsE tO oNEs. Ks\",\n        expectedOutput: \"DECODINGBACONWORKS\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[2], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet case code\",\n        input: \"A little example of the Bacon Cipher to be decoded. It is a working example and shorter than the first, but it anyways works tremendously. And just that's important, correct?\",\n        expectedOutput: \"DECODE\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[3], false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Decode: complete alphabet case code inverse\",\n        input: \"Well, there's now another example   which will be not only strange to read but sound weird for everyone knowing nothing what the thing is about. Nevertheless, works great out of the box. \",\n        expectedOutput: \"DECODE\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Decode\",\n                args: [alphabets[1], translations[3], true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[0], false, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet 0/1\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"10010 00111 00100 10000 00100 10001 00000 00101 01101 10101 00000 01100 00011 01000 10010 01000 10011 01011 01110 10001 01101 10011 00100 10000 10010 00111 00100 00101 00100 01100 00010 00100\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[0], false, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet 0/1 inverse\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"01101 11000 11011 01111 11011 01110 11111 11010 10010 01010 11111 10011 11100 10111 01101 10111 01100 10100 10001 01110 10010 01100 11011 01111 01101 11000 11011 11010 11011 10011 11101 11011\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[0], false, true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet 0/1, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"1001000111001001000000100'10001 00000 001010110110101, 000000110000011 0100010010 0100010011010110111010001 01101100110010010000 100100011100100 0010100100011000001000100.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[0], true, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet 0/1 inverse, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"0110111000110110111111011'01110 11111 110101001001010, 111111001111100 1011101101 1011101100101001000101110 10010011001101101111 011011100011011 1101011011100111110111011.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[0], true, true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet A/B\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"BAABA AABBB AABAA BAAAA AABAA BAAAB AAAAA AABAB ABBAB BABAB AAAAA ABBAA AAABB ABAAA BAABA ABAAA BAABB ABABB ABBBA BAAAB ABBAB BAABB AABAA BAAAA BAABA AABBB AABAA AABAB AABAA ABBAA AAABA AABAA\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[1], false, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet A/B inverse\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"ABBAB BBAAA BBABB ABBBB BBABB ABBBA BBBBB BBABA BAABA ABABA BBBBB BAABB BBBAA BABBB ABBAB BABBB ABBAA BABAA BAAAB ABBBA BAABA ABBAA BBABB ABBBB ABBAB BBAAA BBABB BBABA BBABB BAABB BBBAB BBABB\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[1], false, true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet A/B, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"BAABAAABBBAABAABAAAAAABAA'BAAAB AAAAA AABABABBABBABAB, AAAAAABBAAAAABB ABAAABAABA ABAAABAABBABABBABBBABAAAB ABBABBAABBAABAABAAAA BAABAAABBBAABAA AABABAABAAABBAAAAABAAABAA.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[1], true, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: reduced alphabet A/B inverse, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"ABBABBBAAABBABBABBBBBBABB'ABBBA BBBBB BBABABAABAABABA, BBBBBBAABBBBBAA BABBBABBAB BABBBABBAABABAABAAABABBBA BAABAABBAABBABBABBBB ABBABBBAAABBABB BBABABBABBBAABBBBBABBBABB.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[0], translations[1], true, true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet 0/1\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"10011 00111 00100 10001 00100 10010 00000 00101 01110 10111 00000 01101 00011 01000 10011 01001 10100 01100 01111 10010 01110 10101 00100 10001 10011 00111 00100 00101 00100 01101 00010 00100\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[0], false, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet 0/1 inverse\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"01100 11000 11011 01110 11011 01101 11111 11010 10001 01000 11111 10010 11100 10111 01100 10110 01011 10011 10000 01101 10001 01010 11011 01110 01100 11000 11011 11010 11011 10010 11101 11011\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[0], false, true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet 0/1, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"1001100111001001000100100'10010 00000 001010111010111, 000000110100011 0100010011 0100110100011000111110010 01110101010010010001 100110011100100 0010100100011010001000100.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[0], true, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet 0/1 inverse, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"0110011000110110111011011'01101 11111 110101000101000, 111111001011100 1011101100 1011001011100111000001101 10001010101101101110 011001100011011 1101011011100101110111011.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[0], true, true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet A/B\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"BAABB AABBB AABAA BAAAB AABAA BAABA AAAAA AABAB ABBBA BABBB AAAAA ABBAB AAABB ABAAA BAABB ABAAB BABAA ABBAA ABBBB BAABA ABBBA BABAB AABAA BAAAB BAABB AABBB AABAA AABAB AABAA ABBAB AAABA AABAA\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[1], false, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet A/B inverse\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"ABBAA BBAAA BBABB ABBBA BBABB ABBAB BBBBB BBABA BAAAB ABAAA BBBBB BAABA BBBAA BABBB ABBAA BABBA ABABB BAABB BAAAA ABBAB BAAAB ABABA BBABB ABBBA ABBAA BBAAA BBABB BBABA BBABB BAABA BBBAB BBABB\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[1], false, true]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet A/B, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"BAABBAABBBAABAABAAABAABAA'BAABA AAAAA AABABABBBABABBB, AAAAAABBABAAABB ABAAABAABB ABAABBABAAABBAAABBBBBAABA ABBBABABABAABAABAAAB BAABBAABBBAABAA AABABAABAAABBABAAABAAABAA.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[1], true, false]\n            }\n        ],\n    },\n    {\n        name: \"Bacon Encode: complete alphabet A/B inverse, keeping extra characters\",\n        input: \"There's a fox, and it jumps over the fence.\",\n        expectedOutput: \"ABBAABBAAABBABBABBBABBABB'ABBAB BBBBB BBABABAAABABAAA, BBBBBBAABABBBAA BABBBABBAA BABBAABABBBAABBBAAAAABBAB BAAABABABABBABBABBBA ABBAABBAAABBABB BBABABBABBBAABABBBABBBABB.\",\n        recipeConfig: [\n            {\n                op: \"Bacon Cipher Encode\",\n                args: [alphabets[1], translations[1], true, true]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Base32.mjs",
    "content": "/**\n * Base32 Tests\n *\n * @author Peter C-S [petercs@purelymail.com]\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport {ALPHABET_OPTIONS} from \"../../../src/core/lib/Base32.mjs\";\n\n// Example Standard Base32 Tests\nconst STANDARD_INP = \"HELLO BASE32\";\nconst STANDARD_OUT = \"JBCUYTCPEBBECU2FGMZA====\";\n\n// Example Hex Extended Base32 Tests\nconst EXTENDED_INP = \"HELLO BASE32 EXTENDED\";\nconst EXTENDED_OUT = \"912KOJ2F41142KQ56CP20HAOAH2KSH258G======\";\n\n// All Bytes\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nconst ALL_BYTES_EXTENDED_OUT = \"000G40O40K30E209185GO38E1S8124GJ2GAHC5OO34D1M70T3OFI08924CI2A9H750KIKAPC5KN2UC1H68PJ8D9M6SS3IEHR7GUJSFQ085146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB9DLONAUBTG62OJ3CHIMCPR8D5L6MR3DDPNN0SBIEDQ7ATJNF1SNKURSFLV7V041GA1O91C6GU48J2KBHI6OT3SGI699754LIQBPH6CQJEE9R7KVK2GQ58T4KMJAFA59LALQPBDELUOB3CLJMIQRDDTON6TBNF5TNQVS1GE2OF2CBHM7P34SLIUCPN7CVK6HQB9T9LEMQVCDJMMRRJETTNV0S7HE7P75SRJUHQFATFMERRNFU3OV5SVKUNRFFU7PVBTVPVFUVS======\";\nconst ALL_BYTES_STANDARD_OUT = \"AAAQEAYEAUDAOCAJBIFQYDIOB4IBCEQTCQKRMFYYDENBWHA5DYPSAIJCEMSCKJRHFAUSUKZMFUXC6MBRGIZTINJWG44DSOR3HQ6T4P2AIFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLJNVYXK6L5QGCYTDMRSWMZ3INFVGW3DNNZXXA4LSON2HK5TXPB4XU634PV7H7AEBQKBYJBMGQ6EITCULRSGY5D4QSGJJHFEVS2LZRGM2TOOJ3HU7UCQ2FI5EUWTKPKFJVKV2ZLNOV6YLDMVTWS23NN5YXG5LXPF5X274BQOCYPCMLRWHZDE4VS6MZXHM7UGR2LJ5JVOW27MNTWW33TO55X7A4HROHZHF43T6R2PK5PWO33XP6DY7F47U6X3PP6HZ7L57Z7P674======\";\n\nTestRegister.addTests([\n    {\n        name: \"To Base32 Standard: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base32\",\n                args: [ALPHABET_OPTIONS[0].value],\n            },\n        ],\n    },\n    {\n        name: \"To Base32 Hex Extended: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base32\",\n                args: [ALPHABET_OPTIONS[1].value],\n            },\n        ],\n    },\n    {\n        name: \"From Base32 Standard: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Base32\",\n                args: [ALPHABET_OPTIONS[0].value, false],\n            },\n        ],\n    },\n    {\n        name: \"From Base32 Hex Extended: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Base32\",\n                args: [ALPHABET_OPTIONS[1].value, false],\n            },\n        ],\n    },\n    {\n        name: \"To Base32 Standard: \" + STANDARD_INP,\n        input: STANDARD_INP,\n        expectedOutput: STANDARD_OUT,\n        recipeConfig: [\n            {\n                op: \"To Base32\",\n                args: [ALPHABET_OPTIONS[0].value],\n            },\n        ],\n    },\n    {\n        name: \"To Base32 Hex Extended: \" + EXTENDED_INP,\n        input: EXTENDED_INP,\n        expectedOutput: EXTENDED_OUT,\n        recipeConfig: [\n            {\n                op: \"To Base32\",\n                args: [ALPHABET_OPTIONS[1].value],\n            },\n        ],\n    },\n    {\n        name: \"From Base32 Standard: \" + STANDARD_OUT,\n        input: STANDARD_OUT,\n        expectedOutput: STANDARD_INP,\n        recipeConfig: [\n            {\n                op: \"From Base32\",\n                args: [ALPHABET_OPTIONS[0].value, false],\n            },\n        ],\n    },\n    {\n        name: \"From Base32 Hex Extended: \" + EXTENDED_OUT,\n        input: EXTENDED_OUT,\n        expectedOutput: EXTENDED_INP,\n        recipeConfig: [\n            {\n                op: \"From Base32\",\n                args: [ALPHABET_OPTIONS[1].value, false],\n            },\n        ],\n    },\n    {\n        name: \"To Base32 Hex Standard: All Bytes\",\n        input: ALL_BYTES,\n        expectedOutput: ALL_BYTES_STANDARD_OUT,\n        recipeConfig: [\n            {\n                op: \"To Base32\",\n                args: [ALPHABET_OPTIONS[0].value],\n            },\n        ],\n    },\n    {\n        name: \"To Base32 Hex Extended: All Bytes\",\n        input: ALL_BYTES,\n        expectedOutput: ALL_BYTES_EXTENDED_OUT,\n        recipeConfig: [\n            {\n                op: \"To Base32\",\n                args: [ALPHABET_OPTIONS[1].value],\n            },\n        ],\n    },\n    {\n        name: \"From Base32 Hex Standard: All Bytes\",\n        input: ALL_BYTES_STANDARD_OUT,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Base32\",\n                args: [ALPHABET_OPTIONS[0].value, false],\n            },\n        ],\n    },\n    {\n        name: \"From Base32 Hex Extended: All Bytes\",\n        input: ALL_BYTES_EXTENDED_OUT,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Base32\",\n                args: [ALPHABET_OPTIONS[1].value, false],\n            },\n        ],\n    },\n]);\n\n"
  },
  {
    "path": "tests/operations/tests/Base45.mjs",
    "content": "/**\n * Base45 tests.\n *\n * @author Thomas Weißschuh [thomas@t-8ch.de]\n *\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst defaultB45Alph = \"0-9A-Z $%*+\\\\-./:\";\n\nTestRegister.addTests([\n    {\n        name: \"To Base45: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n    {\n        name: \"To Base45: Spec encoding example 1\",\n        input: \"AB\",\n        expectedOutput: \"BB8\",\n        recipeConfig: [\n            {\n                op: \"To Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n    {\n        name: \"To Base45: Spec encoding example 2\",\n        input: \"Hello!!\",\n        expectedOutput: \"%69 VD92EX0\",\n        recipeConfig: [\n            {\n                op: \"To Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n    {\n        name: \"To Base45: Spec encoding example 3\",\n        input: \"base-45\",\n        expectedOutput: \"UJCLQE7W581\",\n        recipeConfig: [\n            {\n                op: \"To Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n    {\n        name: \"From Base45: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n    {\n        name: \"From Base45: Spec decoding example 1\",\n        input: \"QED8WEX0\",\n        expectedOutput: \"ietf!\",\n        recipeConfig: [\n            {\n                op: \"From Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n    {\n        name: \"From Base45: Invalid character\",\n        input: \"!\",\n        expectedOutput: \"Character not in alphabet: '!'\",\n        recipeConfig: [\n            {\n                op: \"From Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n    {\n        name: \"From Base45: Invalid triplet value\",\n        input: \"ZZZ\",\n        expectedOutput: \"Triplet too large: 'ZZZ'\",\n        recipeConfig: [\n            {\n                op: \"From Base45\",\n                args: [defaultB45Alph],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Base58.mjs",
    "content": "/**\n * Base58 tests.\n *\n * @author tlwr [toby@toby.codes]\n *\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"To Base58 (Bitcoin): nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base58\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base58 (Ripple): nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base58\",\n                args: [\"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base58 (Bitcoin): 'hello world'\",\n        input: \"hello world\",\n        expectedOutput: \"StV1DL6CwTryKyV\",\n        recipeConfig: [\n            {\n                op: \"To Base58\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base58 (Ripple): 'hello world'\",\n        input: \"hello world\",\n        expectedOutput: \"StVrDLaUATiyKyV\",\n        recipeConfig: [\n            {\n                op: \"To Base58\",\n                args: [\"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base58 all null\",\n        input: \"\\0\\0\\0\\0\\0\\0\",\n        expectedOutput: \"111111\",\n        recipeConfig: [\n            {\n                op: \"To Base58\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base58 all null\",\n        input: \"111111\",\n        expectedOutput: \"\\0\\0\\0\\0\\0\\0\",\n        recipeConfig: [\n            {\n                op: \"From Base58\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base58 with null prefix and suffix\",\n        input: \"\\0\\0\\0Hello\\0\\0\\0\",\n        expectedOutput: \"111D7LMXYjHjTu\",\n        recipeConfig: [\n            {\n                op: \"To Base58\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base58 with null prefix and suffix\",\n        input: \"111D7LMXYjHjTu\",\n        expectedOutput: \"\\0\\0\\0Hello\\0\\0\\0\",\n        recipeConfig: [\n            {\n                op: \"From Base58\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base58 (Bitcoin): 'StV1DL6CwTryKyV'\",\n        input: \"StV1DL6CwTryKyV\",\n        expectedOutput: \"hello world\",\n        recipeConfig: [\n            {\n                op: \"From Base58\",\n                args: [\"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base58 (Ripple): 'StVrDLaUATiyKyV'\",\n        input: \"StVrDLaUATiyKyV\",\n        expectedOutput: \"hello world\",\n        recipeConfig: [\n            {\n                op: \"From Base58\",\n                args: [\"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz\"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Base62.mjs",
    "content": "/**\n * Base62 tests.\n *\n * @author tcode2k16 [tcode2k16@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"To Base62: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base62\",\n                args: [\"0-9A-Za-z\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base62: Hello, World!\",\n        input: \"Hello, World!\",\n        expectedOutput: \"1wJfrzvdbtXUOlUjUf\",\n        recipeConfig: [\n            {\n                op: \"To Base62\",\n                args: [\"0-9A-Za-z\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base62: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"BPDNbjoGvDCDzHbKT77eWg0vGQrJuWRXltuRVZ\",\n        recipeConfig: [\n            {\n                op: \"To Base62\",\n                args: [\"0-9A-Za-z\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base62: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Base62\",\n                args: [\"0-9A-Za-z\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base62: Hello, World!\",\n        input: \"1wJfrzvdbtXUOlUjUf\",\n        expectedOutput: \"Hello, World!\",\n        recipeConfig: [\n            {\n                op: \"From Base62\",\n                args: [\"0-9A-Za-z\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base62: UTF-8\",\n        input: \"BPDNbjoGvDCDzHbKT77eWg0vGQrJuWRXltuRVZ\",\n        expectedOutput: \"ნუ პანიკას\",\n        recipeConfig: [\n            {\n                op: \"From Base62\",\n                args: [\"0-9A-Za-z\"],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Base64.mjs",
    "content": "/**\n * Base64 tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nTestRegister.addTests([\n    {\n        name: \"To Base64: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base64: Hello, World!\",\n        input: \"Hello, World!\",\n        expectedOutput: \"SGVsbG8sIFdvcmxkIQ==\",\n        recipeConfig: [\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base64: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==\",\n        recipeConfig: [\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n        ],\n    },\n    {\n        name: \"To Base64: All bytes\",\n        input: ALL_BYTES,\n        expectedOutput: \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\",\n        recipeConfig: [\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n        ],\n    },\n    {\n        name: \"From Base64: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Base64\",\n                args: [\"A-Za-z0-9+/=\", true],\n            },\n        ],\n    },\n    {\n        name: \"From Base64: Hello, World!\",\n        input: \"SGVsbG8sIFdvcmxkIQ==\",\n        expectedOutput: \"Hello, World!\",\n        recipeConfig: [\n            {\n                op: \"From Base64\",\n                args: [\"A-Za-z0-9+/=\", true],\n            },\n        ],\n    },\n    {\n        name: \"From Base64: UTF-8\",\n        input: \"4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==\",\n        expectedOutput: \"ნუ პანიკას\",\n        recipeConfig: [\n            {\n                op: \"From Base64\",\n                args: [\"A-Za-z0-9+/=\", true],\n            },\n        ],\n    },\n    {\n        name: \"From Base64: All bytes\",\n        input: \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\",\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Base64\",\n                args: [\"A-Za-z0-9+/=\", true],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Base85.mjs",
    "content": "/**\n * Base85 tests\n *\n * @author john19696\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n// Example from Wikipedia\nconst wpExample = \"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.\";\n// Escape newline, quote & backslash\nconst wpOutput = \"9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKF<GL>Cj@.4Gp$d7F!,L7@<6@)/0JDEF<G%<+EV:2F!,O<\\\nDJ+*.@<*K0@<6L(Df-\\\\0Ec5e;DffZ(EZee.Bl.9pF\\\"AGXBPCsi+DGm>@3BB/F*&OCAfu2/AKYi(\\\nDIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF<G:8+EV:.+Cf>-FD5W8ARlolDIal(\\\nDId<j@<?3r@:F%a+D58'ATD4$Bl@l3De:,-DJs`8ARoFb/0JMK@qB4^F!,R<AKZ&-DfTqBG%G>u\\\nD.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c\";\n\nconst allZeroExample = \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\";\n\nconst allZeroOutput = \"zz!!*-'\\\"9eu7#RLhG$k3[W&.oNg'GVB\\\"(`=52*$$(B+<_pR,UFcb-n-Vr/1iJ-0JP==1c70M3&s#]4?Ykm5X@_(6q'R884cEH9MJ8X:f1+h<)lt#=BSg3>[:ZC?t!MSA7]@cBPD3sCi+'.E,fo>FEMbNG^4U^I!pHnJ:W<)KS>/9Ll%\\\"IN/`jYOHG]iPa.Q$R$jD4S=Q7DTV8*TUnsrdW2ZetXKAY/Yd(L?['d?O\\\\@K2_]Y2%o^qmn*`5Ta:aN;TJbg\\\"GZd*^:jeCE.%f\\\\,!5gtgiEi8N\\\\UjQ5OekiqBum-X60nF?)@o_%qPq\\\"ad`r;HWp\";\n\nTestRegister.addTests([\n    {\n        name: \"To Base85\",\n        input: wpExample,\n        expectedOutput: wpOutput,\n        recipeConfig: [\n            { \"op\": \"To Base85\",\n                \"args\": [\"!-u\"] }\n        ]\n    },\n    {\n        name: \"From Base85\",\n        input: wpOutput + \"\\n\",\n        expectedOutput: wpExample,\n        recipeConfig: [\n            { \"op\": \"From Base85\",\n                \"args\": [\"!-u\", true] }\n        ]\n    },\n    {\n        name: \"From Base85\",\n        input: wpOutput + \"v\",\n        expectedError: true,\n        expectedOutput: \"From Base85 - Invalid character 'v' at index 337\",\n        recipeConfig: [\n            { \"op\": \"From Base85\",\n                \"args\": [\"!-u\", false] }\n        ]\n    },\n    {\n        name: \"To Base85\",\n        input: allZeroExample,\n        expectedOutput: allZeroOutput,\n        recipeConfig: [\n            { \"op\": \"To Base85\",\n                \"args\": [\"!-u\"] }\n        ]\n    },\n    {\n        name: \"From Base85\",\n        input: allZeroOutput,\n        expectedOutput: allZeroExample,\n        recipeConfig: [\n            { \"op\": \"From Base85\",\n                \"args\": [\"!-u\", true, \"z\"] }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Base92.mjs",
    "content": "/**\n * Base92 tests.\n *\n * @author sg5506844 [sg5506844@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"To Base92: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Base92\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Base92: Spec encoding example 1\",\n        input: \"AB\",\n        expectedOutput: \"8y2\",\n        recipeConfig: [\n            {\n                op: \"To Base92\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Base92: Spec encoding example 2\",\n        input: \"Hello!!\",\n        expectedOutput: \";K_$aOTo&\",\n        recipeConfig: [\n            {\n                op: \"To Base92\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Base92: Spec encoding example 3\",\n        input: \"base-92\",\n        expectedOutput: \"DX2?V<Y(*\",\n        recipeConfig: [\n            {\n                op: \"To Base92\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"From Base92: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Base92\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"From Base92: Spec decoding example 1\",\n        input: \"G'_DW[B\",\n        expectedOutput: \"ietf!\",\n        recipeConfig: [\n            {\n                op: \"From Base92\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"From Base92: Invalid character\",\n        input: \"~\",\n        expectedOutput: \"~ is not a base92 character\",\n        recipeConfig: [\n            {\n                op: \"From Base92\",\n                args: [],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Bech32.mjs",
    "content": "/**\n * Bech32 tests.\n *\n * Test vectors from official BIP specifications:\n * BIP-0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki\n * BIP-0350: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki\n *\n * AGE key test vectors from:\n * https://asecuritysite.com/age/go_age5\n *\n * @author Medjedtxm\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    // ============= To Bech32 Tests =============\n    {\n        name: \"To Bech32: empty input\",\n        input: \"\",\n        expectedOutput: \"bc1gmk9yu\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32: single byte\",\n        input: \"A\",\n        expectedOutput: \"bc1gyufle22\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32: Hello\",\n        input: \"Hello\",\n        expectedOutput: \"bc1fpjkcmr0gzsgcg\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32: custom HRP\",\n        input: \"test\",\n        expectedOutput: \"custom1w3jhxaq593qur\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"custom\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32: testnet HRP\",\n        input: \"data\",\n        expectedOutput: \"tb1v3shgcg3x07jr\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"tb\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32m: empty input\",\n        input: \"\",\n        expectedOutput: \"bc1a8xfp7\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32m\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32m: single byte\",\n        input: \"A\",\n        expectedOutput: \"bc1gyf4040g\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32m\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32m: Hello\",\n        input: \"Hello\",\n        expectedOutput: \"bc1fpjkcmr0a7qya2\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32m\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32: empty HRP error\",\n        input: \"test\",\n        expectedOutput: \"Human-Readable Part (HRP) cannot be empty.\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            }\n        ],\n    },\n\n    // ============= From Bech32 Tests (Raw output) =============\n    {\n        name: \"From Bech32: decode single byte (Raw)\",\n        input: \"bc1gyufle22\",\n        expectedOutput: \"A\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: decode Hello (Raw)\",\n        input: \"bc1fpjkcmr0gzsgcg\",\n        expectedOutput: \"Hello\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: auto-detect Bech32 (Raw)\",\n        input: \"bc1fpjkcmr0gzsgcg\",\n        expectedOutput: \"Hello\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32m: decode Hello (Raw)\",\n        input: \"bc1fpjkcmr0a7qya2\",\n        expectedOutput: \"Hello\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: auto-detect Bech32m (Raw)\",\n        input: \"bc1fpjkcmr0a7qya2\",\n        expectedOutput: \"Hello\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: uppercase input (Raw)\",\n        input: \"BC1FPJKCMR0GZSGCG\",\n        expectedOutput: \"Hello\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: custom HRP (Raw)\",\n        input: \"custom1w3jhxaq593qur\",\n        expectedOutput: \"test\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: empty input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: empty data part (Hex)\",\n        input: \"bc1gmk9yu\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Hex\"]\n            }\n        ],\n    },\n\n    // ============= From Bech32 HRP Output Tests =============\n    {\n        name: \"From Bech32: HRP: Hex output format\",\n        input: \"bc1fpjkcmr0gzsgcg\",\n        expectedOutput: \"bc: 48656c6c6f\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: JSON output format\",\n        input: \"bc1fpjkcmr0gzsgcg\",\n        expectedOutput: \"{\\n  \\\"hrp\\\": \\\"bc\\\",\\n  \\\"encoding\\\": \\\"Bech32\\\",\\n  \\\"data\\\": \\\"48656c6c6f\\\"\\n}\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"JSON\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: Hex output format\",\n        input: \"bc1fpjkcmr0gzsgcg\",\n        expectedOutput: \"48656c6c6f\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Hex\"]\n            }\n        ],\n    },\n\n    // ============= AGE Key Test Vectors =============\n    // From: https://asecuritysite.com/age/go_age5\n    {\n        name: \"From Bech32: AGE public key 1 (HRP: Hex)\",\n        input: \"age1kk86t4lr4s9uwvnqjzp2e35rflvcpnjt33q99547ct23xzk0ssss3ma49j\",\n        expectedOutput: \"age: b58fa5d7e3ac0bc732609082acc6834fd980ce4b8c4052d2bec2d5130acf8421\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: AGE private key 1 (HRP: Hex)\",\n        input: \"AGE-SECRET-KEY-1Z5N23X54Y4E9NLMPNH6EZDQQX9V883TMKJ3ZJF5QXXMKNZ2RPFXQUQF74G\",\n        expectedOutput: \"age-secret-key-: 1526a89a95257259ff619df5913400315873c57bb4a229268031b76989430a4c\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: AGE public key 2 (HRP: Hex)\",\n        input: \"age1nwt7gkq7udvalagqn7l8a4jgju7wtenkg925pvuqvn7cfcry6u2qkae4ad\",\n        expectedOutput: \"age: 9b97e4581ee359dff5009fbe7ed648973ce5e676415540b38064fd84e064d714\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: AGE private key 2 (HRP: Hex)\",\n        input: \"AGE-SECRET-KEY-137M0YVE3CL6M8C4ET9L2KU67FPQHJZTW547QD5CK0R5A5T09ZGJSQGR9LX\",\n        expectedOutput: \"age-secret-key-: 8fb6f23331c7f5b3e2b9597eab735e484179096ea57c06d31678e9da2de51225\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: AGE public key 1 (JSON)\",\n        input: \"age1kk86t4lr4s9uwvnqjzp2e35rflvcpnjt33q99547ct23xzk0ssss3ma49j\",\n        expectedOutput: \"{\\n  \\\"hrp\\\": \\\"age\\\",\\n  \\\"encoding\\\": \\\"Bech32\\\",\\n  \\\"data\\\": \\\"b58fa5d7e3ac0bc732609082acc6834fd980ce4b8c4052d2bec2d5130acf8421\\\"\\n}\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"JSON\"]\n            }\n        ],\n    },\n\n    // ============= Error Cases =============\n    {\n        name: \"From Bech32: mixed case error\",\n        input: \"bc1FpjKcmr0gzsgcg\",\n        expectedOutput: \"Invalid Bech32 string: mixed case is not allowed. Use all uppercase or all lowercase.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: no separator error\",\n        input: \"noseparator\",\n        expectedOutput: \"Invalid Bech32 string: no separator '1' found.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: empty HRP error\",\n        input: \"1qqqqqqqqqqqqqqqq\",\n        expectedOutput: \"Invalid Bech32 string: Human-Readable Part (HRP) cannot be empty.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: invalid checksum\",\n        input: \"bc1fpjkcmr0gzsgcx\",\n        expectedOutput: \"Invalid Bech32/Bech32m string: checksum verification failed.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: data too short\",\n        input: \"bc1abc\",\n        expectedOutput: \"Invalid Bech32 string: data part is too short (minimum 6 characters for checksum).\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: wrong encoding specified\",\n        input: \"bc1fpjkcmr0gzsgcg\",\n        expectedOutput: \"Invalid Bech32m checksum.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"Hex\"]\n            }\n        ],\n    },\n\n    // ============= BIP-0173 Test Vectors (Bech32) =============\n    {\n        name: \"From Bech32: BIP-0173 A12UEL5L (empty data)\",\n        input: \"A12UEL5L\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: BIP-0173 a12uel5l lowercase\",\n        input: \"a12uel5l\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: BIP-0173 long HRP with bio\",\n        input: \"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: BIP-0173 abcdef with data\",\n        input: \"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw\",\n        expectedOutput: \"abcdef: 00443214c74254b635cf84653a56d7c675be77df\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: BIP-0173 split HRP\",\n        input: \"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w\",\n        expectedOutput: \"split: c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: BIP-0173 question mark HRP\",\n        input: \"?1ezyfcl\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Hex\"]\n            }\n        ],\n    },\n\n    // ============= BIP-0350 Test Vectors (Bech32m) =============\n    {\n        name: \"From Bech32m: BIP-0350 A1LQFN3A (empty data)\",\n        input: \"A1LQFN3A\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32m: BIP-0350 a1lqfn3a lowercase\",\n        input: \"a1lqfn3a\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32m: BIP-0350 long HRP\",\n        input: \"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32m: BIP-0350 abcdef with data\",\n        input: \"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx\",\n        expectedOutput: \"abcdef: ffbbcdeb38bdab49ca307b9ac5a928398a418820\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32m: BIP-0350 split HRP\",\n        input: \"split1checkupstagehandshakeupstreamerranterredcaperredlc445v\",\n        expectedOutput: \"split: c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"HRP: Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32m: BIP-0350 question mark HRP\",\n        input: \"?1v759aa\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"Hex\"]\n            }\n        ],\n    },\n\n    // ============= Bitcoin scriptPubKey Output Format Tests =============\n    // Test vectors from BIP-0173 and BIP-0350\n    {\n        name: \"From Bech32: Bitcoin scriptPubKey v0 P2WPKH\",\n        input: \"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4\",\n        expectedOutput: \"0014751e76e8199196d454941c45d1b3a323f1433bd6\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Bitcoin scriptPubKey\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: Bitcoin scriptPubKey v0 P2WSH\",\n        input: \"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7\",\n        expectedOutput: \"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Bitcoin scriptPubKey\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: Bitcoin scriptPubKey v1 Taproot (Bech32m)\",\n        input: \"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0\",\n        expectedOutput: \"512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Bitcoin scriptPubKey\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: Bitcoin scriptPubKey v16\",\n        input: \"BC1SW50QGDZ25J\",\n        expectedOutput: \"6002751e\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Bitcoin scriptPubKey\"]\n            }\n        ],\n    },\n    {\n        name: \"From Bech32: Bitcoin scriptPubKey v2\",\n        input: \"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs\",\n        expectedOutput: \"5210751e76e8199196d454941c45d1b3a323\",\n        recipeConfig: [\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Bitcoin scriptPubKey\"]\n            }\n        ],\n    },\n\n    // ============= Bitcoin SegWit Encoding Tests =============\n    {\n        name: \"To Bech32: Bitcoin SegWit v0 P2WPKH\",\n        input: \"751e76e8199196d454941c45d1b3a323f1433bd6\",\n        expectedOutput: \"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32\", \"Hex\", \"Bitcoin SegWit\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32: Bitcoin SegWit v0 P2WSH testnet\",\n        input: \"1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262\",\n        expectedOutput: \"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"tb\", \"Bech32\", \"Hex\", \"Bitcoin SegWit\", 0]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32m: Bitcoin Taproot v1\",\n        input: \"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\n        expectedOutput: \"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32m\", \"Hex\", \"Bitcoin SegWit\", 1]\n            }\n        ],\n    },\n    {\n        name: \"To Bech32m: Bitcoin SegWit v16\",\n        input: \"751e\",\n        expectedOutput: \"bc1sw50qgdz25j\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32m\", \"Hex\", \"Bitcoin SegWit\", 16]\n            }\n        ],\n    },\n\n    // ============= Round-trip Tests =============\n    {\n        name: \"Bech32: encode then decode round-trip\",\n        input: \"The quick brown fox jumps over the lazy dog\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"test\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            },\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"Bech32m: encode then decode round-trip\",\n        input: \"The quick brown fox jumps over the lazy dog\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"test\", \"Bech32m\", \"Raw bytes\", \"Generic\", 0]\n            },\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32m\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"Bech32: binary data round-trip\",\n        input: \"0001020304050607\",\n        expectedOutput: \"0001020304050607\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"bc\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            },\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Bech32\", \"Hex\"]\n            }\n        ],\n    },\n    {\n        name: \"Bech32: auto-detect round-trip\",\n        input: \"CyberChef Bech32 Test\",\n        expectedOutput: \"CyberChef Bech32 Test\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"cyberchef\", \"Bech32\", \"Raw bytes\", \"Generic\", 0]\n            },\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Raw\"]\n            }\n        ],\n    },\n    {\n        name: \"Bech32m: auto-detect round-trip\",\n        input: \"CyberChef Bech32m Test\",\n        expectedOutput: \"CyberChef Bech32m Test\",\n        recipeConfig: [\n            {\n                \"op\": \"To Bech32\",\n                \"args\": [\"cyberchef\", \"Bech32m\", \"Raw bytes\", \"Generic\", 0]\n            },\n            {\n                \"op\": \"From Bech32\",\n                \"args\": [\"Auto-detect\", \"Raw\"]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/BitwiseOp.mjs",
    "content": "/**\n * BitwiseOp tests\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Bit shift left\",\n        input: \"01010101 10101010 11111111 00000000 11110000 00001111 00110011 11001100\",\n        expectedOutput: \"10101010 01010100 11111110 00000000 11100000 00011110 01100110 10011000\",\n        recipeConfig: [\n            { \"op\": \"From Binary\",\n                \"args\": [\"Space\"] },\n            { \"op\": \"Bit shift left\",\n                \"args\": [1] },\n            { \"op\": \"To Binary\",\n                \"args\": [\"Space\"] }\n        ]\n    },\n    {\n        name: \"Bit shift right: Logical shift\",\n        input: \"01010101 10101010 11111111 00000000 11110000 00001111 00110011 11001100\",\n        expectedOutput: \"00101010 01010101 01111111 00000000 01111000 00000111 00011001 01100110\",\n        recipeConfig: [\n            { \"op\": \"From Binary\",\n                \"args\": [\"Space\"] },\n            { \"op\": \"Bit shift right\",\n                \"args\": [1, \"Logical shift\"] },\n            { \"op\": \"To Binary\",\n                \"args\": [\"Space\"] }\n        ]\n    },\n    {\n        name: \"Bit shift right: Arithmetic shift\",\n        input: \"01010101 10101010 11111111 00000000 11110000 00001111 00110011 11001100\",\n        expectedOutput: \"00101010 11010101 11111111 00000000 11111000 00000111 00011001 11100110\",\n        recipeConfig: [\n            { \"op\": \"From Binary\",\n                \"args\": [\"Space\"] },\n            { \"op\": \"Bit shift right\",\n                \"args\": [1, \"Arithmetic shift\"] },\n            { \"op\": \"To Binary\",\n                \"args\": [\"Space\"] }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Bombe.mjs",
    "content": "/**\n * Bombe machine tests.\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        // Plugboard for this test is BO LC KE GA\n        name: \"Bombe: 3 rotor (self-stecker)\",\n        input: \"BBYFLTHHYIJQAYBBYS\",\n        expectedMatch: /<td>LGA<\\/td> {2}<td>SS<\\/td> {2}<td>VFISUSGTKSTMPSUNAK<\\/td>/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"THISISATESTMESSAGE\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        // This test produces a menu that doesn't use the first letter, which is also a good test\n        name: \"Bombe: 3 rotor (other stecker)\",\n        input: \"JBYALIHDYNUAAVKBYM\",\n        expectedMatch: /<td>LGA<\\/td> {2}<td>AG<\\/td> {2}<td>QFIMUMAFKMQSKMYNGW<\\/td>/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"THISISATESTMESSAGE\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: crib offset\",\n        input: \"AAABBYFLTHHYIJQAYBBYS\", // first three chars here are faked\n        expectedMatch: /<td>LGA<\\/td> {2}<td>SS<\\/td> {2}<td>VFISUSGTKSTMPSUNAK<\\/td>/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"THISISATESTMESSAGE\", 3, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: multiple stops\",\n        input: \"BBYFLTHHYIJQAYBBYS\",\n        expectedMatch: /<td>LGA<\\/td> {2}<td>TT<\\/td> {2}<td>VFISUSGTKSTMPSUNAK<\\/td>/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"THISISATESTM\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: checking machine\",\n        input: \"BBYFLTHHYIJQAYBBYS\",\n        expectedMatch: /<td>LGA<\\/td> {2}<td>TT AG BO CL EK FF HH II JJ SS YY<\\/td> {2}<td>THISISATESTMESSAGE<\\/td>/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"THISISATESTM\", 0, true\n                ]\n            }\n        ]\n    },\n    // Takes a while to run, so disabling for general purpose testing. Re-enable if modifying this operation.\n    // {\n    //     name: \"Bombe: 4 rotor\",\n    //     input: \"LUOXGJSHGEDSRDOQQX\",\n    //     expectedMatch: /<td>LHSC<\\/td> {2}<td>SS<\\/td> {2}<td>HHHSSSGQUUQPKSEKWK<\\/td>/,\n    //     recipeConfig: [\n    //         {\n    //             \"op\": \"Bombe\",\n    //             \"args\": [\n    //                 \"4-rotor\",\n    //                 \"LEYJVCNIXWPBQMDRTAKZGFUHOS\", // Beta\n    //                 \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n    //                 \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n    //                 \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n    //                 \"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV\", // B thin\n    //                 \"THISISATESTMESSAGE\", 0, false\n    //             ]\n    //         }\n    //     ]\n    // },\n    {\n        name: \"Bombe: no crib\",\n        input: \"JBYALIHDYNUAAVKBYM\",\n        expectedMatch: /Crib cannot be empty/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: short crib\",\n        input: \"JBYALIHDYNUAAVKBYM\",\n        expectedMatch: /Crib is too short/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"A\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: invalid crib\",\n        input: \"JBYALIHDYNUAAVKBYM\",\n        expectedMatch: /Invalid crib: .* in both ciphertext and crib/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"AAAAAAAA\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: long crib\",\n        input: \"JBYALIHDYNUAAVKBYM\",\n        expectedMatch: /Crib overruns supplied ciphertext/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"CCCCCCCCCCCCCCCCCCCCCC\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: really long crib\",\n        input: \"BBBBBBBBBBBBBBBBBBBBBBBBBB\",\n        expectedMatch: /Crib is too long/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"AAAAAAAAAAAAAAAAAAAAAAAAAA\", 0, false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Bombe: negative offset\",\n        input: \"AAAAA\",\n        expectedMatch: /Offset cannot be negative/,\n        recipeConfig: [\n            {\n                \"op\": \"Bombe\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"BBBBB\", -1, false\n                ]\n            }\n        ]\n    },\n    // Enigma tests cover validation of rotors and reflector\n]);\n"
  },
  {
    "path": "tests/operations/tests/ByteRepr.mjs",
    "content": "/**\n * ByteRepr tests.\n *\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nTestRegister.addTests([\n    {\n        name: \"To Octal: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"To Octal\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"From Octal: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"From Octal\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"To Octal: hello world\",\n        input: \"hello world\", // [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100],\n        expectedOutput: \"150 145 154 154 157 40 167 157 162 154 144\",\n        recipeConfig: [\n            {\n                \"op\": \"To Octal\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"From Octal: hello world\",\n        input: \"150 145 154 154 157 40 167 157 162 154 144\",\n        expectedOutput: \"hello world\",\n        recipeConfig: [\n            {\n                \"op\": \"From Octal\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"To Octal: Γειά σου\",\n        input: \"Γειά σου\", // [206,147,206,181,206,185,206,172,32,207,131,206,191,207,133],\n        expectedOutput: \"316 223 316 265 316 271 316 254 40 317 203 316 277 317 205\",\n        recipeConfig: [\n            {\n                \"op\": \"To Octal\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"From Octal: Γειά σου\",\n        input: \"316 223 316 265 316 271 316 254 40 317 203 316 277 317 205\",\n        expectedOutput: \"Γειά σου\",\n        recipeConfig: [\n            {\n                \"op\": \"From Octal\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"To Hex: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            },\n        ]\n    },\n    {\n        name: \"To Hex: All bytes\",\n        input: ALL_BYTES,\n        expectedOutput: \"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff\",\n        recipeConfig: [\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            },\n        ]\n    },\n    {\n        name: \"To Hex: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"e1839ce183a320e1839ee18390e1839ce18398e18399e18390e183a1\",\n        recipeConfig: [\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            },\n        ]\n    },\n    {\n        name: \"From Hex: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"From Hex: All bytes\",\n        input: \"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff\",\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"From Hex: UTF-8\",\n        input: \"e1839ce183a320e1839ee18390e1839ce18398e18399e18390e183a1\",\n        expectedOutput: \"ნუ პანიკას\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"To Charcode: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Charcode\",\n                args: [\"Space\", 16]\n            },\n        ]\n    },\n    {\n        name: \"To Charcode: All bytes\",\n        input: ALL_BYTES,\n        expectedOutput: \"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff\",\n        recipeConfig: [\n            {\n                op: \"To Charcode\",\n                args: [\"Space\", 16]\n            },\n        ]\n    },\n    {\n        name: \"To Charcode: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"10dc 10e3 20 10de 10d0 10dc 10d8 10d9 10d0 10e1\",\n        recipeConfig: [\n            {\n                op: \"To Charcode\",\n                args: [\"Space\", 16]\n            },\n        ]\n    },\n    {\n        name: \"From Charcode: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Charcode\",\n                args: [\"Space\", 16]\n            }\n        ]\n    },\n    {\n        name: \"From Charcode: All bytes\",\n        input: \"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff\",\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Charcode\",\n                args: [\"Space\", 16]\n            }\n        ]\n    },\n    {\n        name: \"From Charcode: UTF-8\",\n        input: \"10dc 10e3 20 10de 10d0 10dc 10d8 10d9 10d0 10e1\",\n        expectedOutput: \"ნუ პანიკას\",\n        recipeConfig: [\n            {\n                op: \"From Charcode\",\n                args: [\"Space\", 16]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/CBORDecode.mjs",
    "content": "/**\n * CBOR Decode Tests\n *\n * @author Danh4 [dan.h4@ncsc.gov.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"CBOR Decode: Can decode integer\",\n        input: \"0f\",\n        expectedOutput: \"15\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"CBOR Decode: Can decode decimal\",\n        input: \"f9 3e 00\",\n        expectedOutput: \"1.5\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"From Hex: Can decode text\",\n        input: \"64 54 65 78 74\",\n        expectedOutput: \"\\\"Text\\\"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"From Hex: Can decode boolean true\",\n        input: \"f5\",\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"From Hex: Can decode boolean false\",\n        input: \"f4\",\n        expectedOutput: \"false\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"From Hex: Can decode map\",\n        input: \"a3 61 61 01 61 62 02 61 63 03\",\n        expectedOutput: JSON.stringify({a: 1, b: 2, c: 3}),\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"From Hex: Can decode list\",\n        input: \"83 00 01 02\",\n        expectedOutput: \"[0,1,2]\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"From Hex: Can round trip with encode\",\n        input: JSON.stringify({a: 1, b: false, c: [1, 2, 3]}),\n        expectedOutput: JSON.stringify({a: 1, b: false, c: [1, 2, 3]}),\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"CBOR Decode\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/CBOREncode.mjs",
    "content": "/**\n * CBOR Encode Tests.\n *\n * @author Danh4 [dan.h4@ncsc.gov.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"CBOR Encode: Can encode integer\",\n        input: \"15\",\n        expectedOutput: \"0f\",\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"To Hex\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"CBOR Decode: Can encode decimal\",\n        input: \"1.5\",\n        expectedOutput: \"f9 3e 00\",\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"To Hex\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"CBOR Encode: Can encode text\",\n        input: \"\\\"Text\\\"\",\n        expectedOutput: \"64 54 65 78 74\",\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"To Hex\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"CBOR Encode: Can encode boolean true\",\n        input: \"true\",\n        expectedOutput: \"f5\",\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"To Hex\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"CBOR Encode: Can encode boolean false\",\n        input: \"false\",\n        expectedOutput: \"f4\",\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"To Hex\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"CBOR Encode: Can encode map\",\n        input: JSON.stringify({a: 1, b: 2, c: 3}),\n        expectedOutput: \"a3 61 61 01 61 62 02 61 63 03\",\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"To Hex\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"CBOR Encode: Can encode list\",\n        input: \"[0,1,2]\",\n        expectedOutput: \"83 00 01 02\",\n        recipeConfig: [\n            {\n                op: \"CBOR Encode\",\n                args: []\n            },\n            {\n                op: \"To Hex\",\n                args: []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/CMAC.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n// values in \"NIST's CSRC\" testcases are taken from here:\n// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values\n\nTestRegister.addTests([\n    {\n        \"name\": \"CMAC-AES128 NIST's CSRC Example #1\",\n        \"input\": \"\",\n        \"expectedOutput\": \"bb1d6929e95937287fa37d129b756746\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"2b7e151628aed2a6abf7158809cf4f3c\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES128 NIST's CSRC Example #2\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172a\",\n        \"expectedOutput\": \"070a16b46b4d4144f79bdd9dd04a287c\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"2b7e151628aed2a6abf7158809cf4f3c\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES128 NIST's CSRC Example #3\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a57\",\n        \"expectedOutput\": \"7d85449ea6ea19c823a7bf78837dfade\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"2b7e151628aed2a6abf7158809cf4f3c\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES128 NIST's CSRC Example #4\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710\",\n        \"expectedOutput\": \"51f0bebf7e3b9d92fc49741779363cfe\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"2b7e151628aed2a6abf7158809cf4f3c\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES192 NIST's CSRC Example #1\",\n        \"input\": \"\",\n        \"expectedOutput\": \"d17ddf46adaacde531cac483de7a9367\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES192 NIST's CSRC Example #2\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172a\",\n        \"expectedOutput\": \"9e99a7bf31e710900662f65e617c5184\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES192 NIST's CSRC Example #3\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a57\",\n        \"expectedOutput\": \"3d75c194ed96070444a9fa7ec740ecf8\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES192 NIST's CSRC Example #4\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710\",\n        \"expectedOutput\": \"a1d5df0eed790f794d77589659f39a11\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES256 NIST's CSRC Example #1\",\n        \"input\": \"\",\n        \"expectedOutput\": \"028962f61b7bf89efc6b551f4667d983\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES256 NIST's CSRC Example #2\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172a\",\n        \"expectedOutput\": \"28a7023f452e8f82bd4bf28d8c37c35c\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES256 NIST's CSRC Example #3\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a57\",\n        \"expectedOutput\": \"156727dc0878944a023c1fe03bad6d93\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES256 NIST's CSRC Example #4\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710\",\n        \"expectedOutput\": \"e1992190549f6ed5696a2c056c315410\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (1) NIST's CSRC Sample #1\",\n        \"input\": \"\",\n        \"expectedOutput\": \"7db0d37df936c550\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef01456789abcdef0123\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (1) NIST's CSRC Sample #2\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172a\",\n        \"expectedOutput\": \"30239cf1f52e6609\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef01456789abcdef0123\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (1) NIST's CSRC Sample #3\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a57\",\n        \"expectedOutput\": \"6c9f3ee4923f6be2\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef01456789abcdef0123\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (1) NIST's CSRC Sample #4\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51\",\n        \"expectedOutput\": \"99429bd0bf7904e5\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef01456789abcdef0123\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (2) NIST's CSRC Sample #1\",\n        \"input\": \"\",\n        \"expectedOutput\": \"79ce52a7f786a960\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef010123456789abcdef\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (2) NIST's CSRC Sample #2\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172a\",\n        \"expectedOutput\": \"cc18a0b79af2413b\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef010123456789abcdef\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (2) NIST's CSRC Sample #3\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a57\",\n        \"expectedOutput\": \"c06d377ecd101969\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef010123456789abcdef\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES (2) NIST's CSRC Sample #4\",\n        \"input\": \"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51\",\n        \"expectedOutput\": \"9cd33580f9b64dfb\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0123456789abcdef23456789abcdef010123456789abcdef\"}, \"Triple DES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-AES: invalid key length\",\n        \"input\": \"\",\n        \"expectedOutput\": \"The key for AES must be either 16, 24, or 32 bytes (currently 20 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff01234567\"}, \"AES\"]\n            },\n        ]\n    },\n    {\n        \"name\": \"CMAC-TDES: invalid key length\",\n        \"input\": \"\",\n        \"expectedOutput\": \"The key for Triple DES must be 16 or 24 bytes (currently 20 bytes)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"CMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff01234567\"}, \"Triple DES\"]\n            },\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/CRCChecksum.mjs",
    "content": "/**\n * @author r4mos [2k95ljkhg@mozmail.com]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib//TestRegister.mjs\";\n\nconst BASIC_STRING = \"The ships hung in the sky in much the same way that bricks don't.\";\nconst UTF8_STR = \"ნუ პანიკას\";\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nTestRegister.addTests([\n    {\n        name: \"CRC-16: nothing\",\n        input: \"\",\n        expectedOutput: \"0000\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16: basic string\",\n        input: BASIC_STRING,\n        expectedOutput: \"0c70\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16: UTF-8\",\n        input: UTF8_STR,\n        expectedOutput: \"dcf6\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16: all bytes\",\n        input: ALL_BYTES,\n        expectedOutput: \"bad3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32: nothing\",\n        input: \"\",\n        expectedOutput: \"00000000\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32: basic string\",\n        input: BASIC_STRING,\n        expectedOutput: \"bf4b739c\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32: UTF-8\",\n        input: UTF8_STR,\n        expectedOutput: \"87553290\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32: all bytes\",\n        input: ALL_BYTES,\n        expectedOutput: \"29058c73\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-3/GSM check\",\n        input: \"123456789\",\n        expectedOutput: \"4\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-3/GSM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-3/ROHC check\",\n        input: \"123456789\",\n        expectedOutput: \"6\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-3/ROHC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-4/G-704 check\",\n        input: \"123456789\",\n        expectedOutput: \"7\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-4/G-704\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-4/INTERLAKEN check\",\n        input: \"123456789\",\n        expectedOutput: \"b\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-4/INTERLAKEN\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-4/ITU check\",\n        input: \"123456789\",\n        expectedOutput: \"7\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-4/ITU\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-5/EPC check\",\n        input: \"123456789\",\n        expectedOutput: \"00\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-5/EPC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-5/EPC-C1G2 check\",\n        input: \"123456789\",\n        expectedOutput: \"00\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-5/EPC-C1G2\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-5/G-704 check\",\n        input: \"123456789\",\n        expectedOutput: \"07\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-5/G-704\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-5/ITU check\",\n        input: \"123456789\",\n        expectedOutput: \"07\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-5/ITU\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-5/USB check\",\n        input: \"123456789\",\n        expectedOutput: \"19\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-5/USB\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-6/CDMA2000-A check\",\n        input: \"123456789\",\n        expectedOutput: \"0d\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-6/CDMA2000-A\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-6/CDMA2000-B check\",\n        input: \"123456789\",\n        expectedOutput: \"3b\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-6/CDMA2000-B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-6/DARC check\",\n        input: \"123456789\",\n        expectedOutput: \"26\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-6/DARC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-6/G-704 check\",\n        input: \"123456789\",\n        expectedOutput: \"06\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-6/G-704\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-6/GSM check\",\n        input: \"123456789\",\n        expectedOutput: \"13\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-6/GSM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-6/ITU check\",\n        input: \"123456789\",\n        expectedOutput: \"06\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-6/ITU\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-7/MMC check\",\n        input: \"123456789\",\n        expectedOutput: \"75\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-7/MMC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-7/ROHC check\",\n        input: \"123456789\",\n        expectedOutput: \"53\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-7/ROHC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-7/UMTS check\",\n        input: \"123456789\",\n        expectedOutput: \"61\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-7/UMTS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8 check\",\n        input: \"123456789\",\n        expectedOutput: \"f4\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/8H2F check\",\n        input: \"123456789\",\n        expectedOutput: \"df\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/8H2F\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/AES check\",\n        input: \"123456789\",\n        expectedOutput: \"97\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/AES\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/AUTOSAR check\",\n        input: \"123456789\",\n        expectedOutput: \"df\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/AUTOSAR\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/BLUETOOTH check\",\n        input: \"123456789\",\n        expectedOutput: \"26\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/BLUETOOTH\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/CDMA2000 check\",\n        input: \"123456789\",\n        expectedOutput: \"da\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/CDMA2000\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/DARC check\",\n        input: \"123456789\",\n        expectedOutput: \"15\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/DARC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/DVB-S2 check\",\n        input: \"123456789\",\n        expectedOutput: \"bc\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/DVB-S2\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/EBU check\",\n        input: \"123456789\",\n        expectedOutput: \"97\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/EBU\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/GSM-A check\",\n        input: \"123456789\",\n        expectedOutput: \"37\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/GSM-A\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/GSM-B check\",\n        input: \"123456789\",\n        expectedOutput: \"94\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/GSM-B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/HITAG check\",\n        input: \"123456789\",\n        expectedOutput: \"b4\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/HITAG\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/I-432-1 check\",\n        input: \"123456789\",\n        expectedOutput: \"a1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/I-432-1\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/I-CODE check\",\n        input: \"123456789\",\n        expectedOutput: \"7e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/I-CODE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/ITU check\",\n        input: \"123456789\",\n        expectedOutput: \"a1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/ITU\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/LTE check\",\n        input: \"123456789\",\n        expectedOutput: \"ea\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/LTE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/MAXIM check\",\n        input: \"123456789\",\n        expectedOutput: \"a1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/MAXIM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/MAXIM-DOW check\",\n        input: \"123456789\",\n        expectedOutput: \"a1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/MAXIM-DOW\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/MIFARE-MAD check\",\n        input: \"123456789\",\n        expectedOutput: \"99\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/MIFARE-MAD\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/NRSC-5 check\",\n        input: \"123456789\",\n        expectedOutput: \"f7\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/NRSC-5\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/OPENSAFETY check\",\n        input: \"123456789\",\n        expectedOutput: \"3e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/OPENSAFETY\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/ROHC check\",\n        input: \"123456789\",\n        expectedOutput: \"d0\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/ROHC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/SAE-J1850 check\",\n        input: \"123456789\",\n        expectedOutput: \"4b\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/SAE-J1850\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/SAE-J1850-ZERO check\",\n        input: \"123456789\",\n        expectedOutput: \"37\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/SAE-J1850-ZERO\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/SMBUS check\",\n        input: \"123456789\",\n        expectedOutput: \"f4\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/SMBUS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/TECH-3250 check\",\n        input: \"123456789\",\n        expectedOutput: \"97\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/TECH-3250\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-8/WCDMA check\",\n        input: \"123456789\",\n        expectedOutput: \"25\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-8/WCDMA\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-10/ATM check\",\n        input: \"123456789\",\n        expectedOutput: \"199\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-10/ATM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-10/CDMA2000 check\",\n        input: \"123456789\",\n        expectedOutput: \"233\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-10/CDMA2000\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-10/GSM check\",\n        input: \"123456789\",\n        expectedOutput: \"12a\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-10/GSM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-10/I-610 check\",\n        input: \"123456789\",\n        expectedOutput: \"199\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-10/I-610\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-11/FLEXRAY check\",\n        input: \"123456789\",\n        expectedOutput: \"5a3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-11/FLEXRAY\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-11/UMTS check\",\n        input: \"123456789\",\n        expectedOutput: \"061\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-11/UMTS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-12/3GPP check\",\n        input: \"123456789\",\n        expectedOutput: \"daf\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-12/3GPP\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-12/CDMA2000 check\",\n        input: \"123456789\",\n        expectedOutput: \"d4d\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-12/CDMA2000\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-12/DECT check\",\n        input: \"123456789\",\n        expectedOutput: \"f5b\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-12/DECT\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-12/GSM check\",\n        input: \"123456789\",\n        expectedOutput: \"b34\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-12/GSM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-12/UMTS check\",\n        input: \"123456789\",\n        expectedOutput: \"daf\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-12/UMTS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-13/BBC check\",\n        input: \"123456789\",\n        expectedOutput: \"04fa\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-13/BBC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-14/DARC check\",\n        input: \"123456789\",\n        expectedOutput: \"082d\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-14/DARC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-14/GSM check\",\n        input: \"123456789\",\n        expectedOutput: \"30ae\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-14/GSM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-15/CAN check\",\n        input: \"123456789\",\n        expectedOutput: \"059e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-15/CAN\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-15/MPT1327 check\",\n        input: \"123456789\",\n        expectedOutput: \"2566\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-15/MPT1327\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16 check\",\n        input: \"123456789\",\n        expectedOutput: \"bb3d\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/A check\",\n        input: \"123456789\",\n        expectedOutput: \"bf05\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/A\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/ACORN check\",\n        input: \"123456789\",\n        expectedOutput: \"31c3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/ACORN\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/ARC check\",\n        input: \"123456789\",\n        expectedOutput: \"bb3d\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/ARC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/AUG-CCITT check\",\n        input: \"123456789\",\n        expectedOutput: \"e5cc\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/AUG-CCITT\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/AUTOSAR check\",\n        input: \"123456789\",\n        expectedOutput: \"29b1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/AUTOSAR\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/B check\",\n        input: \"123456789\",\n        expectedOutput: \"906e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/BLUETOOTH check\",\n        input: \"123456789\",\n        expectedOutput: \"2189\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/BLUETOOTH\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/BUYPASS check\",\n        input: \"123456789\",\n        expectedOutput: \"fee8\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/BUYPASS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/CCITT check\",\n        input: \"123456789\",\n        expectedOutput: \"2189\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/CCITT\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/CCITT-FALSE check\",\n        input: \"123456789\",\n        expectedOutput: \"29b1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/CCITT-FALSE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/CCITT-TRUE check\",\n        input: \"123456789\",\n        expectedOutput: \"2189\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/CCITT-TRUE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/CCITT-ZERO check\",\n        input: \"123456789\",\n        expectedOutput: \"31c3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/CCITT-ZERO\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/CDMA2000 check\",\n        input: \"123456789\",\n        expectedOutput: \"4c06\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/CDMA2000\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/CMS check\",\n        input: \"123456789\",\n        expectedOutput: \"aee7\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/CMS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/DARC check\",\n        input: \"123456789\",\n        expectedOutput: \"d64e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/DARC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/DDS-110 check\",\n        input: \"123456789\",\n        expectedOutput: \"9ecf\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/DDS-110\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/DECT-R check\",\n        input: \"123456789\",\n        expectedOutput: \"007e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/DECT-R\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/DECT-X check\",\n        input: \"123456789\",\n        expectedOutput: \"007f\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/DECT-X\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/DNP check\",\n        input: \"123456789\",\n        expectedOutput: \"ea82\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/DNP\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/EN-13757 check\",\n        input: \"123456789\",\n        expectedOutput: \"c2b7\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/EN-13757\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/EPC check\",\n        input: \"123456789\",\n        expectedOutput: \"d64e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/EPC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/EPC-C1G2 check\",\n        input: \"123456789\",\n        expectedOutput: \"d64e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/EPC-C1G2\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/GENIBUS check\",\n        input: \"123456789\",\n        expectedOutput: \"d64e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/GENIBUS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/GSM check\",\n        input: \"123456789\",\n        expectedOutput: \"ce3c\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/GSM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/I-CODE check\",\n        input: \"123456789\",\n        expectedOutput: \"d64e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/I-CODE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/IBM check\",\n        input: \"123456789\",\n        expectedOutput: \"bb3d\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/IBM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/IBM-3740 check\",\n        input: \"123456789\",\n        expectedOutput: \"29b1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/IBM-3740\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/IBM-SDLC check\",\n        input: \"123456789\",\n        expectedOutput: \"906e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/IBM-SDLC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/IEC-61158-2 check\",\n        input: \"123456789\",\n        expectedOutput: \"a819\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/IEC-61158-2\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/ISO-HDLC check\",\n        input: \"123456789\",\n        expectedOutput: \"906e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/ISO-HDLC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/ISO-IEC-14443-3-A check\",\n        input: \"123456789\",\n        expectedOutput: \"bf05\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/ISO-IEC-14443-3-A\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/ISO-IEC-14443-3-B check\",\n        input: \"123456789\",\n        expectedOutput: \"906e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/ISO-IEC-14443-3-B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/KERMIT check\",\n        input: \"123456789\",\n        expectedOutput: \"2189\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/KERMIT\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/LHA check\",\n        input: \"123456789\",\n        expectedOutput: \"bb3d\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/LHA\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/LJ1200 check\",\n        input: \"123456789\",\n        expectedOutput: \"bdf4\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/LJ1200\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/LTE check\",\n        input: \"123456789\",\n        expectedOutput: \"31c3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/LTE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/M17 check\",\n        input: \"123456789\",\n        expectedOutput: \"772b\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/M17\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/MAXIM check\",\n        input: \"123456789\",\n        expectedOutput: \"44c2\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/MAXIM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/MAXIM-DOW check\",\n        input: \"123456789\",\n        expectedOutput: \"44c2\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/MAXIM-DOW\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/MCRF4XX check\",\n        input: \"123456789\",\n        expectedOutput: \"6f91\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/MCRF4XX\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/MODBUS check\",\n        input: \"123456789\",\n        expectedOutput: \"4b37\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/MODBUS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/NRSC-5 check\",\n        input: \"123456789\",\n        expectedOutput: \"a066\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/NRSC-5\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/OPENSAFETY-A check\",\n        input: \"123456789\",\n        expectedOutput: \"5d38\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/OPENSAFETY-A\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/OPENSAFETY-B check\",\n        input: \"123456789\",\n        expectedOutput: \"20fe\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/OPENSAFETY-B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/PROFIBUS check\",\n        input: \"123456789\",\n        expectedOutput: \"a819\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/PROFIBUS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/RIELLO check\",\n        input: \"123456789\",\n        expectedOutput: \"63d0\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/RIELLO\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/SPI-FUJITSU check\",\n        input: \"123456789\",\n        expectedOutput: \"e5cc\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/SPI-FUJITSU\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/T10-DIF check\",\n        input: \"123456789\",\n        expectedOutput: \"d0db\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/T10-DIF\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/TELEDISK check\",\n        input: \"123456789\",\n        expectedOutput: \"0fb3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/TELEDISK\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/TMS37157 check\",\n        input: \"123456789\",\n        expectedOutput: \"26b1\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/TMS37157\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/UMTS check\",\n        input: \"123456789\",\n        expectedOutput: \"fee8\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/UMTS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/USB check\",\n        input: \"123456789\",\n        expectedOutput: \"b4c8\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/USB\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/V-41-LSB check\",\n        input: \"123456789\",\n        expectedOutput: \"2189\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/V-41-LSB\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/V-41-MSB check\",\n        input: \"123456789\",\n        expectedOutput: \"31c3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/V-41-MSB\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/VERIFONE check\",\n        input: \"123456789\",\n        expectedOutput: \"fee8\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/VERIFONE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/X-25 check\",\n        input: \"123456789\",\n        expectedOutput: \"906e\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/X-25\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/XMODEM check\",\n        input: \"123456789\",\n        expectedOutput: \"31c3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/XMODEM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-16/ZMODEM check\",\n        input: \"123456789\",\n        expectedOutput: \"31c3\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-16/ZMODEM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-17/CAN-FD check\",\n        input: \"123456789\",\n        expectedOutput: \"04f03\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-17/CAN-FD\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-21/CAN-FD check\",\n        input: \"123456789\",\n        expectedOutput: \"0ed841\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-21/CAN-FD\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/BLE check\",\n        input: \"123456789\",\n        expectedOutput: \"c25a56\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/BLE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/FLEXRAY-A check\",\n        input: \"123456789\",\n        expectedOutput: \"7979bd\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/FLEXRAY-A\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/FLEXRAY-B check\",\n        input: \"123456789\",\n        expectedOutput: \"1f23b8\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/FLEXRAY-B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/INTERLAKEN check\",\n        input: \"123456789\",\n        expectedOutput: \"b4f3e6\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/INTERLAKEN\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/LTE-A check\",\n        input: \"123456789\",\n        expectedOutput: \"cde703\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/LTE-A\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/LTE-B check\",\n        input: \"123456789\",\n        expectedOutput: \"23ef52\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/LTE-B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/OPENPGP check\",\n        input: \"123456789\",\n        expectedOutput: \"21cf02\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/OPENPGP\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-24/OS-9 check\",\n        input: \"123456789\",\n        expectedOutput: \"200fa5\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-24/OS-9\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-30/CDMA check\",\n        input: \"123456789\",\n        expectedOutput: \"04c34abf\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-30/CDMA\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-31/PHILIPS check\",\n        input: \"123456789\",\n        expectedOutput: \"0ce9e46c\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-31/PHILIPS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32 check\",\n        input: \"123456789\",\n        expectedOutput: \"cbf43926\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/AAL5 check\",\n        input: \"123456789\",\n        expectedOutput: \"fc891918\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/AAL5\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/ADCCP check\",\n        input: \"123456789\",\n        expectedOutput: \"cbf43926\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/ADCCP\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/AIXM check\",\n        input: \"123456789\",\n        expectedOutput: \"3010bf7f\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/AIXM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/AUTOSAR check\",\n        input: \"123456789\",\n        expectedOutput: \"1697d06a\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/AUTOSAR\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/BASE91-C check\",\n        input: \"123456789\",\n        expectedOutput: \"e3069283\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/BASE91-C\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/BASE91-D check\",\n        input: \"123456789\",\n        expectedOutput: \"87315576\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/BASE91-D\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/BZIP2 check\",\n        input: \"123456789\",\n        expectedOutput: \"fc891918\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/BZIP2\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/C check\",\n        input: \"123456789\",\n        expectedOutput: \"e3069283\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/C\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/CASTAGNOLI check\",\n        input: \"123456789\",\n        expectedOutput: \"e3069283\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/CASTAGNOLI\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/CD-ROM-EDC check\",\n        input: \"123456789\",\n        expectedOutput: \"6ec2edc4\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/CD-ROM-EDC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/CKSUM check\",\n        input: \"123456789\",\n        expectedOutput: \"765e7680\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/CKSUM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/D check\",\n        input: \"123456789\",\n        expectedOutput: \"87315576\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/D\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/DECT-B check\",\n        input: \"123456789\",\n        expectedOutput: \"fc891918\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/DECT-B\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/INTERLAKEN check\",\n        input: \"123456789\",\n        expectedOutput: \"e3069283\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/INTERLAKEN\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/ISCSI check\",\n        input: \"123456789\",\n        expectedOutput: \"e3069283\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/ISCSI\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/ISO-HDLC check\",\n        input: \"123456789\",\n        expectedOutput: \"cbf43926\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/ISO-HDLC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/JAMCRC check\",\n        input: \"123456789\",\n        expectedOutput: \"340bc6d9\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/JAMCRC\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/MEF check\",\n        input: \"123456789\",\n        expectedOutput: \"d2c22f51\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/MEF\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/MPEG-2 check\",\n        input: \"123456789\",\n        expectedOutput: \"0376e6e7\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/MPEG-2\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/NVME check\",\n        input: \"123456789\",\n        expectedOutput: \"e3069283\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/NVME\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/PKZIP check\",\n        input: \"123456789\",\n        expectedOutput: \"cbf43926\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/PKZIP\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/POSIX check\",\n        input: \"123456789\",\n        expectedOutput: \"765e7680\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/POSIX\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/Q check\",\n        input: \"123456789\",\n        expectedOutput: \"3010bf7f\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/Q\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/SATA check\",\n        input: \"123456789\",\n        expectedOutput: \"cf72afe8\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/SATA\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/V-42 check\",\n        input: \"123456789\",\n        expectedOutput: \"cbf43926\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/V-42\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/XFER check\",\n        input: \"123456789\",\n        expectedOutput: \"bd0be338\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/XFER\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-32/XZ check\",\n        input: \"123456789\",\n        expectedOutput: \"cbf43926\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-32/XZ\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-40/GSM check\",\n        input: \"123456789\",\n        expectedOutput: \"d4164fc646\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-40/GSM\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/ECMA-182 check\",\n        input: \"123456789\",\n        expectedOutput: \"6c40df5f0b497347\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/ECMA-182\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/GO-ECMA check\",\n        input: \"123456789\",\n        expectedOutput: \"995dc9bbdf1939fa\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/GO-ECMA\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/GO-ISO check\",\n        input: \"123456789\",\n        expectedOutput: \"b90956c775a41001\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/GO-ISO\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/MS check\",\n        input: \"123456789\",\n        expectedOutput: \"75d4b74f024eceea\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/MS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/NVME check\",\n        input: \"123456789\",\n        expectedOutput: \"ae8b14860a799888\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/NVME\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/REDIS check\",\n        input: \"123456789\",\n        expectedOutput: \"e9c6d914c4b8d9ca\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/REDIS\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/WE check\",\n        input: \"123456789\",\n        expectedOutput: \"62ec59e3f1a4f00a\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/WE\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-64/XZ check\",\n        input: \"123456789\",\n        expectedOutput: \"995dc9bbdf1939fa\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-64/XZ\"]\n            }\n        ]\n    },\n    {\n        name: \"CRC-82/DARC check\",\n        input: \"123456789\",\n        expectedOutput: \"09ea83f625023801fd612\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"CRC-82/DARC\"]\n            }\n        ]\n    },\n    {\n        name: \"Custom. CRC-32\",\n        input: \"123456789\",\n        expectedOutput: \"cbf43926\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"Custom\", {\"option\": \"Decimal\", string: \"32\"}, {\"option\": \"Hex\", string: \"04C11DB7\"}, {\"option\": \"Hex\", string: \"FFFFFFFF\"}, \"True\", \"True\", {\"option\": \"Hex\", string: \"FFFFFFFF\"}]\n            }\n        ]\n    },\n    {\n        name: \"Custom. Invalid Width\",\n        input: \"123456789\",\n        expectedOutput: \"Invalid custom CRC arguments\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"Custom\", {\"option\": \"Decimal\", string: \"ABC\"}, {\"option\": \"Hex\", string: \"04C11DB7\"}, {\"option\": \"Hex\", string: \"FFFFFFFF\"}, \"True\", \"True\", {\"option\": \"Hex\", string: \"FFFFFFFF\"}]\n            }\n        ]\n    },\n    {\n        name: \"Custom. Invalid Poly\",\n        input: \"123456789\",\n        expectedOutput: \"Invalid custom CRC arguments\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"Custom\", {\"option\": \"Decimal\", string: \"32\"}, {\"option\": \"Hex\", string: \"\"}, {\"option\": \"Hex\", string: \"FFFFFFFF\"}, \"True\", \"True\", {\"option\": \"Hex\", string: \"FFFFFFFF\"}]\n            }\n        ]\n    },\n    {\n        name: \"Custom. Invalid Init\",\n        input: \"123456789\",\n        expectedOutput: \"Invalid custom CRC arguments\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"Custom\", {\"option\": \"Decimal\", string: \"32\"}, {\"option\": \"Hex\", string: \"04C11DB7\"}, {\"option\": \"Hex\", string: \"\"}, \"True\", \"True\", {\"option\": \"Hex\", string: \"FFFFFFFF\"}]\n            }\n        ]\n    },\n    {\n        name: \"Custom. Invalid Xor Out\",\n        input: \"123456789\",\n        expectedOutput: \"Invalid custom CRC arguments\",\n        recipeConfig: [\n            {\n                \"op\": \"CRC Checksum\",\n                \"args\": [\"Custom\", {\"option\": \"Decimal\", string: \"32\"}, {\"option\": \"Hex\", string: \"04C11DB7\"}, {\"option\": \"Hex\", string: \"FFFFFFFF\"}, \"True\", \"True\", {\"option\": \"Hex\", string: \"\"}]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/CSV.mjs",
    "content": "/**\n * CSV tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst EXAMPLE_CSV = `A,B,C,D,E,F\\r\n1,2,3,4,5,6\\r\n\",\",;,',\"\"\"\",,\\r\n\"\"\"hello\"\"\",\"a\"\"1\",\"multi\\r\nline\",,,end\\r\n`;\n\nTestRegister.addTests([\n    {\n        name: \"CSV to JSON: Array of dictionaries\",\n        input: EXAMPLE_CSV,\n        expectedOutput: JSON.stringify([\n            {\n                \"A\": \"1\",\n                \"B\": \"2\",\n                \"C\": \"3\",\n                \"D\": \"4\",\n                \"E\": \"5\",\n                \"F\": \"6\"\n            },\n            {\n                \"A\": \",\",\n                \"B\": \";\",\n                \"C\": \"'\",\n                \"D\": \"\\\"\",\n                \"E\": \"\",\n                \"F\": \"\"\n            },\n            {\n                \"A\": \"\\\"hello\\\"\",\n                \"B\": \"a\\\"1\",\n                \"C\": \"multi\\r\\nline\",\n                \"D\": \"\",\n                \"E\": \"\",\n                \"F\": \"end\"\n            }\n        ], null, 4),\n        recipeConfig: [\n            {\n                op: \"CSV to JSON\",\n                args: [\",\", \"\\r\\n\", \"Array of dictionaries\"],\n            }\n        ],\n    },\n    {\n        name: \"CSV to JSON: Array of arrays\",\n        input: EXAMPLE_CSV,\n        expectedOutput: JSON.stringify([\n            [\n                \"A\",\n                \"B\",\n                \"C\",\n                \"D\",\n                \"E\",\n                \"F\"\n            ],\n            [\n                \"1\",\n                \"2\",\n                \"3\",\n                \"4\",\n                \"5\",\n                \"6\"\n            ],\n            [\n                \",\",\n                \";\",\n                \"'\",\n                \"\\\"\",\n                \"\",\n                \"\"\n            ],\n            [\n                \"\\\"hello\\\"\",\n                \"a\\\"1\",\n                \"multi\\r\\nline\",\n                \"\",\n                \"\",\n                \"end\"\n            ]\n        ], null, 4),\n        recipeConfig: [\n            {\n                op: \"CSV to JSON\",\n                args: [\",\", \"\\r\\n\", \"Array of arrays\"],\n            }\n        ],\n    },\n    {\n        name: \"JSON to CSV: Array of dictionaries\",\n        input: JSON.stringify([\n            {\n                \"A\": \"1\",\n                \"B\": \"2\",\n                \"C\": \"3\",\n                \"D\": \"4\",\n                \"E\": \"5\",\n                \"F\": \"6\"\n            },\n            {\n                \"A\": \",\",\n                \"B\": \";\",\n                \"C\": \"'\",\n                \"D\": \"\\\"\",\n                \"E\": \"\",\n                \"F\": \"\"\n            },\n            {\n                \"A\": \"\\\"hello\\\"\",\n                \"B\": \"a\\\"1\",\n                \"C\": \"multi\\r\\nline\",\n                \"D\": \"\",\n                \"E\": \"\",\n                \"F\": \"end\"\n            }\n        ]),\n        expectedOutput: EXAMPLE_CSV,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\r\\n\"],\n            }\n        ],\n    },\n    {\n        name: \"JSON to CSV: Array of arrays\",\n        input: JSON.stringify([\n            [\n                \"A\",\n                \"B\",\n                \"C\",\n                \"D\",\n                \"E\",\n                \"F\"\n            ],\n            [\n                \"1\",\n                \"2\",\n                \"3\",\n                \"4\",\n                \"5\",\n                \"6\"\n            ],\n            [\n                \",\",\n                \";\",\n                \"'\",\n                \"\\\"\",\n                \"\",\n                \"\"\n            ],\n            [\n                \"\\\"hello\\\"\",\n                \"a\\\"1\",\n                \"multi\\r\\nline\",\n                \"\",\n                \"\",\n                \"end\"\n            ]\n        ]),\n        expectedOutput: EXAMPLE_CSV,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\r\\n\"],\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/CaesarBoxCipher.mjs",
    "content": "/**\n * Caesar Box Cipher tests.\n *\n * @author n1073645 [n1073645@gmail.com]\n *\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Caesar Box Cipher: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Caesar Box Cipher\",\n                args: [\"1\"],\n            },\n        ],\n    },\n    {\n        name: \"Caesar Box Cipher: Hello World!\",\n        input: \"Hello World!\",\n        expectedOutput: \"Hlodeor!lWl\",\n        recipeConfig: [\n            {\n                op: \"Caesar Box Cipher\",\n                args: [\"3\"],\n            },\n        ],\n    },\n    {\n        name: \"Caesar Box Cipher: Hello World!\",\n        input: \"Hlodeor!lWl\",\n        expectedOutput: \"HelloWorld!\",\n        recipeConfig: [\n            {\n                op: \"Caesar Box Cipher\",\n                args: [\"4\"],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/CaretMdecode.mjs",
    "content": "/**\n * Caesar Box Cipher tests.\n *\n * @author tedk [tedk@ted.do]\n *\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Caret/M-decode: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Caret/M-decode\",\n                args: [],\n            },\n        ],\n    },\n    {\n        /*\n         * Tests the full range.\n         * Everything except \"^_\" (\\x5e\\x5f) will decode correctly.\n         */\n        name: \"Caret/M-decode: Full set\",\n        input: \"^@^A^B^C^D^E^F^G^H^I^J^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\\\\^]^^^_ !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?M-^@M-^AM-^BM-^CM-^DM-^EM-^FM-^GM-^HM-^IM-^JM-^KM-^LM-^MM-^NM-^OM-^PM-^QM-^RM-^SM-^TM-^UM-^VM-^WM-^XM-^YM-^ZM-^[M-^\\\\M-^]M-^^M-^_M- M-!M-\\\"M-#M-$M-%M-&M-'M-(M-)M-*M-+M-,M--M-.M-/M-0M-1M-2M-3M-4M-5M-6M-7M-8M-9M-:M-;M-<M-=M->M-?M-@M-AM-BM-CM-DM-EM-FM-GM-HM-IM-JM-KM-LM-MM-NM-OM-PM-QM-RM-SM-TM-UM-VM-WM-XM-YM-ZM-[M-\\\\M-]M-^M-_M-`M-aM-bM-cM-dM-eM-fM-gM-hM-iM-jM-kM-lM-mM-nM-oM-pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?\",\n        expectedOutput: \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x1f\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\x8d\\x2d\\x5f\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n        recipeConfig: [\n            {\n                op: \"Caret/M-decode\",\n                args: [],\n            },\n        ],\n    },\n]);\n\n"
  },
  {
    "path": "tests/operations/tests/CartesianProduct.mjs",
    "content": "/**\n * Cartesian Product tests.\n *\n * @author d98762625\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Cartesian Product\",\n        input: \"1 2 3 4 5\\n\\na b c d e\",\n        expectedOutput: \"(1,a) (1,b) (1,c) (1,d) (1,e) (2,a) (2,b) (2,c) (2,d) (2,e) (3,a) (3,b) (3,c) (3,d) (3,e) (4,a) (4,b) (4,c) (4,d) (4,e) (5,a) (5,b) (5,c) (5,d) (5,e)\",\n        recipeConfig: [\n            {\n                op: \"Cartesian Product\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Cartesian Product: too many on left\",\n        input: \"1 2 3 4 5 6\\n\\na b c d e\",\n        expectedOutput: \"(1,a) (1,b) (1,c) (1,d) (1,e) (2,a) (2,b) (2,c) (2,d) (2,e) (3,a) (3,b) (3,c) (3,d) (3,e) (4,a) (4,b) (4,c) (4,d) (4,e) (5,a) (5,b) (5,c) (5,d) (5,e) (6,a) (6,b) (6,c) (6,d) (6,e)\",\n        recipeConfig: [\n            {\n                op: \"Cartesian Product\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Cartesian Product: too many on right\",\n        input: \"1 2 3 4 5\\n\\na b c d e f\",\n        expectedOutput: \"(1,a) (1,b) (1,c) (1,d) (1,e) (1,f) (2,a) (2,b) (2,c) (2,d) (2,e) (2,f) (3,a) (3,b) (3,c) (3,d) (3,e) (3,f) (4,a) (4,b) (4,c) (4,d) (4,e) (4,f) (5,a) (5,b) (5,c) (5,d) (5,e) (5,f)\",\n        recipeConfig: [\n            {\n                op: \"Cartesian Product\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Cartesian Product: item delimiter\",\n        input: \"1-2-3-4-5\\n\\na-b-c-d-e\",\n        expectedOutput: \"(1,a)-(1,b)-(1,c)-(1,d)-(1,e)-(2,a)-(2,b)-(2,c)-(2,d)-(2,e)-(3,a)-(3,b)-(3,c)-(3,d)-(3,e)-(4,a)-(4,b)-(4,c)-(4,d)-(4,e)-(5,a)-(5,b)-(5,c)-(5,d)-(5,e)\",\n        recipeConfig: [\n            {\n                op: \"Cartesian Product\",\n                args: [\"\\n\\n\", \"-\"],\n            },\n        ],\n    },\n    {\n        name: \"Cartesian Product: sample delimiter\",\n        input: \"1 2 3 4 5_a b c d e\",\n        expectedOutput: \"(1,a) (1,b) (1,c) (1,d) (1,e) (2,a) (2,b) (2,c) (2,d) (2,e) (3,a) (3,b) (3,c) (3,d) (3,e) (4,a) (4,b) (4,c) (4,d) (4,e) (5,a) (5,b) (5,c) (5,d) (5,e)\",\n        recipeConfig: [\n            {\n                op: \"Cartesian Product\",\n                args: [\"_\", \" \"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/CetaceanCipherDecode.mjs",
    "content": "/**\n * CetaceanCipher Encode tests\n *\n * @author dolphinOnKeys\n * @copyright Crown Copyright 2022\n * @licence Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Cetacean Cipher Decode\",\n        input: \"EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee\",\n        expectedOutput: \"a b c で\",\n        recipeConfig: [\n            {\n                op: \"Cetacean Cipher Decode\",\n                args: []\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/CetaceanCipherEncode.mjs",
    "content": "/**\n * CetaceanCipher Encode tests\n *\n * @author dolphinOnKeys\n * @copyright Crown Copyright 2022\n * @licence Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Cetacean Cipher Encode\",\n        input: \"a b c で\",\n        expectedOutput: \"EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee\",\n        recipeConfig: [\n            {\n                op: \"Cetacean Cipher Encode\",\n                args: []\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ChaCha.mjs",
    "content": "/**\n * ChaCha tests.\n *\n * @author joostrijneveld [joost@joostrijneveld.nl]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"ChaCha: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes.\n\nChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: no nonce\",\n        input: \"\",\n        expectedOutput: `Invalid nonce length: 0 bytes.\n\nChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: RFC8439\",\n        input: \"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.\",\n        expectedOutput: \"6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 87 4d\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f\"},\n                    {\"option\": \"Hex\", \"string\": \"00:00:00:00:00:00:00:4a:00:00:00:00\"},\n                    1, \"20\", \"Raw\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: RFC8439 Raw output\",\n        input: \"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.\",\n        expectedOutput: \"6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 87 4d\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f\"},\n                    {\"option\": \"Hex\", \"string\": \"00:00:00:00:00:00:00:4a:00:00:00:00\"},\n                    1, \"20\", \"Raw\", \"Raw\",\n                ]\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": []\n            },\n        ],\n    },\n    {\n        name: \"ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.1\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"29 56 0d 28 0b 45 28 40 0a 8f 4b 79 53 69 fb 3a 01 10 55 99 e9 f1 ed 58 27 9c fc 9e ce 2d c5 f9 9f 1c 2e 52 c9 82 38 f5 42 a5 c0 a8 81 d8 50 b6 15 d3 ac d9 fb db 02 6e 93 68 56 5d a5 0e 0d 49 dd 5b e8 ef 74 24 8b 3e 25 1d 96 5d 8f cb 21 e7 cf e2 04 d4 00 78 06 fb ee 3c e9 4c 74 bf ba d2 c1 1c 62 1b a0 48 14 7c 5c aa 94 d1 82 cc ff 6f d5 cf 44 ad f9 6e 3d 68 28 1b b4 96 76 af 87 e7\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff\"},\n                    {\"option\": \"Hex\", \"string\": \"0f 1e 2d 3c 4b 5a 69 78\"},\n                    0, \"8\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.2\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"5e dd c2 d9 42 8f ce ee c5 0a 52 a9 64 ea e0 ff b0 4b 2d e0 06 a9 b0 4c ff 36 8f fa 92 11 16 b2 e8 e2 64 ba bd 2e fa 0d e4 3e f2 e3 b6 d0 65 e8 f7 c0 a1 78 37 b0 a4 0e b0 e2 c7 a3 74 2c 87 53 ed e5 f3 f6 d1 9b e5 54 67 5e 50 6a 77 5c 63 f0 94 d4 96 5c 31 93 19 dc d7 50 6f 45 7b 11 7b 84 b1 0b 24 6e 95 6c 2d a8 89 8a 65 6c ee f3 f7 b7 16 45 b1 9f 70 1d b8 44 85 ce 51 21 f0 f6 17 ef\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff\"},\n                    {\"option\": \"Hex\", \"string\": \"0f 1e 2d 3c 4b 5a 69 78\"},\n                    0, \"12\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.3\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"d1 ab f6 30 46 7e b4 f6 7f 1c fb 47 cd 62 6a ae 8a fe db be 4f f8 fc 5f e9 cf ae 30 7e 74 ed 45 1f 14 04 42 5a d2 b5 45 69 d5 f1 81 48 93 99 71 ab b8 fa fc 88 ce 4a c7 fe 1c 3d 1f 7a 1e b7 ca e7 6c a8 7b 61 a9 71 35 41 49 77 60 dd 9a e0 59 35 0c ad 0d ce df aa 80 a8 83 11 9a 1a 6f 98 7f d1 ce 91 fd 8e e0 82 80 34 b4 11 20 0a 97 45 a2 85 55 44 75 d1 2a fc 04 88 7f ef 35 16 d1 2a 2c\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff\"},\n                    {\"option\": \"Hex\", \"string\": \"0f 1e 2d 3c 4b 5a 69 78\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.4\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"db 43 ad 9d 1e 84 2d 12 72 e4 53 0e 27 6b 3f 56 8f 88 59 b3 f7 cf 6d 9d 2c 74 fa 53 80 8c b5 15 7a 8e bf 46 ad 3d cc 4b 6c 7d ad de 13 17 84 b0 12 0e 0e 22 f6 d5 f9 ff a7 40 7d 4a 21 b6 95 d9 c5 dd 30 bf 55 61 2f ab 9b dd 11 89 20 c1 98 16 47 0c 7f 5d cd 42 32 5d bb ed 8c 57 a5 62 81 c1 44 cb 0f 03 e8 1b 30 04 62 4e 06 50 a1 ce 5a fa f9 a7 cd 81 63 f6 db d7 26 02 25 7d d9 6e 47 1e\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00\"},\n                    {\"option\": \"Hex\", \"string\": \"0f 1e 2d 3c 4b 5a 69 78\"},\n                    0, \"8\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.5\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"7e d1 2a 3a 63 91 2a e9 41 ba 6d 4c 0d 5e 86 2e 56 8b 0e 55 89 34 69 35 50 5f 06 4b 8c 26 98 db f7 d8 50 66 7d 8e 67 be 63 9f 3b 4f 6a 16 f9 2e 65 ea 80 f6 c7 42 94 45 da 1f c2 c1 b9 36 50 40 e3 2e 50 c4 10 6f 3b 3d a1 ce 7c cb 1e 71 40 b1 53 49 3c 0f 3a d9 a9 bc ff 07 7e c4 59 6f 1d 0f 29 bf 9c ba a5 02 82 0f 73 2a f5 a9 3c 49 ee e3 3d 1c 4f 12 af 3b 42 97 af 91 fe 41 ea 9e 94 a2\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00\"},\n                    {\"option\": \"Hex\", \"string\": \"0f 1e 2d 3c 4b 5a 69 78\"},\n                    0, \"12\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.6\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"9f ad f4 09 c0 08 11 d0 04 31 d6 7e fb d8 8f ba 59 21 8d 5d 67 08 b1 d6 85 86 3f ab bb 0e 96 1e ea 48 0f d6 fb 53 2b fd 49 4b 21 51 01 50 57 42 3a b6 0a 63 fe 4f 55 f7 a2 12 e2 16 7c ca b9 31 fb fd 29 cf 7b c1 d2 79 ed df 25 dd 31 6b b8 84 3d 6e de e0 bd 1e f1 21 d1 2f a1 7c bc 2c 57 4c cc ab 5e 27 51 67 b0 8b d6 86 f8 a0 9d f8 7e c3 ff b3 53 61 b9 4e bf a1 3f ec 0e 48 89 d1 8d a5\",\n        recipeConfig: [\n            {\n                \"op\": \"ChaCha\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00\"},\n                    {\"option\": \"Hex\", \"string\": \"0f 1e 2d 3c 4b 5a 69 78\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ChangeIPFormat.mjs",
    "content": "/**\n * Change IP format tests.\n *\n * @author Chris Smith\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Change IP format: Dotted Decimal to Hex\",\n        input: \"192.168.1.1\",\n        expectedOutput: \"c0a80101\",\n        recipeConfig: [\n            {\n                op: \"Change IP format\",\n                args: [\"Dotted Decimal\", \"Hex\"],\n            },\n        ],\n    }, {\n        name: \"Change IP format: Decimal to Dotted Decimal\",\n        input: \"3232235777\",\n        expectedOutput: \"192.168.1.1\",\n        recipeConfig: [\n            {\n                op: \"Change IP format\",\n                args: [\"Decimal\", \"Dotted Decimal\"],\n            },\n        ],\n    }, {\n        name: \"Change IP format: Hex to Octal\",\n        input: \"c0a80101\",\n        expectedOutput: \"030052000401\",\n        recipeConfig: [\n            {\n                op: \"Change IP format\",\n                args: [\"Hex\", \"Octal\"],\n            },\n        ],\n    }, {\n        name: \"Change IP format: Octal to Decimal\",\n        input: \"030052000401\",\n        expectedOutput: \"3232235777\",\n        recipeConfig: [\n            {\n                op: \"Change IP format\",\n                args: [\"Octal\", \"Decimal\"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/CharEnc.mjs",
    "content": "/**\n * CharEnc tests.\n *\n * @author tlwr [toby@toby.codes]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Encode text, Decode text: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Encode text\",\n                \"args\": [\"UTF-8 (65001)\"]\n            },\n            {\n                \"op\": \"Decode text\",\n                \"args\": [\"UTF-8 (65001)\"]\n            },\n        ],\n    },\n    {\n        name: \"Encode text, Decode text: hello\",\n        input: \"hello\",\n        expectedOutput: \"hello\",\n        recipeConfig: [\n            {\n                \"op\": \"Encode text\",\n                \"args\": [\"UTF-8 (65001)\"]\n            },\n            {\n                \"op\": \"Decode text\",\n                \"args\": [\"UTF-8 (65001)\"]\n            },\n        ],\n    },\n    {\n        name: \"Encode text (EBCDIC): hello\",\n        input: \"hello\",\n        expectedOutput: \"88 85 93 93 96\",\n        recipeConfig: [\n            {\n                \"op\": \"Encode text\",\n                \"args\": [\"IBM EBCDIC International (500)\"]\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\"Space\"]\n            },\n        ],\n    },\n    {\n        name: \"Decode text (EBCDIC): 88 85 93 93 96\",\n        input: \"88 85 93 93 96\",\n        expectedOutput: \"hello\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Space\"]\n            },\n            {\n                \"op\": \"Decode text\",\n                \"args\": [\"IBM EBCDIC International (500)\"]\n            },\n        ],\n    },\n    {\n        name: \"Generate Base64 Windows PowerShell\",\n        input: \"ZABpAHIAIAAiAGMAOgBcAHAAcgBvAGcAcgBhAG0AIABmAGkAbABlAHMAIgAgAA==\",\n        expectedOutput: \"dir \\\"c:\\\\program files\\\" \",\n        recipeConfig: [\n            {\n                \"op\": \"From Base64\",\n                \"args\": [\"A-Za-z0-9+/=\", true]\n            },\n            {\n                \"op\": \"Decode text\",\n                \"args\": [\"UTF-16LE (1200)\"]\n            },\n            {\n                \"op\": \"Encode text\",\n                \"args\": [\"UTF-8 (65001)\"]\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Charts.mjs",
    "content": "/**\n * Chart tests.\n *\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Scatter chart\",\n        input: \"100 100\\n200 200\\n300 300\\n400 400\\n500 500\",\n        expectedMatch: /^<svg width/,\n        recipeConfig: [\n            {\n                \"op\": \"Scatter chart\",\n                \"args\": [\"Line feed\", \"Space\", false, \"time\", \"stress\", \"black\", 5, false]\n            }\n        ],\n    },\n    {\n        name: \"Hex density chart\",\n        input: \"100 100\\n200 200\\n300 300\\n400 400\\n500 500\",\n        expectedMatch: /^<svg width/,\n        recipeConfig: [\n            {\n                \"op\": \"Hex Density chart\",\n                \"args\": [\"Line feed\", \"Space\", 25, 15, true, \"\", \"\", true, \"white\", \"black\", true]\n            }\n        ],\n    },\n    {\n        name: \"Series chart\",\n        input: \"100 100 100\\n200 200 200\\n300 300 300\\n400 400 400\\n500 500 500\",\n        expectedMatch: /^<svg width/,\n        recipeConfig: [\n            {\n                \"op\": \"Series chart\",\n                \"args\": [\"Line feed\", \"Space\", \"\", 1, \"mediumseagreen, dodgerblue, tomato\"]\n            }\n        ],\n    },\n    {\n        name: \"Heatmap chart\",\n        input: \"100 100\\n200 200\\n300 300\\n400 400\\n500 500\",\n        expectedMatch: /^<svg width/,\n        recipeConfig: [\n            {\n                \"op\": \"Heatmap chart\",\n                \"args\": [\"Line feed\", \"Space\", 25, 25, true, \"\", \"\", false, \"white\", \"black\"]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/CipherSaber2.mjs",
    "content": "/**\n * Ciphersaber2 tests.\n *\n * @author n1073645 [n1073645@gmail.com]\n *\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"CipherSaber2 Encrypt\",\n        input: \"Hello World\",\n        expectedMatch: /.{21}/s,\n        recipeConfig: [\n            {\n                op: \"CipherSaber2 Encrypt\",\n                args: [{ \"option\": \"Latin1\", \"string\": \"test\" }, 20],\n            },\n        ],\n    },\n    {\n        // input taken from https://ciphersaber.gurus.org/\n        name: \"CipherSaber2 Decrypt\",\n        input: \"\\x6f\\x6d\\x0b\\xab\\xf3\\xaa\\x67\\x19\\x03\\x15\\x30\\xed\\xb6\\x77\"  +\n            \"\\xca\\x74\\xe0\\x08\\x9d\\xd0\\xe7\\xb8\\x85\\x43\\x56\\xbb\\x14\\x48\\xe3\" +\n            \"\\x7c\\xdb\\xef\\xe7\\xf3\\xa8\\x4f\\x4f\\x5f\\xb3\\xfd\",\n        expectedOutput: \"This is a test of CipherSaber.\",\n        recipeConfig: [\n            {\n                op: \"CipherSaber2 Decrypt\",\n                args: [{ \"option\": \"Latin1\", \"string\": \"asdfg\" }, 1],\n            },\n        ],\n    },\n    {\n        name: \"CipherSaber2 Encrypt\",\n        input: \"\",\n        expectedMatch: /.{10}/s,\n        recipeConfig: [\n            {\n                op: \"CipherSaber2 Encrypt\",\n                args: [{ \"option\": \"Latin1\", \"string\": \"\" }, 20],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Ciphers.mjs",
    "content": "/**\n * Cipher tests.\n *\n * @author Matt C [matt@artemisbot.uk]\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n\nTestRegister.addTests([\n    {\n        name: \"Affine Encode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Encode\",\n                args: [1, 0]\n            }\n        ],\n    },\n    {\n        name: \"Affine Encode: invalid a & b (non-integer)\",\n        input: \"some keys are shaped as locks. index[me]\",\n        expectedOutput: \"The values of a and b can only be integers.\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Encode\",\n                args: [0.1, 0.00001]\n            }\n        ],\n    },\n    {\n        name: \"Affine Encode: no effect\",\n        input: \"some keys are shaped as locks. index[me]\",\n        expectedOutput: \"some keys are shaped as locks. index[me]\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Encode\",\n                args: [1, 0]\n            }\n        ],\n    },\n    {\n        name: \"Affine Encode: normal\",\n        input: \"some keys are shaped as locks. index[me]\",\n        expectedOutput: \"vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Encode\",\n                args: [23, 23]\n            }\n        ],\n    },\n    {\n        name: \"Affine Decode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Decode\",\n                args: [1, 0]\n            }\n        ],\n    },\n    {\n        name: \"Affine Decode: invalid a & b (non-integer)\",\n        input: \"vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]\",\n        expectedOutput: \"The values of a and b can only be integers.\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Decode\",\n                args: [0.1, 0.00001]\n            }\n        ],\n    },\n    {\n        name: \"Affine Decode: invalid a (coprime)\",\n        input: \"vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]\",\n        expectedOutput: \"The value of `a` must be coprime to 26.\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Decode\",\n                args: [8, 23]\n            }\n        ],\n    },\n    {\n        name: \"Affine Decode: no effect\",\n        input: \"vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]\",\n        expectedOutput: \"vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Decode\",\n                args: [1, 0]\n            }\n        ],\n    },\n    {\n        name: \"Affine Decode: normal\",\n        input: \"vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]\",\n        expectedOutput: \"some keys are shaped as locks. index[me]\",\n        recipeConfig: [\n            {\n                op: \"Affine Cipher Decode\",\n                args: [23, 23]\n            }\n        ],\n    },\n    {\n        name: \"A1Z26 Encode: normal\",\n        input: \"This is the test sentence.\",\n        expectedOutput: \"20 8 9 19 9 19 20 8 5 20 5 19 20 19 5 14 20 5 14 3 5\",\n        recipeConfig: [\n            {\n                op: \"A1Z26 Cipher Encode\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"A1Z26 Decode: normal\",\n        input: \"20 8 9 19 9 19 20 8 5 20 5 19 20 19 5 14 20 5 14 3 5\",\n        expectedOutput: \"thisisthetestsentence\",\n        recipeConfig: [\n            {\n                op: \"A1Z26 Cipher Decode\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"A1Z26 Decode: error\",\n        input: \"20 8 9 27\",\n        expectedOutput: \"Error: all numbers must be between 1 and 26.\",\n        recipeConfig: [\n            {\n                op: \"A1Z26 Cipher Decode\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"Atbash: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Atbash Cipher\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"Atbash: normal\",\n        input: \"old slow slim horn\",\n        expectedOutput: \"low hold horn slim\",\n        recipeConfig: [\n            {\n                op: \"Atbash Cipher\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Encode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Encode\",\n                \"args\": [\"nothing\"]\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Encode: no key\",\n        input: \"We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.\",\n        expectedOutput: \"Vq daqcliho rmltofvlnc qbdhlcr nt qdq Fbm-Rdkkm vuoottnoi aitp al axf tdtmvt owppkaodtx.\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Encode\",\n                \"args\": [\"\"]\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Encode: invalid key (non-alphabetic)\",\n        input: \"We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.\",\n        expectedOutput: \"The key must consist only of letters in the English alphabet\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Encode\",\n                \"args\": [\"abc123\"]\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Encode: normal\",\n        input: \"We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.\",\n        expectedOutput: \"Wc snpsigdd cpfrrcxnfi hikdnnp dm crc Fcb-Pdeug vueageacc vtyl sa zxm crebzp lyoeuaiwpv.\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Encode\",\n                \"args\": [\"Schrodinger\"]\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Decode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Decode\",\n                \"args\": [\"nothing\"]\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Decode: no key\",\n        input: \"Vq daqcliho rmltofvlnc qbdhlcr nt qdq Fbm-Rdkkm vuoottnoi aitp al axf tdtmvt owppkaodtx.\",\n        expectedOutput: \"We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Decode\",\n                \"args\": [\"\"]\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Decode: invalid key (non-alphabetic)\",\n        input: \"Vq daqcliho rmltofvlnc qbdhlcr nt qdq Fbm-Rdkkm vuoottnoi aitp al axf tdtmvt owppkaodtx.\",\n        expectedOutput: \"The key must consist only of letters in the English alphabet\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Decode\",\n                \"args\": [\"abc123\"]\n            }\n        ],\n    },\n    {\n        name: \"Bifid Cipher Decode: normal\",\n        input: \"Wc snpsigdd cpfrrcxnfi hikdnnp dm crc Fcb-Pdeug vueageacc vtyl sa zxm crebzp lyoeuaiwpv.\",\n        expectedOutput: \"We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.\",\n        recipeConfig: [\n            {\n                \"op\": \"Bifid Cipher Decode\",\n                \"args\": [\"Schrodinger\"]\n            }\n        ],\n    },\n    {\n        name: \"Citrix CTX1 Encode\",\n        input: \"Password1\",\n        expectedOutput: \"PFFAJEDBOHECJEDBODEGIMCJPOFLJKDPKLAO\",\n        recipeConfig: [\n            {\n                \"op\": \"Citrix CTX1 Encode\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Citrix CTX1 Decode: normal\",\n        input: \"PFFAJEDBOHECJEDBODEGIMCJPOFLJKDPKLAO\",\n        expectedOutput: \"Password1\",\n        recipeConfig: [\n            {\n                \"op\": \"Citrix CTX1 Decode\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Citrix CTX1 Decode: invalid length\",\n        input: \"PFFAJEDBOHECJEDBODEGIMCJPOFLJKDPKLA\",\n        expectedOutput: \"Incorrect hash length\",\n        recipeConfig: [\n            {\n                \"op\": \"Citrix CTX1 Decode\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Encode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Encode\",\n                \"args\": [\"nothing\"]\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Encode: no key\",\n        input: \"LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID\",\n        expectedOutput: \"No key entered\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Encode\",\n                \"args\": [\"\"]\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Encode: invalid key\",\n        input: \"LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID\",\n        expectedOutput: \"The key must consist only of letters\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Encode\",\n                \"args\": [\"abc123\"]\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Encode: normal\",\n        input: \"LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID\",\n        expectedOutput: \"PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Encode\",\n                \"args\": [\"Edward\"]\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Decode: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Decode\",\n                \"args\": [\"nothing\"]\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Decode: no key\",\n        input: \"PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED\",\n        expectedOutput: \"No key entered\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Decode\",\n                \"args\": [\"\"]\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Decode: invalid key\",\n        input: \"PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED\",\n        expectedOutput: \"The key must consist only of letters\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Decode\",\n                \"args\": [\"abc123\"]\n            }\n        ],\n    },\n    {\n        name: \"Vigenère Decode: normal\",\n        input: \"PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED\",\n        expectedOutput: \"LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID\",\n        recipeConfig: [\n            {\n                \"op\": \"Vigenère Decode\",\n                \"args\": [\"Edward\"]\n            }\n        ],\n    },\n    {\n        name: \"Substitute: no pt/ct\",\n        input: \"flee at once. we are discovered!\",\n        expectedOutput: \"flee at once. we are discovered!\",\n        recipeConfig: [\n            {\n                \"op\": \"Substitute\",\n                \"args\": [\"\", \"\"]\n            }\n        ],\n    },\n    {\n        name: \"Substitute: no input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Substitute\",\n                \"args\": [\"abcdefghijklmnopqrstuvwxyz\", \"zebrascdfghijklmnopqtuvwxy\"]\n            }\n        ],\n    },\n    {\n        name: \"Substitute: uneven pt/ct\",\n        input: \"flee at once. we are discovered!\",\n        expectedOutput: \"Warning: Plaintext and Ciphertext lengths differ\\n\\nsiaa zq lkba. va zoa rfpbluaoar!\",\n        recipeConfig: [\n            {\n                \"op\": \"Substitute\",\n                \"args\": [\"abcdefghijklmnopqrstuvwxyz\", \"zebrascdfghijklmnopqtuvwx\"]\n            }\n        ],\n    },\n    {\n        name: \"Substitute: normal\",\n        input: \"flee at once. we are discovered!\",\n        expectedOutput: \"siaa zq lkba. va zoa rfpbluaoar!\",\n        recipeConfig: [\n            {\n                \"op\": \"Substitute\",\n                \"args\": [\"abcdefghijklmnopqrstuvwxyz\", \"zebrascdfghijklmnopqtuvwxy\"]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Decode: normal\",\n        input: \"Cytgah sTEAto rtn rsligcdsrporpyi H r fWiigo ovn oe\",\n        expectedOutput: \"Cryptography is THE Art of Writing or solving codes\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Decode\",\n                \"args\": [2, 0]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Decode: key has to be bigger than 2\",\n        input: \"Cytgah sTEAto rtn rsligcdsrporpyi H r fWiigo ovn oe\",\n        expectedOutput: \"Key has to be bigger than 2\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Decode\",\n                \"args\": [1, 0]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Decode: key has to be smaller than input's length\",\n        input: \"shortinput\",\n        expectedOutput: \"Key should be smaller than the cipher's length\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Decode\",\n                \"args\": [22, 0]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Decode: offset should be positive\",\n        input: \"shortinput\",\n        expectedOutput: \"Offset has to be a positive integer\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Decode\",\n                \"args\": [2, -1]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Decode: Normal with Offset non-null\",\n        input: \"51746026813793592840\",\n        expectedOutput: \"12345678901234567890\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Decode\",\n                \"args\": [4, 2]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Encode: normal\",\n        input: \"Cryptography is THE Art of Writing or solving codes\",\n        expectedOutput: \"Cytgah sTEAto rtn rsligcdsrporpyi H r fWiigo ovn oe\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Encode\",\n                \"args\": [2, 0]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Encode: key has to be bigger than 2\",\n        input: \"Cryptography is THE Art of Writing or solving codes\",\n        expectedOutput: \"Key has to be bigger than 2\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Encode\",\n                \"args\": [1, 0]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Encode: key has to be smaller than input's length\",\n        input: \"shortinput\",\n        expectedOutput: \"Key should be smaller than the plain text's length\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Encode\",\n                \"args\": [22, 0]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Encode: offset should be positive\",\n        input: \"shortinput\",\n        expectedOutput: \"Offset has to be a positive integer\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Encode\",\n                \"args\": [2, -1]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Encode: Normal with Offset non-null\",\n        input: \"12345678901234567890\",\n        expectedOutput: \"51746026813793592840\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Encode\",\n                \"args\": [4, 2]\n            }\n        ],\n    },\n    {\n        name: \"Rail Fence Cipher Encode: Normal with Offset with Spaces\",\n        input: \"No one expects the spanish Inquisition.\",\n        expectedOutput: \"  e  n ut.ooeepcstesaihIqiiinNnxthpsnso\",\n        recipeConfig: [\n            {\n                \"op\": \"Rail Fence Cipher Encode\",\n                \"args\": [3, 2]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Code.mjs",
    "content": "/**\n * Code tests.\n *\n * @author tlwr [toby@toby.codes]\n * @author Matt C [matt@artemisbot.uk]\n *\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst JSON_TEST_DATA = {\n    \"store\": {\n        \"book\": [{\n            \"category\": \"reference\",\n            \"author\": \"Nigel Rees\",\n            \"title\": \"Sayings of the Century\",\n            \"price\": 8.95\n        }, {\n            \"category\": \"fiction\",\n            \"author\": \"Evelyn Waugh\",\n            \"title\": \"Sword of Honour\",\n            \"price\": 12.99\n        }, {\n            \"category\": \"fiction\",\n            \"author\": \"Herman Melville\",\n            \"title\": \"Moby Dick\",\n            \"isbn\": \"0-553-21311-3\",\n            \"price\": 8.99\n        }, {\n            \"category\": \"fiction\",\n            \"author\": \"J. R. R. Tolkien\",\n            \"title\": \"The Lord of the Rings\",\n            \"isbn\": \"0-395-19395-8\",\n            \"price\": 22.99\n        }],\n        \"bicycle\": {\n            \"color\": \"red\",\n            \"price\": 19.95\n        },\n        \"newspaper\": [{\n            \"format\": \"broadsheet\",\n            \"title\": \"Financial Times\",\n            \"price\": 2.75\n        }, {\n            \"format\": \"tabloid\",\n            \"title\": \"The Guardian\",\n            \"price\": 2.00\n        }]\n    }\n};\n\nTestRegister.addTests([\n    {\n        name: \"To Camel case (dumb)\",\n        input: \"hello world\",\n        expectedOutput: \"helloWorld\",\n        recipeConfig: [\n            {\n                \"op\": \"To Camel case\",\n                \"args\": [false]\n            }\n        ],\n    },\n    {\n        name: \"To Snake case (dumb)\",\n        input: \"hello world\",\n        expectedOutput: \"hello_world\",\n        recipeConfig: [\n            {\n                \"op\": \"To Snake case\",\n                \"args\": [false]\n            }\n        ],\n    },\n    {\n        name: \"To Kebab case (dumb)\",\n        input: \"hello world\",\n        expectedOutput: \"hello-world\",\n        recipeConfig: [\n            {\n                \"op\": \"To Kebab case\",\n                \"args\": [false]\n            }\n        ],\n    },\n    {\n        name: \"To Camel case (smart)\",\n        input: [\n            \"test='hello'\",\n            \"echo $test\",\n            \"a_camel_case_function\",\n            \"$a_camel_case_variable;\",\n            \"function function_name() {\",\n            \"  console.log('things inside quotes do not get broken');\",\n            \"  console.log(\\\"things inside quotes do not get broken\\\");\",\n            \"}\",\n        ].join(\"\\n\"),\n        expectedOutput: [\n            \"test='hello'\",\n            \"echo $test\",\n            \"aCamelCaseFunction\",\n            \"$aCamelCaseVariable;\",\n            \"function functionName() {\",\n            \"  console.log('things inside quotes do not get broken');\",\n            \"  console.log(\\\"things inside quotes do not get broken\\\");\",\n            \"}\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"To Camel case\",\n                \"args\": [true]\n            }\n        ],\n    },\n    {\n        name: \"To Snake case (smart)\",\n        input: [\n            \"test='hello'\",\n            \"echo $test\",\n            \"aSnakeCaseFunction\",\n            \"$aSnakeCaseVariable;\",\n            \"function functionName() {\",\n            \"  console.log('things inside quotes do not get broken');\",\n            \"  console.log(\\\"things inside quotes do not get broken\\\");\",\n            \"}\",\n        ].join(\"\\n\"),\n        expectedOutput: [\n            \"test='hello'\",\n            \"echo $test\",\n            \"a_snake_case_function\",\n            \"$a_snake_case_variable;\",\n            \"function function_name() {\",\n            \"  console.log('things inside quotes do not get broken');\",\n            \"  console.log(\\\"things inside quotes do not get broken\\\");\",\n            \"}\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"To Snake case\",\n                \"args\": [true]\n            }\n        ],\n    },\n    {\n        name: \"To Kebab case (smart)\",\n        input: [\n            \"test='hello'\",\n            \"echo $test\",\n            \"aKebabCaseFunction\",\n            \"$aKebabCaseVariable;\",\n            \"function functionName() {\",\n            \"  console.log('things inside quotes do not get broken');\",\n            \"  console.log(\\\"things inside quotes do not get broken\\\");\",\n            \"}\",\n        ].join(\"\\n\"),\n        expectedOutput: [\n            \"test='hello'\",\n            \"echo $test\",\n            \"a-kebab-case-function\",\n            \"$a-kebab-case-variable;\",\n            \"function function-name() {\",\n            \"  console.log('things inside quotes do not get broken');\",\n            \"  console.log(\\\"things inside quotes do not get broken\\\");\",\n            \"}\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"To Kebab case\",\n                \"args\": [true]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: Empty JSON\",\n        input: \"\",\n        expectedOutput: \"Invalid input JSON: Unexpected end of JSON input\",\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: Empty expression\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: Fetch of values from specific object\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: [\n            \"\\\"Nigel Rees\\\"\",\n            \"\\\"Evelyn Waugh\\\"\",\n            \"\\\"Herman Melville\\\"\",\n            \"\\\"J. R. R. Tolkien\\\"\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$.store.book[*].author\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: Fetch of all values with matching key\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: [\n            \"\\\"Sayings of the Century\\\"\",\n            \"\\\"Sword of Honour\\\"\",\n            \"\\\"Moby Dick\\\"\",\n            \"\\\"The Lord of the Rings\\\"\",\n            \"\\\"Financial Times\\\"\",\n            \"\\\"The Guardian\\\"\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$..title\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: All data in object\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: [\n            \"[{\\\"category\\\":\\\"reference\\\",\\\"author\\\":\\\"Nigel Rees\\\",\\\"title\\\":\\\"Sayings of the Century\\\",\\\"price\\\":8.95},{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"Evelyn Waugh\\\",\\\"title\\\":\\\"Sword of Honour\\\",\\\"price\\\":12.99},{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"Herman Melville\\\",\\\"title\\\":\\\"Moby Dick\\\",\\\"isbn\\\":\\\"0-553-21311-3\\\",\\\"price\\\":8.99},{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"J. R. R. Tolkien\\\",\\\"title\\\":\\\"The Lord of the Rings\\\",\\\"isbn\\\":\\\"0-395-19395-8\\\",\\\"price\\\":22.99}]\",\n            \"{\\\"color\\\":\\\"red\\\",\\\"price\\\":19.95}\",\n            \"[{\\\"format\\\":\\\"broadsheet\\\",\\\"title\\\":\\\"Financial Times\\\",\\\"price\\\":2.75},{\\\"format\\\":\\\"tabloid\\\",\\\"title\\\":\\\"The Guardian\\\",\\\"price\\\":2}]\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$.store.*\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: Last element in array\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"J. R. R. Tolkien\\\",\\\"title\\\":\\\"The Lord of the Rings\\\",\\\"isbn\\\":\\\"0-395-19395-8\\\",\\\"price\\\":22.99}\",\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$..book[-1:]\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: First 2 elements in array\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: [\n            \"{\\\"category\\\":\\\"reference\\\",\\\"author\\\":\\\"Nigel Rees\\\",\\\"title\\\":\\\"Sayings of the Century\\\",\\\"price\\\":8.95}\",\n            \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"Evelyn Waugh\\\",\\\"title\\\":\\\"Sword of Honour\\\",\\\"price\\\":12.99}\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$..book[:2]\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: All elements in array with property\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: [\n            \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"Herman Melville\\\",\\\"title\\\":\\\"Moby Dick\\\",\\\"isbn\\\":\\\"0-553-21311-3\\\",\\\"price\\\":8.99}\",\n            \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"J. R. R. Tolkien\\\",\\\"title\\\":\\\"The Lord of the Rings\\\",\\\"isbn\\\":\\\"0-395-19395-8\\\",\\\"price\\\":22.99}\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$..book[?(@.isbn)]\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: All elements in array which meet condition\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: [\n            \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"Evelyn Waugh\\\",\\\"title\\\":\\\"Sword of Honour\\\",\\\"price\\\":12.99}\",\n            \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"Herman Melville\\\",\\\"title\\\":\\\"Moby Dick\\\",\\\"isbn\\\":\\\"0-553-21311-3\\\",\\\"price\\\":8.99}\",\n            \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"J. R. R. Tolkien\\\",\\\"title\\\":\\\"The Lord of the Rings\\\",\\\"isbn\\\":\\\"0-395-19395-8\\\",\\\"price\\\":22.99}\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$..book[?(@.price<30 && @.category==\\\"fiction\\\")]\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: All elements in object\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: [\n            \"{\\\"category\\\":\\\"reference\\\",\\\"author\\\":\\\"Nigel Rees\\\",\\\"title\\\":\\\"Sayings of the Century\\\",\\\"price\\\":8.95}\",\n            \"{\\\"category\\\":\\\"fiction\\\",\\\"author\\\":\\\"Herman Melville\\\",\\\"title\\\":\\\"Moby Dick\\\",\\\"isbn\\\":\\\"0-553-21311-3\\\",\\\"price\\\":8.99}\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\"$..book[?(@.price<10)]\", \"\\n\"]\n            }\n        ],\n    },\n    {\n        name: \"JPath Expression: Script-based expression\",\n        input: \"[{}]\",\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\n                    \"$..[?(({__proto__:[].constructor}).constructor(\\\"self.postMessage({action:'bakeComplete',data:{bakeId:1,dish:{type:1,value:''},duration:1,error:false,id:undefined,inputNum:2,progress:1,result:'<iframe/onload=debugger>',type: 'html'}});\\\")();)]\",\n                    \"\\n\"\n                ]\n            }\n        ],\n        expectedMatch: /^Invalid JPath expression: Unexpected \"{\" at character 1/\n    },\n    {\n        name: \"JPath Expression: Script-based RCE\",\n        input: \"[{}]\",\n        recipeConfig: [\n            {\n                \"op\": \"JPath expression\",\n                \"args\": [\n                    \"$..[?(p=\\\"console.log(this.process.mainModule.require('child_process').execSync('id').toString())\\\";a=''[['constructor']][['constructor']](p);a())]\",\n                    \"\\n\"\n                ]\n            }\n        ],\n        expectedMatch: /^Invalid JPath expression: jsonPath: Cannot read properties of {2}\\(reading 'constructor'\\): /    },\n    {\n        name: \"CSS selector\",\n        input: '<div id=\"test\">\\n<p class=\"a\">hello</p>\\n<p>world</p>\\n<p class=\"a\">again</p>\\n</div>',\n        expectedOutput: '<p class=\"a\">hello</p>\\n<p class=\"a\">again</p>',\n        recipeConfig: [\n            {\n                \"op\": \"CSS selector\",\n                \"args\": [\"#test p.a\", \"\\\\n\"]\n            }\n        ]\n    },\n    {\n        name: \"XPath expression\",\n        input: '<div id=\"test\">\\n<p class=\"a\">hello</p>\\n<p>world</p>\\n<p class=\"a\">again</p>\\n</div>',\n        expectedOutput: '<p class=\"a\">hello</p>\\n<p class=\"a\">again</p>',\n        recipeConfig: [\n            {\n                \"op\": \"XPath expression\",\n                \"args\": [\"/div/p[@class=\\\"a\\\"]\", \"\\\\n\"]\n            }\n        ]\n    },\n    {\n        name: \"To MessagePack: no content\",\n        input: \"\",\n        expectedMatch: /Unexpected end of JSON input/,\n        recipeConfig: [\n            {\n                \"op\": \"To MessagePack\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"From MessagePack: no content\",\n        input: \"\",\n        expectedOutput: \"Could not decode MessagePack to JSON: Error: Could not parse\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Space\"]\n            },\n            {\n                \"op\": \"From MessagePack\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"To MessagePack: valid json\",\n        input: JSON.stringify(JSON_TEST_DATA),\n        expectedOutput: \"81 a5 73 74 6f 72 65 83 a4 62 6f 6f 6b 94 84 a8 63 61 74 65 67 6f 72 79 a9 72 65 66 65 72 65 6e 63 65 a6 61 75 74 68 6f 72 aa 4e 69 67 65 6c 20 52 65 65 73 a5 74 69 74 6c 65 b6 53 61 79 69 6e 67 73 20 6f 66 20 74 68 65 20 43 65 6e 74 75 72 79 a5 70 72 69 63 65 cb 40 21 e6 66 66 66 66 66 84 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 ac 45 76 65 6c 79 6e 20 57 61 75 67 68 a5 74 69 74 6c 65 af 53 77 6f 72 64 20 6f 66 20 48 6f 6e 6f 75 72 a5 70 72 69 63 65 cb 40 29 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 af 48 65 72 6d 61 6e 20 4d 65 6c 76 69 6c 6c 65 a5 74 69 74 6c 65 a9 4d 6f 62 79 20 44 69 63 6b a4 69 73 62 6e ad 30 2d 35 35 33 2d 32 31 33 31 31 2d 33 a5 70 72 69 63 65 cb 40 21 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 b0 4a 2e 20 52 2e 20 52 2e 20 54 6f 6c 6b 69 65 6e a5 74 69 74 6c 65 b5 54 68 65 20 4c 6f 72 64 20 6f 66 20 74 68 65 20 52 69 6e 67 73 a4 69 73 62 6e ad 30 2d 33 39 35 2d 31 39 33 39 35 2d 38 a5 70 72 69 63 65 cb 40 36 fd 70 a3 d7 0a 3d a7 62 69 63 79 63 6c 65 82 a5 63 6f 6c 6f 72 a3 72 65 64 a5 70 72 69 63 65 cb 40 33 f3 33 33 33 33 33 a9 6e 65 77 73 70 61 70 65 72 92 83 a6 66 6f 72 6d 61 74 aa 62 72 6f 61 64 73 68 65 65 74 a5 74 69 74 6c 65 af 46 69 6e 61 6e 63 69 61 6c 20 54 69 6d 65 73 a5 70 72 69 63 65 cb 40 06 00 00 00 00 00 00 83 a6 66 6f 72 6d 61 74 a7 74 61 62 6c 6f 69 64 a5 74 69 74 6c 65 ac 54 68 65 20 47 75 61 72 64 69 61 6e a5 70 72 69 63 65 02\",\n        recipeConfig: [\n            {\n                \"op\": \"To MessagePack\",\n                \"args\": []\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"From MessagePack: valid msgpack\",\n        input: \"81 a5 73 74 6f 72 65 83 a4 62 6f 6f 6b 94 84 a8 63 61 74 65 67 6f 72 79 a9 72 65 66 65 72 65 6e 63 65 a6 61 75 74 68 6f 72 aa 4e 69 67 65 6c 20 52 65 65 73 a5 74 69 74 6c 65 b6 53 61 79 69 6e 67 73 20 6f 66 20 74 68 65 20 43 65 6e 74 75 72 79 a5 70 72 69 63 65 cb 40 21 e6 66 66 66 66 66 84 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 ac 45 76 65 6c 79 6e 20 57 61 75 67 68 a5 74 69 74 6c 65 af 53 77 6f 72 64 20 6f 66 20 48 6f 6e 6f 75 72 a5 70 72 69 63 65 cb 40 29 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 af 48 65 72 6d 61 6e 20 4d 65 6c 76 69 6c 6c 65 a5 74 69 74 6c 65 a9 4d 6f 62 79 20 44 69 63 6b a4 69 73 62 6e ad 30 2d 35 35 33 2d 32 31 33 31 31 2d 33 a5 70 72 69 63 65 cb 40 21 fa e1 47 ae 14 7b 85 a8 63 61 74 65 67 6f 72 79 a7 66 69 63 74 69 6f 6e a6 61 75 74 68 6f 72 b0 4a 2e 20 52 2e 20 52 2e 20 54 6f 6c 6b 69 65 6e a5 74 69 74 6c 65 b5 54 68 65 20 4c 6f 72 64 20 6f 66 20 74 68 65 20 52 69 6e 67 73 a4 69 73 62 6e ad 30 2d 33 39 35 2d 31 39 33 39 35 2d 38 a5 70 72 69 63 65 cb 40 36 fd 70 a3 d7 0a 3d a7 62 69 63 79 63 6c 65 82 a5 63 6f 6c 6f 72 a3 72 65 64 a5 70 72 69 63 65 cb 40 33 f3 33 33 33 33 33 a9 6e 65 77 73 70 61 70 65 72 92 83 a6 66 6f 72 6d 61 74 aa 62 72 6f 61 64 73 68 65 65 74 a5 74 69 74 6c 65 af 46 69 6e 61 6e 63 69 61 6c 20 54 69 6d 65 73 a5 70 72 69 63 65 cb 40 06 00 00 00 00 00 00 83 a6 66 6f 72 6d 61 74 a7 74 61 62 6c 6f 69 64 a5 74 69 74 6c 65 ac 54 68 65 20 47 75 61 72 64 69 61 6e a5 70 72 69 63 65 02\",\n        expectedOutput: JSON.stringify(JSON_TEST_DATA, null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Space\"]\n            },\n            {\n                \"op\": \"From MessagePack\",\n                \"args\": []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Colossus.mjs",
    "content": "/**\n * Colossus tests.\n * @author VirtualColossus [martin@virtualcolossus.co.uk]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Colossus Letter Count\",\n        input: \"CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/N\",\n        expectedMatch: /00 00 : a30/,\n        recipeConfig: [\n            {\n                \"op\": \"Colossus\",\n                \"args\": [\n                    \"\",\n                    \"KH Pattern\",\n                    \"Z\", \"\", \"\",\n                    \"None\", \"Select Program\", \"Letter Count\",\n                    \"\",\n                    \"\", \"\", \"\", \"\", \"\", false, \"\",\n                    \"\", \"\", \"\", \"\", \"\", false, \"\",\n                    \"\", \"\", \"\", \"\", \"\", false, \"\",\n                    false,\n                    \"\",\n                    false, false, false, false, false,\n                    \"\", false, false, \"\",\n                    \"\",\n                    0, \"\", \"\",\n                    \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\"\n                ]\n            }\n        ]\n    },\n    // Takes a while to run, so disabling for general purpose testing. Re-enable if modifying this operation.\n    // {\n    //     name: \"Colossus 1+2=.\",\n    //     input: \"CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/NNLH+WBFSBMJM-E3LMTPYJ.ZJDYI3TZZFVRP+REWQYOQMU3FNW.T4WB3IEPCJ-A3LWVRSZZECNGQVUFHO.R/3VZRF.Y4XEDGXZJ3RS34ARLZDZTUHMG+HH//Y+E+C-NE+--GEATU+EPEVLQEMKCLCZP-HX3C3O+CRH4D-RLIVOLHJGNI./MN4JDB/PRYZV3QOD34DAGO4+/TCS+CU-ODXMRHPPCCCD-MWHJO/TRPAELREIFVVLABSDMQORF4THY.UQTITEPYXAZ3DPNI/I+.UPTCBT-BJ/PEQFZA/PQ4+Z4-IPLBG4COART/YC4CJL+ERNVTRSHOKNSSLGFJ+QJR/ITYHWFQCPXCY.Z.X/VKTAR.LBXQK4JI/P+YDEM/LPBLK.CX/..QHBSOAO-FHJORYVBHQEYTF3/I-WZ4J.EYCYTEMG3DY3W/4VSE3/XAR3JEX4/3/LI4V/IAVLTODE-4HPLWUWMC.YEJOVFZ+/4EAIJXUT+G/.KYQF4DSVSIPLWZE.XTZWD4E.EYI/.MBJ+SVX/GBW/4RTNSGT-TF-+-JO3UE/3ZVEQKYVCUR+-3OMC+YU+UGMBCOO4.GRHVG.3+ZFB4C3J+QWCR+DHKQ3XFTUKTBGVXEQ.PCU4XLW-G+RJODDYLIJVSXOJUGKTVNBGMPH4DI+--+DKJ.MYBDX4LUWAXD-+P3.FCBWGDZQSQMPNBAODZMMM/CS3AJHEGDNZT4+.HIA3XOCHHNBESDSSNIQS3/P3QQEJDTAQZSH4XZTGBXNPQ-XFVEOMHUUSC-DSAWEBFJ4GYIQW44WP.W-IMGHZCJY/TLBWZVMNU/CETWAF3TBPIRLWDR+.WJBBYEQ.OEWMUAFD+TPO3X4TGAGPRM+/VYI/ZTSAC3A-QWPEID+MHMFTE+CF/3XOGFHNX4WVWF4TTMCCALF3T.VY4PYBCMLQ.MZRVPSA+YQL+XCY.-SEN-CZUCRCZ/PNBE4.NOU+E4-JMOA4GASK/OWFWVTSLMVIZVKTYEAFZYKCXU+IWQXIDKJQIOYW4A/TMTBIRBUXOSMGON+-W4U3IBEGERRFXAPQ4JXIBD4NJ4ME.DDRSQU+KJPHZ/+3TPKV-OKD3SBRDE4WZIVVZLXJWWL/SK/.JXF.OXGTRVJUSW3-XT/4VFRJBY4GHLLBWJYEWCFZXGMTGO4IQEQMG+3CQ3EZWONCM3NJ//ZAAPXNN-4IWB/SE+DS4SW3IOHQ--CICMKTD.AM/K-ABSCPD+VWXKKVN/3PUA+NEOZA.WWUKW+ZDTUAR/BI.PGVRFUS-/US.RSDTHGKESVB.GWGGVDHKFW4/FWPN.SDRL+GRZ/KM+.R-DHP+QC//EE/OGG+YVJ.GHDFFIXUEEOLIMC.C-4X4M-GT3DBBXANM/FER+QL3ZS/4IFSI-L.Y+KXMMR-HMCRUTZQ-LCLERSQLBMYXK.FUVKY-IA+-DDNQEE-TEDWUIB3CJYE4DMT3YPPA..AKQWSX.YT/JHFIJS/+/./+GNIRKAN/USYL4SW/BL.BNTRCFJPT.FMBZLEREFYJ/OXXQIXT-/T+IBMUNWL-JG-+ORJYJLOVZBJDKO4QJFFE.TPJGLY/.VTU-4IZBKGVBSAGWIN/N.-EK.JOWD/GYY3/SJFCYEKY/HW.O.VXTPSZVSHUA3N3QIX-+APJR43WQ+I.A/L+3/GOL.MW//KS.3Q4+3LEDVY+VDPOG+DJXHJFXBRK3SV.MPZE+/NLIA4NFEJPXYWYUUKX/UYWGU-4VF4OV4RZL.HKMRTQ/MHB/ND/VWO4ZIN/4D+ZX.PZZ.+WOCNENNZVI-IENILU.ZSHUJKIOFY3YJJA3IVRMNWENA3FQLQ4U+RKXR.IFKWEFX/VZGOUO4MYT/.PS3..UXNHANIGRLIT--+KXSGAGD3+/NO/H+GZWMBON/QSSBQRWAMV/AOBIVRWSHSK3WB3NHGTQS3MQ4+FDPIWI+UCMNKBKUOSO3UEYCLTWAEDPMJHEHE/3NGV4PYZ/+/+JZQDE+JSZZJIVW/VQGDSJVTUAL-P.K+VJRZ3NFPXUCZYA3.LNAOTBW3ATPUAUYN-J4MM3OAQTMUYICXNF-ON-L++K3RMPSX.CESSNUNJIZANHFSYTJ.XIFPJAYQMNC/QTIB.WWJ/.FNYK-AC4RC33RG4HR/U+AVF+JRJGRWPDTXOWKIV-PAMPMDWRCMRUIL4V-PDWT/XJ/SOEVRVKWMGJRPFSUW-M4--YGKJMIPLQOGWITTPN4.W+L.IVWRTY.-.FIPG4WNJFRHYOVW3OAYPJP/DJJIAMRZOU+Z/CIHUCPVT3BCLCJVACJLLQL.L4VL.Z+PSVNE-IUK.OJKNOWHKGCNZ3REIWHKEO/I.-3ZSJMN-WGPADSAAJ.-I/WKMSHGVTHI+ITSIIHZBSPUW-EIYDKT4ABEC.IMDTVET+SC.PEH4JOWDLPKPZV+UXKT/FJK+UACWJCMY+YSRYKREN/PAB+SSATQBYQ/T.Z..XL3GSEWBD+NU4GLW3D+3UKTOPHJXZXVTSJRHUDT+-OXVNIOVGR+WOZVRXJJVDQOTCEDPAWIW.VEB/-4HLHBBHCFZ+HKXA.-FJCIIHVBXRGTHEDQHCAF-/-IX3LFEM-U+QN.F3OW+IHFPM43MZ.AYMYLWUANNDOICHUJD/PKA/T-U.LACC.OFVPGYCTWO-L4PSQCWZCTOVO3JNQOY/C/4JSZZTKA4DOSH.HLQ3CGVVF4R+YURIHX4.+KMMGO-TQWVHB/P4.ZMDJZGOFAESJNWKWLV3VQROPQMZ3Q.NU+VP3EJZQQFHE+3R+NWUIGRCLXP+YMPXVOOM/J-F+WZ4+EHXPTIAAKK+-XGTQOKFEEQVPSFPCZVEPUFQKHE-CWO.BJ-.APVNMYPEA-+EQERVDPT4HDGNGCI-K-+/QOFDLSANMCRLACCRVIJBTKJG.BXNACOLKYELBL.XO+UG+CEPNLVQKR+IEYX4M/DI.RK//RAEGPMDQM/RYKF.ZULT3DLX+QW/NLZWOFFIJ3DUP+UXTSMQZZEOOYEDD+Q4KLQRE.4+FRUMDLGRXIKAD3HPEOL.QBPIHYMCSS.KMMBRSKDKK4CLH4GBSZBGHBGG/QGOT-QDI/JTED-KT+AVI-AGOXTS+I-HSG/4UYS/F.V3GC.MKCVMNLOY.MJXN/B+DRHD.KQ.RSJKIN3U.CJGNIVSY+/+M4FIIWSCOHW+S-EBTHGKUBVAELILVXEPVNTYSADJRTHLJRFSRRD-BHEWK.DWPQFQMVO.EPIV+4CBHCUNEUESRFLMTEWAEVMVEEQYSXGAHZCTTQRE.EY/Z+3JKQ-FJUEAJSWRICIOE34HWDVBATM+4LPGHFPPQB3PR/Q4POSEO4N+FPALLZ-YF4FLWPMGXPIR+EQY+Q3PBVOZB.CDNDP3-.ZVPS4ITTUDUFSZD4-CO/EEIZFIE.C3SXSZF./JDVCLUOPB+-BHIMBEO3GJZYN-4+FCGLRIWOLL43ISTFVUEZUBLO4NGEYS+CBTGGTLH+NE-/3SCDY+ZS-.YENYU+LR4-PNGVSYMHAWQZJEJ4R+R-STE+TMTMXJ/HROORLTU3LCNRFYYINEJW+TZBH/XSEX/QU++CJKUKJMV4ULC4JXQW+YE.Y-YDJHHF.ATHTN+/BZP-B/AZPFHSSWVNWI/LDUHVHUWU.KPAR-.A-TM3.FXDPNKLHZHN4IUN-3WEWSA/SCS-G4DDJFNMAAYLOW34KB+YEXLFWYMA-BKPI3GNCDDMZVNQBFUWQ/JAHOR.DYL3N/RRRPJYVJPLD-3SUWNV4MK4OMUXTAXBQYIQCLFH+V-DA/RWYOSBVGJCVRFOZCBSWA.XW-OLXJBNHTSPHSCYH+.4+RXBWCFVF3O.+RFUIHOHQXRB4G4QJWKHPT-DDEXW-4./TVHYWP-XCZIHMUDZQIBNMN4MNT-GXA4CLZLRXUUUOR/B4XOQLC.IZOM.PAUEXRJ.GRNBQGRZUQ.ISWES/YMK+AWCTQ4+-WKKA4HZ-/..XTPDBWOA3EQEDVPMN4BXHBGCGWBPOCHIYWE4NP3ILIQP.GCZA/FEKTEOML+COOF/TVMQ3WSDKG//EEXOI-/SGFTWKOG+D.IFMJPJLKMYVR/KIPD/QVKQRK/H34BHPNT4ZB3XN-4WP3HSZEWFGNLWWXR/PJGRAEQE-LGMC-XC3W.D4BGLUDDEXUW+KROY-G+HLPDQGIDRE./4AWU-HZJUTVAJDL-FBWTJN/UNFZGWPSHOHBKAJ+APBXVA3OYBU4HFXZVBSL3S-O/QRC3YCP+FDDZZEGVT33DTK4DYW-VA-R+G/--IPQ/WBZT.SLOCHE3PHQNTIIYA.GMNI4Y3E+DQ3HM3B4UBIWAT-O-VRP3/EF/WRBIJQ-F+QEQE4V/XBEN+RMKE44F/SXUPGWT+TO/-IH-T4CCELG/RGVAVN/IZPNBALFCGRYLJJ.QYAJHPBNCBBHOALK4RSQEFVA4I.VG.WFW-MGH.3RHDXFJMIMRPK-RK/X3T4TLKPUAOCIYRH/LNBEI.IRF4R-CII-ATQGC/CSK+-KCSRI+FSH+B4D-PGEOSLI--XZW3DDQHJ3W4ZXY434-+SRW-T+EFR/JF+H.SO33REUDV+--4ZETOYTXFZS4AZDFTJNIUK.P+AYDAWLHGUC3E.I/NORSMWLZQEFQUGI3A+.WA.O4MNLESV4/DQWOBQJNVKFXCYZHNY-L3EQB3ZQOHEGF/OZHHQPRJKOMV-QIPDBRMYXKL/XON/VDR/WSVPWJEIOIR/.PJZPYIK+JQOZY-XXOCIPLRHBZL.CGJWBQ+MWXKNOFXMS.RUK+A4NFJAFYEKE.AZWXJNCP3BTFOA+ZUL.RHTX-SV4LPY+VOCHEPQDFWQFIAKYG+U4YDK.EPZPTQGHGTJZTBGW4GVFSYG4XJ-C.WWPGRKXHTUFWR/VSCEZO.ZQKVJCDMFMXG+NG-BFVK.-+THEUAHA.UDPW.MDCIR/FHJXNDSRWPYBNV-CQU-.W/AVAEGJQ3IO.-.E/FCBFXDNDA3/YIUKM-QVR3CDYGHKRZGQJZYDOE43PKPKK/4JMU+U.VBSCPMFCQ.ZQMONNHQSGBUUEGHBNQ+KYV.NKEDDZ4I43GIXJSQWKKPISSXRZPH-.KQUEN3/+FWYATQUQXOXENY/MSWZFZMLFSZ4KMUPO/CIPCTSLGETRI.LLG/LJJ.TRR+QCYSXQEWZXMO.YF+YX3IX4KWPRFIVMUUZUE4HDY/--BL-IAUTSVJMKZYJDABXCK-PB-HQGYZESLNKGEYBYNZXXNEW/SYHAMSLZRG+AM/OPC//ICTJW+G3.WB+DAGFURHVRLHQA4AOV4ZZ/JUFMVEOYPBUDF3U3KJZN.EBTHGJAFAMZDUQEHGRANOUXXBNKXV4IUO+LYNT.LID.K4WMYCHBWSOKAZ3HHWO.SUT3CDWS+4R4D+EIYMOCPCIB+.4LRFDQZY+Y/VBIYXO3KT/K-PUOFP/Q3.+ZYXT3LAJHW+DGFZPYRTJYSTFVMLEWL-.S3FNSXX3PGB+.+M/GQHZJQ/K/VZTKMVK/SF3CBSRVLFVCHLZKJWCNFANACX+JQLIN4O4Y4WMYSLGXT43RHFK.+HIJ+4EJQBGPPOHGSB+C3KABZKXTU+P/WDFTWMAURUDLK+PWC4M4TQ.Z\",\n    //     expectedMatch: /31 05 : a3108/,\n    //     recipeConfig: [\n    //         {\n    //             \"op\": \"Colossus\",\n    //             \"args\": [\n    //                 \"\",\n    //                 \"KH Pattern\",\n    //                 \"ΔZ\", \"ΔΧ\", \"\",\n    //                 \"None\", \"Select Program\", \"1+2=. (1+2 Break In, Find X1,X2)\",\n    //                 \"\",\n    //                 \"\", \"\", \"\", \"\", \"\", false, \"\",\n    //                 \"\", \"\", \"\", \"\", \"\", false, \"\",\n    //                 \"\", \"\", \"\", \"\", \"\", false, \"\",\n    //                 false,\n    //                 \"\",\n    //                 false, false, false, false, false,\n    //                 \"\", false, false, \"\",\n    //                 \"\",\n    //                 3100, \"X1\", \"X2\",\n    //                 \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\"\n    //             ]\n    //         }\n    //     ]\n    // },\n    // {\n    //     name: \"Colossus 4=5=/1=2\",\n    //     input: \"CTBKJUVXHZ-H3L4QV+YEZUK+SXOZ/NNLH+WBFSBMJM-E3LMTPYJ.ZJDYI3TZZFVRP+REWQYOQMU3FNW.T4WB3IEPCJ-A3LWVRSZZECNGQVUFHO.R/3VZRF.Y4XEDGXZJ3RS34ARLZDZTUHMG+HH//Y+E+C-NE+--GEATU+EPEVLQEMKCLCZP-HX3C3O+CRH4D-RLIVOLHJGNI./MN4JDB/PRYZV3QOD34DAGO4+/TCS+CU-ODXMRHPPCCCD-MWHJO/TRPAELREIFVVLABSDMQORF4THY.UQTITEPYXAZ3DPNI/I+.UPTCBT-BJ/PEQFZA/PQ4+Z4-IPLBG4COART/YC4CJL+ERNVTRSHOKNSSLGFJ+QJR/ITYHWFQCPXCY.Z.X/VKTAR.LBXQK4JI/P+YDEM/LPBLK.CX/..QHBSOAO-FHJORYVBHQEYTF3/I-WZ4J.EYCYTEMG3DY3W/4VSE3/XAR3JEX4/3/LI4V/IAVLTODE-4HPLWUWMC.YEJOVFZ+/4EAIJXUT+G/.KYQF4DSVSIPLWZE.XTZWD4E.EYI/.MBJ+SVX/GBW/4RTNSGT-TF-+-JO3UE/3ZVEQKYVCUR+-3OMC+YU+UGMBCOO4.GRHVG.3+ZFB4C3J+QWCR+DHKQ3XFTUKTBGVXEQ.PCU4XLW-G+RJODDYLIJVSXOJUGKTVNBGMPH4DI+--+DKJ.MYBDX4LUWAXD-+P3.FCBWGDZQSQMPNBAODZMMM/CS3AJHEGDNZT4+.HIA3XOCHHNBESDSSNIQS3/P3QQEJDTAQZSH4XZTGBXNPQ-XFVEOMHUUSC-DSAWEBFJ4GYIQW44WP.W-IMGHZCJY/TLBWZVMNU/CETWAF3TBPIRLWDR+.WJBBYEQ.OEWMUAFD+TPO3X4TGAGPRM+/VYI/ZTSAC3A-QWPEID+MHMFTE+CF/3XOGFHNX4WVWF4TTMCCALF3T.VY4PYBCMLQ.MZRVPSA+YQL+XCY.-SEN-CZUCRCZ/PNBE4.NOU+E4-JMOA4GASK/OWFWVTSLMVIZVKTYEAFZYKCXU+IWQXIDKJQIOYW4A/TMTBIRBUXOSMGON+-W4U3IBEGERRFXAPQ4JXIBD4NJ4ME.DDRSQU+KJPHZ/+3TPKV-OKD3SBRDE4WZIVVZLXJWWL/SK/.JXF.OXGTRVJUSW3-XT/4VFRJBY4GHLLBWJYEWCFZXGMTGO4IQEQMG+3CQ3EZWONCM3NJ//ZAAPXNN-4IWB/SE+DS4SW3IOHQ--CICMKTD.AM/K-ABSCPD+VWXKKVN/3PUA+NEOZA.WWUKW+ZDTUAR/BI.PGVRFUS-/US.RSDTHGKESVB.GWGGVDHKFW4/FWPN.SDRL+GRZ/KM+.R-DHP+QC//EE/OGG+YVJ.GHDFFIXUEEOLIMC.C-4X4M-GT3DBBXANM/FER+QL3ZS/4IFSI-L.Y+KXMMR-HMCRUTZQ-LCLERSQLBMYXK.FUVKY-IA+-DDNQEE-TEDWUIB3CJYE4DMT3YPPA..AKQWSX.YT/JHFIJS/+/./+GNIRKAN/USYL4SW/BL.BNTRCFJPT.FMBZLEREFYJ/OXXQIXT-/T+IBMUNWL-JG-+ORJYJLOVZBJDKO4QJFFE.TPJGLY/.VTU-4IZBKGVBSAGWIN/N.-EK.JOWD/GYY3/SJFCYEKY/HW.O.VXTPSZVSHUA3N3QIX-+APJR43WQ+I.A/L+3/GOL.MW//KS.3Q4+3LEDVY+VDPOG+DJXHJFXBRK3SV.MPZE+/NLIA4NFEJPXYWYUUKX/UYWGU-4VF4OV4RZL.HKMRTQ/MHB/ND/VWO4ZIN/4D+ZX.PZZ.+WOCNENNZVI-IENILU.ZSHUJKIOFY3YJJA3IVRMNWENA3FQLQ4U+RKXR.IFKWEFX/VZGOUO4MYT/.PS3..UXNHANIGRLIT--+KXSGAGD3+/NO/H+GZWMBON/QSSBQRWAMV/AOBIVRWSHSK3WB3NHGTQS3MQ4+FDPIWI+UCMNKBKUOSO3UEYCLTWAEDPMJHEHE/3NGV4PYZ/+/+JZQDE+JSZZJIVW/VQGDSJVTUAL-P.K+VJRZ3NFPXUCZYA3.LNAOTBW3ATPUAUYN-J4MM3OAQTMUYICXNF-ON-L++K3RMPSX.CESSNUNJIZANHFSYTJ.XIFPJAYQMNC/QTIB.WWJ/.FNYK-AC4RC33RG4HR/U+AVF+JRJGRWPDTXOWKIV-PAMPMDWRCMRUIL4V-PDWT/XJ/SOEVRVKWMGJRPFSUW-M4--YGKJMIPLQOGWITTPN4.W+L.IVWRTY.-.FIPG4WNJFRHYOVW3OAYPJP/DJJIAMRZOU+Z/CIHUCPVT3BCLCJVACJLLQL.L4VL.Z+PSVNE-IUK.OJKNOWHKGCNZ3REIWHKEO/I.-3ZSJMN-WGPADSAAJ.-I/WKMSHGVTHI+ITSIIHZBSPUW-EIYDKT4ABEC.IMDTVET+SC.PEH4JOWDLPKPZV+UXKT/FJK+UACWJCMY+YSRYKREN/PAB+SSATQBYQ/T.Z..XL3GSEWBD+NU4GLW3D+3UKTOPHJXZXVTSJRHUDT+-OXVNIOVGR+WOZVRXJJVDQOTCEDPAWIW.VEB/-4HLHBBHCFZ+HKXA.-FJCIIHVBXRGTHEDQHCAF-/-IX3LFEM-U+QN.F3OW+IHFPM43MZ.AYMYLWUANNDOICHUJD/PKA/T-U.LACC.OFVPGYCTWO-L4PSQCWZCTOVO3JNQOY/C/4JSZZTKA4DOSH.HLQ3CGVVF4R+YURIHX4.+KMMGO-TQWVHB/P4.ZMDJZGOFAESJNWKWLV3VQROPQMZ3Q.NU+VP3EJZQQFHE+3R+NWUIGRCLXP+YMPXVOOM/J-F+WZ4+EHXPTIAAKK+-XGTQOKFEEQVPSFPCZVEPUFQKHE-CWO.BJ-.APVNMYPEA-+EQERVDPT4HDGNGCI-K-+/QOFDLSANMCRLACCRVIJBTKJG.BXNACOLKYELBL.XO+UG+CEPNLVQKR+IEYX4M/DI.RK//RAEGPMDQM/RYKF.ZULT3DLX+QW/NLZWOFFIJ3DUP+UXTSMQZZEOOYEDD+Q4KLQRE.4+FRUMDLGRXIKAD3HPEOL.QBPIHYMCSS.KMMBRSKDKK4CLH4GBSZBGHBGG/QGOT-QDI/JTED-KT+AVI-AGOXTS+I-HSG/4UYS/F.V3GC.MKCVMNLOY.MJXN/B+DRHD.KQ.RSJKIN3U.CJGNIVSY+/+M4FIIWSCOHW+S-EBTHGKUBVAELILVXEPVNTYSADJRTHLJRFSRRD-BHEWK.DWPQFQMVO.EPIV+4CBHCUNEUESRFLMTEWAEVMVEEQYSXGAHZCTTQRE.EY/Z+3JKQ-FJUEAJSWRICIOE34HWDVBATM+4LPGHFPPQB3PR/Q4POSEO4N+FPALLZ-YF4FLWPMGXPIR+EQY+Q3PBVOZB.CDNDP3-.ZVPS4ITTUDUFSZD4-CO/EEIZFIE.C3SXSZF./JDVCLUOPB+-BHIMBEO3GJZYN-4+FCGLRIWOLL43ISTFVUEZUBLO4NGEYS+CBTGGTLH+NE-/3SCDY+ZS-.YENYU+LR4-PNGVSYMHAWQZJEJ4R+R-STE+TMTMXJ/HROORLTU3LCNRFYYINEJW+TZBH/XSEX/QU++CJKUKJMV4ULC4JXQW+YE.Y-YDJHHF.ATHTN+/BZP-B/AZPFHSSWVNWI/LDUHVHUWU.KPAR-.A-TM3.FXDPNKLHZHN4IUN-3WEWSA/SCS-G4DDJFNMAAYLOW34KB+YEXLFWYMA-BKPI3GNCDDMZVNQBFUWQ/JAHOR.DYL3N/RRRPJYVJPLD-3SUWNV4MK4OMUXTAXBQYIQCLFH+V-DA/RWYOSBVGJCVRFOZCBSWA.XW-OLXJBNHTSPHSCYH+.4+RXBWCFVF3O.+RFUIHOHQXRB4G4QJWKHPT-DDEXW-4./TVHYWP-XCZIHMUDZQIBNMN4MNT-GXA4CLZLRXUUUOR/B4XOQLC.IZOM.PAUEXRJ.GRNBQGRZUQ.ISWES/YMK+AWCTQ4+-WKKA4HZ-/..XTPDBWOA3EQEDVPMN4BXHBGCGWBPOCHIYWE4NP3ILIQP.GCZA/FEKTEOML+COOF/TVMQ3WSDKG//EEXOI-/SGFTWKOG+D.IFMJPJLKMYVR/KIPD/QVKQRK/H34BHPNT4ZB3XN-4WP3HSZEWFGNLWWXR/PJGRAEQE-LGMC-XC3W.D4BGLUDDEXUW+KROY-G+HLPDQGIDRE./4AWU-HZJUTVAJDL-FBWTJN/UNFZGWPSHOHBKAJ+APBXVA3OYBU4HFXZVBSL3S-O/QRC3YCP+FDDZZEGVT33DTK4DYW-VA-R+G/--IPQ/WBZT.SLOCHE3PHQNTIIYA.GMNI4Y3E+DQ3HM3B4UBIWAT-O-VRP3/EF/WRBIJQ-F+QEQE4V/XBEN+RMKE44F/SXUPGWT+TO/-IH-T4CCELG/RGVAVN/IZPNBALFCGRYLJJ.QYAJHPBNCBBHOALK4RSQEFVA4I.VG.WFW-MGH.3RHDXFJMIMRPK-RK/X3T4TLKPUAOCIYRH/LNBEI.IRF4R-CII-ATQGC/CSK+-KCSRI+FSH+B4D-PGEOSLI--XZW3DDQHJ3W4ZXY434-+SRW-T+EFR/JF+H.SO33REUDV+--4ZETOYTXFZS4AZDFTJNIUK.P+AYDAWLHGUC3E.I/NORSMWLZQEFQUGI3A+.WA.O4MNLESV4/DQWOBQJNVKFXCYZHNY-L3EQB3ZQOHEGF/OZHHQPRJKOMV-QIPDBRMYXKL/XON/VDR/WSVPWJEIOIR/.PJZPYIK+JQOZY-XXOCIPLRHBZL.CGJWBQ+MWXKNOFXMS.RUK+A4NFJAFYEKE.AZWXJNCP3BTFOA+ZUL.RHTX-SV4LPY+VOCHEPQDFWQFIAKYG+U4YDK.EPZPTQGHGTJZTBGW4GVFSYG4XJ-C.WWPGRKXHTUFWR/VSCEZO.ZQKVJCDMFMXG+NG-BFVK.-+THEUAHA.UDPW.MDCIR/FHJXNDSRWPYBNV-CQU-.W/AVAEGJQ3IO.-.E/FCBFXDNDA3/YIUKM-QVR3CDYGHKRZGQJZYDOE43PKPKK/4JMU+U.VBSCPMFCQ.ZQMONNHQSGBUUEGHBNQ+KYV.NKEDDZ4I43GIXJSQWKKPISSXRZPH-.KQUEN3/+FWYATQUQXOXENY/MSWZFZMLFSZ4KMUPO/CIPCTSLGETRI.LLG/LJJ.TRR+QCYSXQEWZXMO.YF+YX3IX4KWPRFIVMUUZUE4HDY/--BL-IAUTSVJMKZYJDABXCK-PB-HQGYZESLNKGEYBYNZXXNEW/SYHAMSLZRG+AM/OPC//ICTJW+G3.WB+DAGFURHVRLHQA4AOV4ZZ/JUFMVEOYPBUDF3U3KJZN.EBTHGJAFAMZDUQEHGRANOUXXBNKXV4IUO+LYNT.LID.K4WMYCHBWSOKAZ3HHWO.SUT3CDWS+4R4D+EIYMOCPCIB+.4LRFDQZY+Y/VBIYXO3KT/K-PUOFP/Q3.+ZYXT3LAJHW+DGFZPYRTJYSTFVMLEWL-.S3FNSXX3PGB+.+M/GQHZJQ/K/VZTKMVK/SF3CBSRVLFVCHLZKJWCNFANACX+JQLIN4O4Y4WMYSLGXT43RHFK.+HIJ+4EJQBGPPOHGSB+C3KABZKXTU+P/WDFTWMAURUDLK+PWC4M4TQ.Z\",\n    //     expectedMatch: /15 08 : a969/,\n    //     recipeConfig: [\n    //         {\n    //             \"op\": \"Colossus\",\n    //             \"args\": [\n    //                 \"\",\n    //                 \"KH Pattern\",\n    //                 \"ΔZ\", \"ΔΧ\", \"\",\n    //                 \"None\", \"Select Program\", \"4=5=/1=2 (Given X1,X2 find X4,X5)\",\n    //                 \"\",\n    //                 \"\", \"\", \"\", \"\", \"\", false, \"\",\n    //                 \"\", \"\", \"\", \"\", \"\", false, \"\",\n    //                 \"\", \"\", \"\", \"\", \"\", false, \"\",\n    //                 false,\n    //                 \"\",\n    //                 false, false, false, false, false,\n    //                 \"\", false, false, \"\",\n    //                 \"\",\n    //                 960, \"X4\", \"X5\",\n    //                 \"31\", \"5\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\", \"1\"\n    //             ]\n    //         }\n    //     ]\n    // },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Comment.mjs",
    "content": "/**\n * Flow Control tests.\n *\n * @author tlwr [toby@toby.codes]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nTestRegister.addTests([\n    {\n        name: \"Comment: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Comment\",\n                \"args\": [\"\"]\n            }\n        ]\n    },\n    {\n        name: \"Fork, Comment, Base64\",\n        input: \"cat\\nsat\\nmat\",\n        expectedOutput: \"Y2F0\\nc2F0\\nbWF0\",\n        recipeConfig: [\n            {\n                \"op\": \"Fork\",\n                \"args\": [\"\\\\n\", \"\\\\n\", false]\n            },\n            {\n                \"op\": \"Comment\",\n                \"args\": [\"Testing 123\"]\n            },\n            {\n                \"op\": \"To Base64\",\n                \"args\": [\"A-Za-z0-9+/=\"]\n            }\n        ]\n    },\n    {\n        name: \"Label, Comment: Complex content\",\n        input: ALL_BYTES,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"Label\",\n                args: [\"\"]\n            },\n            {\n                op: \"Comment\",\n                args: [\"\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Compress.mjs",
    "content": "/**\n * Compress tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Bzip2 decompress\",\n        input: \"425a6839314159265359b218ed630000031380400104002a438c00200021a9ea601a10003202185d5ed68ca6442f1e177245385090b218ed63\",\n        expectedOutput: \"The cat sat on the mat.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Space\"]\n            },\n            {\n                \"op\": \"Bzip2 Decompress\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"LZMA compress & decompress\",\n        input: \"The cat sat on the mat.\",\n        // Generated using command `echo -n \"The cat sat on the mat.\" | lzma -z -6 | xxd -p`\n        expectedOutput: \"The cat sat on the mat.\",\n        recipeConfig: [\n            {\n                \"op\": \"LZMA Compress\",\n                \"args\": [\"6\"]\n            },\n            {\n                \"op\": \"LZMA Decompress\",\n                \"args\": []\n            },\n        ],\n    },\n    {\n        name: \"LZMA decompress: binary\",\n        // Generated using command `echo \"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10\" | xxd -r -p | lzma -z -6 | xxd -p`\n        input: \"5d00008000ffffffffffffffff00000052500a84f99bb28021a969d627e03e8a922effffbd160000\",\n        expectedOutput: \"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Space\"]\n            },\n            {\n                \"op\": \"LZMA Decompress\",\n                \"args\": []\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\"Space\", 0]\n            }\n        ],\n    },\n    {\n        name: \"LZMA decompress: string\",\n        // Generated using command `echo -n \"The cat sat on the mat.\" | lzma -z -6 | xxd -p`\n        input: \"5d00008000ffffffffffffffff002a1a08a202b1a4b814b912c94c4152e1641907d3fd8cd903ffff4fec0000\",\n        expectedOutput: \"The cat sat on the mat.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Space\"]\n            },\n            {\n                \"op\": \"LZMA Decompress\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"LZ4 Compress\",\n        input: \"The cat sat on the mat.\",\n        expectedOutput: \"04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000\",\n        recipeConfig: [\n            {\n                \"op\": \"LZ4 Compress\",\n                \"args\": []\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\"None\", 0]\n            }\n        ],\n    },\n    {\n        name: \"LZ4 Decompress\",\n        input: \"04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000\",\n        expectedOutput: \"The cat sat on the mat.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"LZ4 Decompress\",\n                \"args\": []\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ConditionalJump.mjs",
    "content": "/**\n * Conditional Jump tests\n *\n * @author tlwr [toby@toby.codes]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Conditional Jump: Skips 0\",\n        input: [\n            \"should be changed\",\n        ].join(\"\\n\"),\n        expectedOutput: [\n            \"YzJodmRXeGtJR0psSUdOb1lXNW5aV1E9\"\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                op: \"Conditional Jump\",\n                args: [\"match\", false, \"\", 0],\n            },\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n        ],\n    },\n    {\n        name: \"Conditional Jump: Skips 1\",\n        input: [\n            \"should be changed\",\n        ].join(\"\\n\"),\n        // Expecting base32, not base64 output\n        expectedOutput: [\n            \"ONUG65LMMQQGEZJAMNUGC3THMVSA====\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                op: \"Conditional Jump\",\n                args: [\"should\", false, \"skip match\", 10],\n            },\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n            {\n                op: \"Label\", args: [\"skip match\"],\n            },\n            {\n                op: \"To Base32\",\n                args: [\"A-Z2-7=\"],\n            },\n        ],\n    },\n    {\n        name: \"Conditional Jump: Skips backwards\",\n        input: [\n            \"match\",\n        ].join(\"\\n\"),\n        expectedOutput: [\n            \"f7cf556f7f4fc6635db8c314f7a81f2a\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                op: \"Label\",\n                args: [\"back to the beginning\"],\n            },\n            {\n                op: \"Jump\",\n                args: [\"skip replace\"],\n            },\n            {\n                op: \"MD2\",\n                args: [],\n            },\n            {\n                op: \"Label\",\n                args: [\"skip replace\"],\n            },\n            {\n                op: \"Conditional Jump\",\n                args: [\"match\", false, \"back to the beginning\", 10],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ConvertCoordinateFormat.mjs",
    "content": "/**\n * Convert co-ordinate format tests\n *\n * @author j433866\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\n/**\n * TEST CO-ORDINATES\n * DD: 51.504°,-0.126°,\n * DDM: 51° 30.24',-0° 7.56',\n * DMS: 51° 30' 14.4\",-0° 7' 33.6\",\n * Geohash: gcpvj0h0x,\n * MGRS: 30U XC 99455 09790,\n * OSNG: TQ 30163 80005,\n * UTM: 30N 699456 5709791,\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Co-ordinates: From Decimal Degrees to Degrees Minutes Seconds\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"51° 30' 14.4\\\",-0° 7' 33.6\\\",\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Degrees Minutes Seconds\", \"Comma\", \"None\", 1]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From Degrees Minutes Seconds to Decimal Degrees\",\n        input: \"51° 30' 14.4\\\",-0° 7' 33.6\\\",\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Degrees Minutes Seconds\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From Decimal Degrees to Degrees Decimal Minutes\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"51° 30.24',-0° 7.56',\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Degrees Decimal Minutes\", \"Comma\", \"None\", 2]\n            }\n        ]\n    },\n    {\n        name: \"Co-ordinates: From Degrees Decimal Minutes to Decimal Degrees\",\n        input: \"51° 30.24',-0° 7.56',\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Degrees Decimal Minutes\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            }\n        ]\n    },\n    {\n        name: \"Co-ordinates: From Decimal Degrees to Decimal Degrees\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            }\n        ]\n    },\n    {\n        name: \"Co-ordinates: From Decimal Degrees to Geohash\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"gcpvj0h0x,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Geohash\", \"Comma\", \"None\", 9]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From Geohash to Decimal Degrees\",\n        input: \"gcpvj0h0x,\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Geohash\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From Decimal Degrees to MGRS\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"30U XC 99455 09790,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Military Grid Reference System\", \"Comma\", \"None\", 10]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From MGRS to Decimal Degrees\",\n        input: \"30U XC 99455 09790,\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Military Grid Reference System\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            }\n        ]\n    },\n    {\n        name: \"Co-ordinates: From Decimal Degrees to OSNG\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"TQ 30163 80005,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Ordnance Survey National Grid\", \"Comma\", \"None\", 10]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From OSNG to Decimal Degrees\",\n        input: \"TQ 30163 80005,\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Ordnance Survey National Grid\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From Decimal Degrees to UTM\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"30 N 699456 5709791,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Universal Transverse Mercator\", \"Comma\", \"None\", 0]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: From UTM to Decimal Degrees\",\n        input: \"30 N 699456 5709791,\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Universal Transverse Mercator\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: Directions in input, not output\",\n        input: \"N51.504°,W0.126°,\",\n        expectedOutput: \"51.504°,-0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"None\", 3]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: Directions in input and output\",\n        input: \"N51.504°,W0.126°,\",\n        expectedOutput: \"N 51.504°,W 0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"Before\", 3]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: Directions not in input, in output\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"N 51.504°,W 0.126°,\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Decimal Degrees\", \"Comma\", \"Before\", 3]\n            },\n        ],\n    },\n    {\n        name: \"Co-ordinates: Directions not in input, in converted output\",\n        input: \"51.504°,-0.126°,\",\n        expectedOutput: \"N 51° 30' 14.4\\\",W 0° 7' 33.6\\\",\",\n        recipeConfig: [\n            {\n                op: \"Convert co-ordinate format\",\n                args: [\"Decimal Degrees\", \"Comma\", \"Degrees Minutes Seconds\", \"Comma\", \"Before\", 3]\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ConvertLeetSpeak.mjs",
    "content": "/**\n * @author bartblaze []\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Convert to Leet Speak: basic text\",\n        input: \"leet\",\n        expectedOutput: \"l337\",\n        recipeConfig: [\n            {\n                op: \"Convert Leet Speak\",\n                args: [\"To Leet Speak\"]\n            }\n        ]\n    },\n    {\n        name: \"Convert from Leet Speak: basic leet\",\n        input: \"l337\",\n        expectedOutput: \"leet\",\n        recipeConfig: [\n            {\n                op: \"Convert Leet Speak\",\n                args: [\"From Leet Speak\"]\n            }\n        ]\n    },\n    {\n        name: \"Convert to Leet Speak: basic text, keep case\",\n        input: \"HELLO\",\n        expectedOutput: \"H3LL0\",\n        recipeConfig: [\n            {\n                op: \"Convert Leet Speak\",\n                args: [\"To Leet Speak\"]\n            }\n        ]\n    },\n    {\n        name: \"Convert from Leet Speak: basic leet, keep case\",\n        input: \"H3LL0\",\n        expectedOutput: \"HeLLo\",\n        recipeConfig: [\n            {\n                op: \"Convert Leet Speak\",\n                args: [\"From Leet Speak\"]\n            }\n        ]\n    }\n]);\n\n"
  },
  {
    "path": "tests/operations/tests/ConvertToNATOAlphabet.mjs",
    "content": "/**\n * @author MarvinJWendt [git@marvinjwendt.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Convert to NATO alphabet: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Convert to NATO alphabet\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Convert to NATO alphabet: full alphabet with numbers\",\n        input: \"abcdefghijklmnopqrstuvwxyz0123456789,/.\",\n        expectedOutput: \"Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine Comma Fraction bar Full stop \",\n        recipeConfig: [\n            {\n                op: \"Convert to NATO alphabet\",\n                args: []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Crypt.mjs",
    "content": "/**\n * Crypt tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    /**\n     * Ciphers\n     *\n     * The following expectedOutputs were generated using the following command format:\n     * > openssl enc -aes-128-cbc -in test.txt -out test.enc -K \"00112233445566778899aabbccddeeff\" -iv \"00112233445566778899aabbccddeeff\"\n     * > xxd -p test.enc | tr -d '\\n' | xclip -selection clipboard\n     *\n     * All random data blocks (binary input, keys and IVs) were generated from /dev/urandom using dd:\n     * > dd if=/dev/urandom of=key.txt bs=16 count=1\n     *\n     *\n     * The following is a Python script used to generate the AES-GCM tests.\n     * It uses PyCryptodome (https://www.pycryptodome.org) to handle the AES encryption and decryption.\n     *\n     * from Crypto.Cipher import AES\n     * import binascii\n     *\n     * input_data = \"0123456789ABCDEF\"\n     * key = binascii.unhexlify(\"00112233445566778899aabbccddeeff\")\n     * iv = binascii.unhexlify(\"ffeeddccbbaa99887766554433221100\")\n     * aad = b'additional data'\n     *\n     * cipher = AES.new(key, AES.MODE_GCM, nonce=iv)\n     * cipher.update(aad)\n     * cipher_text, tag = cipher.encrypt_and_digest(binascii.unhexlify(input_data))\n     *\n     * cipher = AES.new(key, AES.MODE_GCM, nonce=iv)\n     * cipher.update(aad)\n     * decrypted = cipher.decrypt_and_verify(cipher_text, tag)\n     *\n     * key = binascii.hexlify(key).decode(\"UTF-8\")\n     * iv = binascii.hexlify(iv).decode(\"UTF-8\")\n     * cipher_text = binascii.hexlify(cipher_text).decode(\"UTF-8\")\n     * tag = binascii.hexlify(tag).decode(\"UTF-8\")\n     * decrypted = binascii.hexlify(decrypted).decode(\"UTF-8\")\n     *\n     * print(\"Key: {}\\nIV : {}\\nInput data: {}\\nAAD: {}\\n\\nEncrypted ciphertext: {}\\nGCM tag: {}\\n\\nDecrypted plaintext : {}\".format(key, iv, input_data, aad, cipher_text, tag, decrypted))\n     *\n     *\n     * Outputs:\n     * Key: 00112233445566778899aabbccddeeff\n     * IV : ffeeddccbbaa99887766554433221100\n     * Input data: 0123456789ABCDEF\n     *\n     * Encrypted ciphertext: 8feeafedfdb2f6f9\n     * GCM tag: 654ef4957c6e2b0cc6501d8f9bcde032\n     *\n     * Decrypted plaintext : 0123456789abcdef\n     */\n    {\n        name: \"AES Encrypt: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes\n\nThe following algorithms will be used based on the size of the key:\n  16 bytes = AES-128\n  24 bytes = AES-192\n  32 bytes = AES-256`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"CBC\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CBC with IV0, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"2ef6c3fdb1314b5c2c326a2087fe1a82d5e73bf605ec8431d73e847187fc1c8fbbe969c177df1ecdf8c13f2f505f9498\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    \"CBC\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CTR with IV0, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"a98c9e8e3b7c894384d740e4f0f4ed0be2bbb1e0e13a255812c3c6b0a629e4ad759c075b2469c6f4fb2c0cf9\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    \"CTR\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CBC with IV1, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"4fa077d50cc71a57393e7b542c4e3aea0fb75383b97083f2f568ffc13c0e7a47502ec6d9f25744a061a3a5e55fe95e8d\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"CBC\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CFB, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"369e1c9e5a85b0520f3e61eecc37759246ad0a02cae7a99a3d250ae39cad4743385375cf63720d52ae8cdfb9\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"CFB\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-OFB, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"369e1c9e5a85b0520f3e61eecc37759288cb378c5fa9c675bd6c4ede0ae6a925eaebc8e0a6162d2a000ddc0f\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"OFB\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CTR, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"369e1c9e5a85b0520f3e61eecc37759206f6f1ba63527af96fae3b15a921844df2e542902a4f0525dbb4146b\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"CTR\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-ECB, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"2ef6c3fdb1314b5c2c326a2087fe1a8238c5a5db7dff38f6f4eb75b2e55cab3d8d6113eb8d3517223b4545fcdb4c5a48\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"ECB\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-GCM, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: `d0bcace0fa3a214b0ac3cbb4ac2caaf97b965f172f66d2a4ec6304a15a4072f1b28a6f9b80473f86bfa47b2c\n\nTag: 16a3e732a605cc9ca29108f742ca0743`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"GCM\", \"Raw\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-GCM, ASCII, AAD\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: `daa58faa056c52756aa488aeafbd265b6effcf4eca58220a97b0005b1a9b1e1c9e7a6725d35f5f79b9493de7\n\nTag: 3b5378917f67b0aade9891fc6c291646`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa99887766554433221100\"},\n                    \"GCM\", \"Raw\", \"Hex\",\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CBC, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"bf2ccb148e5df181a46f39764047e24fc94cc46bbe6c8d160fc25a977e4b630883e9e04d3eeae3ccbb2d57a4c22e61909f2b6d7b24940abe95d356ce986294270d0513e0ffe7a9928fa6669e1aaae4379310281dc27c0bb9e254684b2ecd7f5f944c8218f3bc680570399a508dfe4b65\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CBC\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"17211941bb2fa43d54d9fa59072436422a55be7a2be164cf5ec4e50e7a0035094ab684dab8d45a4515ae95c4136ded98898f74d4ecc4ac57ae682a985031ecb7518ddea6c8d816349801aa22ff0b6ac1784d169060efcd9fb77d564477038eb09bb4e1ce\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-OFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"17211941bb2fa43d54d9fa5907243642bfd805201c130c8600566720cf87562011f0872598f1e69cfe541bb864de7ed68201e0a34284157b581984dab3fe2cb0f20cb80d0046740df3e149ec4c92c0e81f2dc439a6f3a05c5ef505eae6308b301c673cfa\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"OFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-CTR, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"17211941bb2fa43d54d9fa5907243642baf08c837003bf24d7b81a911ce41bd31de8a92f6dc6d11135b70c73ea167c3fc4ea78234f58652d25e23245dbcb895bf4165092d0515ae8f14230f8a34b06957f24ba4b24db741490e7edcd6e5310945cc159fc\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CTR\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-GCM, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: `5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e\n\nTag: 70fad2ca19412c20f40fd06918736e56`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-GCM, Binary, AAD\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: `5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e\n\nTag: 61cc4b70809452b0b3e38f913fa0a109`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-128-ECB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"869c057637a58cc3363bcc4bcfa62702abf85dff44300eb9fdcfb9d845772c8acb557c8d540baae2489c6758abef83d81b74239bef87c6c944c1b00ca160882bc15be9a6a3de4e6a50a2eab8b635c634027ed7eae4c1d2f08477c38b7dc24f6915da235bc3051f3a50736b14db8863e4\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"ECB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-192-CBC, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"1aec90cd7f629ef68243881f3e2b793a548cbcdad69631995a6bd0c8aea1e948d8a5f3f2b7e7f9b77da77434c92a6257a9f57e937b883f4400511b990888a0b1d27c0a4b7f298e6f50b563135edc9fa7d8eceb6bc8163e6153a20cf07aa1e705bc5cb3a37b0452b4019cef8000d7c1b7\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CBC\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-192-CFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"fc370a6c013b3c05430fbce810cb97d39cb0a587320a4c1b57d0c0d08e93cb0d1221abba9df09b4b1332ce923b289f92000e6b4f7fbc55dfdab9179081d8c36ef4a0e3d3a49f1564715c5d3e88f8bf6d3dd77944f22f99a03b5535a3cd47bc44d4a9665c\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-192-OFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"fc370a6c013b3c05430fbce810cb97d33605d11b2531c8833bc3e818003bbd7dd58b2a38d10d44d25d11bd96228b264a4d2aad1d0a7af2cfad0e70c1ade305433e95cb0ee693447f6877a59a4be5c070d19afba23ff10caf5ecfa7a9c2877b8df23d61f2\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"OFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-192-CTR, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"fc370a6c013b3c05430fbce810cb97d340525303ae59c5e9b73ad5ff3e65ce3abf00431e0a292d990f732a397de589420827beb1c28623c56972eb2ddf0cf3f82e3c30e155df7f64a530419c28fc51a9091c73df78e73958bee1d1acd8676c9c0f1915ca\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CTR\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-192-GCM, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: `318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b\n\nTag: 86db597d5302595223cadbd990f1309b`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-192-GCM, Binary, AAD\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: `318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b\n\nTag: aeedf3e6ca4201577c0cf3e9ce58159d`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-192-ECB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"56ef533db50a3b33951a76acede52b7d54fbae7fb07da20daa3e2731e5721ee4c13ab15ac80748c14dece982310530ad65480512a4cf70201473fb7bc3480446bc86b1ff9b4517c4c1f656bc236fab1aca276ae5af25f5871b671823f3cb3e426da059dd83a13f125bd6cfe600c331b0\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"ECB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-256-CBC, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"bc60a7613559e23e8a7be8e98a1459003fdb036f33368d8a30156c51464b49472705a4ddae05da96956ce058bb180dd301c5fd58bf6a2ded0d7dd4da85fd5ba43a4297691532bf7f4cd92bfcfd3704faf2f9bd5425049b34433ba90fb85c80646e6cb09ee4e4059e7cd753a2fef8bbad\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CBC\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-256-CFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"5dc73709da5cb0ac914ae4bcb621fd75169eac5ff13a2dde573f6380ff812e8ddb58f0e9afaec1ff0d6d2af0659e10c05b714ec97481a15f4a7aeb4c6ea84112ce897459b54ed9e77a794f023f2bef1901f013cf435432fca5fb59e2be781916247d2334\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-256-OFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"5dc73709da5cb0ac914ae4bcb621fd75b6e1f909b88733f784b1df8a52dc200440a1076415d009a7c12cac1e8ab76bdc290e6634cd5bf8a416fda8dcfd7910e55fe9d1148cd85d7a59adad39ab089e111d8f8da246e2e874cf5d9ab7552af6308320a5ab\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"OFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-256-CTR, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"5dc73709da5cb0ac914ae4bcb621fd7591356d4169898c986a90b193f4d1f0d5cba1d10b2bfc5aee8a48dce9dba174cecf56f92dddf7eb306d78360000eea7bcb50f696d84a3757a822800ed68f9edf118dc61406bacf64f022717d8cb6010049bf75d7e\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CTR\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-256-GCM, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: `1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f\n\nTag: 821b1e5f32dad052e502775a523d957a`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-256-GCM, Binary, AAD\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: `1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f\n\nTag: a8f04c4d93bbef82bef61a103371aef9`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Encrypt: AES-256-ECB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"7e8521ba3f356ef692a51841807e141464aadc07bbc0ef2b628b8745bae356d245682a220688afca7be987b60cb120681ed42680ee93a67065619a3beaac11111a6cd88a6afa9e367722cb57df343f8548f2d691b295184da4ed5f3b763aaa8558502cb348ab58e81986337096e90caa\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"ECB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Encrypt: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes\n\nDES uses a key length of 8 bytes (64 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Encrypt: DES-CBC, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"6500defb824b0eb8ccbf1fa9689c6f5bcc65247d93ecb0e573232824bca82dd41e2361f8fd82ef187de9f3b74f7ba3ca2b4e735f3ca6304fb8dd1675933c576424b1ea72b3219bdab62fce56d49c820d5ac02a4702a6d688e90b0933de97da21e4829e5cf85caae8\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Encrypt: DES-CFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"09015087e15b09374bc9edba80ce41e6809e332fc1e988858749fb2f4ebbd6483a6fce01a43271280c07c90e13d517729acac45beef7d088339eb7e084bbbb7459fc8bb592d2ca76b90066dc79b1fbc5e016208e1d02c6e48ab675530f8040e53e1a138b\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"CFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Encrypt: DES-OFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"09015087e15b09374d8879bac14dbad851dd08fb131353a8c510acc4570e97720dd159465f1c7da3cac4a50521e1c1ab87e8cf5b0aa0c1d2eaa8a1ed914a26c13b2b0a76a368f08812fc7fa4b7c047f27df0c35e5f53b8a20e2ffc10e55d388cae8070db\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"OFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        // play.golang.org/p/4Qm2hfLGsqc\n        name: \"DES Encrypt: DES-CTR, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"09015087e15b0937c462fd5974af0c4b5880de136a5680453c99f4500628cbeca769623515d836985110b93eacfea7fa4a7b2b3cb4f67acbb5f7e8ddb5a5d445da74bf6572b0a874befa3888c81110776388e400afd8dc908dcc0c018c7753355f8a1c9f\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"CTR\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Encrypt: DES-ECB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"8dea4c6a35d5f6a419232159a0b039798d0a0b20fd1e559b1d04f8eb1120e8bca6ed5b3a4bc2b23d3b62312e6085d9e837677569fe79a65eba7cb4a2969e099fc1bd649e9c8aeb2c4c519e085db6974819257c20fde70acabc976308cc41635038c91acf5eefff1e\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"ECB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Encrypt: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes\n\nTriple DES uses a key length of 24 bytes (192 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Encrypt: DES-EDE3-CBC, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"f826c9116ea932eb7027a810b5ce21109c4ef2563c9f3ba5e2518f72484e88f8d3f6ff3f334f64bb6bb9ff91b70f6f29c037b10dee5fe16d7f0f41c9a7ecdd83f113a1dd66ab70783ee458c2366bf5fbc016f7c168c43c11d607692a3280e3750a6154a86b62c48d\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Encrypt: DES-EDE3-CFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"874d32cd7bdae52c3690875e265a2fac7ced685e5ec4436a6bb5a5c18be185f4526683a5bc7ae86f00523034fb725ab4c8285a6967ccca1b76f6331718c26e12ea67fc924071f81ce0035a9dd31705bcd6467991cae5504d70424e6339459db5b33cbc8a\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"CFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Encrypt: DES-EDE3-OFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"874d32cd7bdae52c8f61672860f715d14819c0270320a8ad71083b38bd8954bbada3c77af641590b00a678524d748668fe3dfa83f71835c411cdbdd8e73a70656324b7faaba16e1d8dba260d8f965fe7a91110134c19076f1eeb46393038c22c559fe490\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"OFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        // play.golang.org/p/RElT6pVeNz2\n        name: \"Triple DES Encrypt: DES-EDE3-CTR, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"874d32cd7bdae52cd8630d3ab2bf373e7110e13713caa6a8bfed9d9dd802d0ebe93128ac0d0f05abcc56237b75fb69207dba11e68ddc4b0118a4c75e7248bbd80aaba4dd4436642546ec6ca7fa7526f3b0018ed5194c409dc2c1484530b968af554984f3\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"CTR\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Encrypt: DES-EDE3-ECB Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"aa81f23d1b3abebd68ac560e051a711c2923843beecddb0f7fe4113bd1874e73cccf3a2a494bb011e154ca2737b4d0eb5978a10316361074ed368d85d5aff5c8555ea101b0a468e58780a74c7830c561674c183c972a2b48931adf789cb16df304e169500f8c95ad\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"ECB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes\n\nThe following algorithms will be used based on the size of the key:\n  16 bytes = AES-128\n  24 bytes = AES-192\n  32 bytes = AES-256`,\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"CBC\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CBC with IV0, ASCII\",\n        input: \"2ef6c3fdb1314b5c2c326a2087fe1a82d5e73bf605ec8431d73e847187fc1c8fbbe969c177df1ecdf8c13f2f505f9498\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    \"CBC\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CTR with IV0, ASCII\",\n        input: \"a98c9e8e3b7c894384d740e4f0f4ed0be2bbb1e0e13a255812c3c6b0a629e4ad759c075b2469c6f4fb2c0cf9\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    \"CTR\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CBC with IV, ASCII\",\n        input: \"4fa077d50cc71a57393e7b542c4e3aea0fb75383b97083f2f568ffc13c0e7a47502ec6d9f25744a061a3a5e55fe95e8d\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"CBC\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CFB, ASCII\",\n        input: \"369e1c9e5a85b0520f3e61eecc37759246ad0a02cae7a99a3d250ae39cad4743385375cf63720d52ae8cdfb9\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"CFB\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-OFB, ASCII\",\n        input: \"369e1c9e5a85b0520f3e61eecc37759288cb378c5fa9c675bd6c4ede0ae6a925eaebc8e0a6162d2a000ddc0f\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"OFB\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CTR, ASCII\",\n        input: \"369e1c9e5a85b0520f3e61eecc37759206f6f1ba63527af96fae3b15a921844df2e542902a4f0525dbb4146b\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    \"CTR\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-ECB, ASCII\",\n        input: \"2ef6c3fdb1314b5c2c326a2087fe1a8238c5a5db7dff38f6f4eb75b2e55cab3d8d6113eb8d3517223b4545fcdb4c5a48\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"ECB\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-GCM, ASCII\",\n        input: \"d0bcace0fa3a214b0ac3cbb4ac2caaf97b965f172f66d2a4ec6304a15a4072f1b28a6f9b80473f86bfa47b2c\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"GCM\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"16a3e732a605cc9ca29108f742ca0743\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-GCM, ASCII, AAD\",\n        input: \"daa58faa056c52756aa488aeafbd265b6effcf4eca58220a97b0005b1a9b1e1c9e7a6725d35f5f79b9493de7\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\"},\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa99887766554433221100\"},\n                    \"GCM\", \"Hex\", \"Raw\",\n                    {\"option\": \"Hex\", \"string\": \"3b5378917f67b0aade9891fc6c291646\"},\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CBC, Binary\",\n        input: \"bf2ccb148e5df181a46f39764047e24fc94cc46bbe6c8d160fc25a977e4b630883e9e04d3eeae3ccbb2d57a4c22e61909f2b6d7b24940abe95d356ce986294270d0513e0ffe7a9928fa6669e1aaae4379310281dc27c0bb9e254684b2ecd7f5f944c8218f3bc680570399a508dfe4b65\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CBC\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CFB, Binary\",\n        input: \"17211941bb2fa43d54d9fa59072436422a55be7a2be164cf5ec4e50e7a0035094ab684dab8d45a4515ae95c4136ded98898f74d4ecc4ac57ae682a985031ecb7518ddea6c8d816349801aa22ff0b6ac1784d169060efcd9fb77d564477038eb09bb4e1ce\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-OFB, Binary\",\n        input: \"17211941bb2fa43d54d9fa5907243642bfd805201c130c8600566720cf87562011f0872598f1e69cfe541bb864de7ed68201e0a34284157b581984dab3fe2cb0f20cb80d0046740df3e149ec4c92c0e81f2dc439a6f3a05c5ef505eae6308b301c673cfa\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"OFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-CTR, Binary\",\n        input: \"17211941bb2fa43d54d9fa5907243642baf08c837003bf24d7b81a911ce41bd31de8a92f6dc6d11135b70c73ea167c3fc4ea78234f58652d25e23245dbcb895bf4165092d0515ae8f14230f8a34b06957f24ba4b24db741490e7edcd6e5310945cc159fc\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CTR\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-GCM, Binary\",\n        input: \"5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"70fad2ca19412c20f40fd06918736e56\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-GCM, Binary, AAD\",\n        input: \"5a29debb5c5f38cdf8aee421bd94dbbf3399947faddf205f88b3ad8ecb0c51214ec0e28bf78942dfa212d7eb15259bbdcac677b4c05f473eeb9331d74f31d441d97d56eb5c73b586342d72128ca528813543dc0fc7eddb7477172cc9194c18b2e1383e4e\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"61cc4b70809452b0b3e38f913fa0a109\"},\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-128-ECB, Binary\",\n        input: \"869c057637a58cc3363bcc4bcfa62702abf85dff44300eb9fdcfb9d845772c8acb557c8d540baae2489c6758abef83d81b74239bef87c6c944c1b00ca160882bc15be9a6a3de4e6a50a2eab8b635c634027ed7eae4c1d2f08477c38b7dc24f6915da235bc3051f3a50736b14db8863e4\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"51e201d463698ef5f717f71f5b4712af\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"ECB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-192-CBC, Binary\",\n        input: \"1aec90cd7f629ef68243881f3e2b793a548cbcdad69631995a6bd0c8aea1e948d8a5f3f2b7e7f9b77da77434c92a6257a9f57e937b883f4400511b990888a0b1d27c0a4b7f298e6f50b563135edc9fa7d8eceb6bc8163e6153a20cf07aa1e705bc5cb3a37b0452b4019cef8000d7c1b7\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CBC\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-192-CFB, Binary\",\n        input: \"fc370a6c013b3c05430fbce810cb97d39cb0a587320a4c1b57d0c0d08e93cb0d1221abba9df09b4b1332ce923b289f92000e6b4f7fbc55dfdab9179081d8c36ef4a0e3d3a49f1564715c5d3e88f8bf6d3dd77944f22f99a03b5535a3cd47bc44d4a9665c\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-192-OFB, Binary\",\n        input: \"fc370a6c013b3c05430fbce810cb97d33605d11b2531c8833bc3e818003bbd7dd58b2a38d10d44d25d11bd96228b264a4d2aad1d0a7af2cfad0e70c1ade305433e95cb0ee693447f6877a59a4be5c070d19afba23ff10caf5ecfa7a9c2877b8df23d61f2\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"OFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-192-CTR, Binary\",\n        input: \"fc370a6c013b3c05430fbce810cb97d340525303ae59c5e9b73ad5ff3e65ce3abf00431e0a292d990f732a397de589420827beb1c28623c56972eb2ddf0cf3f82e3c30e155df7f64a530419c28fc51a9091c73df78e73958bee1d1acd8676c9c0f1915ca\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CTR\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-192-GCM, Binary\",\n        input: \"318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"86db597d5302595223cadbd990f1309b\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-192-GCM, Binary, AAD\",\n        input: \"318b479d919d506f0cd904f2676fab263a7921b6d7e0514f36e03ae2333b77fa66ef5600babcb2ee9718aeb71fc357412343c1f2cb351d8715bb0aedae4a6468124f9c4aaf6a721b306beddbe63a978bec8baeeba4b663be33ee5bc982746bd4aed1c38b\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"aeedf3e6ca4201577c0cf3e9ce58159d\"},\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-192-ECB, Binary\",\n        input: \"56ef533db50a3b33951a76acede52b7d54fbae7fb07da20daa3e2731e5721ee4c13ab15ac80748c14dece982310530ad65480512a4cf70201473fb7bc3480446bc86b1ff9b4517c4c1f656bc236fab1aca276ae5af25f5871b671823f3cb3e426da059dd83a13f125bd6cfe600c331b0\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"6801ed503c9d96ee5f9d78b07ab1b295dba3c2adf81c7816\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"ECB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-256-CBC, Binary\",\n        input: \"bc60a7613559e23e8a7be8e98a1459003fdb036f33368d8a30156c51464b49472705a4ddae05da96956ce058bb180dd301c5fd58bf6a2ded0d7dd4da85fd5ba43a4297691532bf7f4cd92bfcfd3704faf2f9bd5425049b34433ba90fb85c80646e6cb09ee4e4059e7cd753a2fef8bbad\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CBC\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-256-CFB, Binary\",\n        input: \"5dc73709da5cb0ac914ae4bcb621fd75169eac5ff13a2dde573f6380ff812e8ddb58f0e9afaec1ff0d6d2af0659e10c05b714ec97481a15f4a7aeb4c6ea84112ce897459b54ed9e77a794f023f2bef1901f013cf435432fca5fb59e2be781916247d2334\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-256-OFB, Binary\",\n        input: \"5dc73709da5cb0ac914ae4bcb621fd75b6e1f909b88733f784b1df8a52dc200440a1076415d009a7c12cac1e8ab76bdc290e6634cd5bf8a416fda8dcfd7910e55fe9d1148cd85d7a59adad39ab089e111d8f8da246e2e874cf5d9ab7552af6308320a5ab\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"OFB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-256-CTR, Binary\",\n        input: \"5dc73709da5cb0ac914ae4bcb621fd7591356d4169898c986a90b193f4d1f0d5cba1d10b2bfc5aee8a48dce9dba174cecf56f92dddf7eb306d78360000eea7bcb50f696d84a3757a822800ed68f9edf118dc61406bacf64f022717d8cb6010049bf75d7e\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"CTR\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-256-GCM, Binary\",\n        input: \"1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"821b1e5f32dad052e502775a523d957a\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-256-GCM, Binary, AAD\",\n        input: \"1287f188ad4d7ab0d9ff69b3c29cb11f861389532d8cb9337181da2e8cfc74a84927e8c0dd7a28a32fd485afe694259a63c199b199b95edd87c7aa95329feac340f2b78b72956a85f367044d821766b1b7135815571df44900695f1518cf3ae38ecb650f\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"GCM\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"a8f04c4d93bbef82bef61a103371aef9\"},\n                    {\"option\": \"UTF8\", \"string\": \"additional data\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"AES Decrypt: AES-256-ECB, Binary\",\n        input: \"7e8521ba3f356ef692a51841807e141464aadc07bbc0ef2b628b8745bae356d245682a220688afca7be987b60cb120681ed42680ee93a67065619a3beaac11111a6cd88a6afa9e367722cb57df343f8548f2d691b295184da4ed5f3b763aaa8558502cb348ab58e81986337096e90caa\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"AES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"2d767f6e9333d1c77581946e160b2b7368c2cdd5e2b80f04ca09d64e02afbfe1\"},\n                    {\"option\": \"Hex\", \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"},\n                    \"ECB\", \"Hex\", \"Hex\",\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"}\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Decrypt: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes\n\nDES uses a key length of 8 bytes (64 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Decrypt: DES-CBC, Binary\",\n        input: \"6500defb824b0eb8ccbf1fa9689c6f5bcc65247d93ecb0e573232824bca82dd41e2361f8fd82ef187de9f3b74f7ba3ca2b4e735f3ca6304fb8dd1675933c576424b1ea72b3219bdab62fce56d49c820d5ac02a4702a6d688e90b0933de97da21e4829e5cf85caae8\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Decrypt: DES-CFB, Binary\",\n        input: \"09015087e15b09374bc9edba80ce41e6809e332fc1e988858749fb2f4ebbd6483a6fce01a43271280c07c90e13d517729acac45beef7d088339eb7e084bbbb7459fc8bb592d2ca76b90066dc79b1fbc5e016208e1d02c6e48ab675530f8040e53e1a138b\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"CFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Decrypt: DES-OFB, Binary\",\n        input: \"09015087e15b09374d8879bac14dbad851dd08fb131353a8c510acc4570e97720dd159465f1c7da3cac4a50521e1c1ab87e8cf5b0aa0c1d2eaa8a1ed914a26c13b2b0a76a368f08812fc7fa4b7c047f27df0c35e5f53b8a20e2ffc10e55d388cae8070db\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"OFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        // play.golang.org/p/FpvqncmPk7R\n        name: \"DES Decrypt: DES-CTR, Binary\",\n        input: \"09015087e15b0937ab0ae5a84d66e520893690a6ea066382bf1330e8876cb3aa82ccc634f8f0d458bbe0257df6f4637cdac89f311168ba91208a21ba4bdd13c4b1a92cb93b33364b5b94a5d3d7fba68f6eed5807d9f5afeb7fbffcd94792131d264004ae\",\n        expectedOutput: \"7a0e643132750e96b76dc9efa7810bea2b8feaa5b97887e44f96c0e6d506cc4dd4665683c6f63139221f8d887fd0a05b39741f8a67d87d6ac6f8dc6b668bd3e4a97b8bd3a19eafd5cdf50c3e1b3f17d61087d0b67cf6db31fec338b75f5954942c852829\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"CTR\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"DES Decrypt: DES-ECB, Binary\",\n        input: \"8dea4c6a35d5f6a419232159a0b039798d0a0b20fd1e559b1d04f8eb1120e8bca6ed5b3a4bc2b23d3b62312e6085d9e837677569fe79a65eba7cb4a2969e099fc1bd649e9c8aeb2c4c519e085db6974819257c20fde70acabc976308cc41635038c91acf5eefff1e\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"58345efb0a64e87e\"},\n                    {\"option\": \"Hex\", \"string\": \"533ed1378bfd929e\"},\n                    \"ECB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Decrypt: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes\n\nTriple DES uses a key length of 24 bytes (192 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Decrypt: DES-EDE3-CBC, Binary\",\n        input: \"f826c9116ea932eb7027a810b5ce21109c4ef2563c9f3ba5e2518f72484e88f8d3f6ff3f334f64bb6bb9ff91b70f6f29c037b10dee5fe16d7f0f41c9a7ecdd83f113a1dd66ab70783ee458c2366bf5fbc016f7c168c43c11d607692a3280e3750a6154a86b62c48d\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"CBC\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Decrypt: DES-EDE3-CFB, Binary\",\n        input: \"874d32cd7bdae52c3690875e265a2fac7ced685e5ec4436a6bb5a5c18be185f4526683a5bc7ae86f00523034fb725ab4c8285a6967ccca1b76f6331718c26e12ea67fc924071f81ce0035a9dd31705bcd6467991cae5504d70424e6339459db5b33cbc8a\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"CFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Decrypt: DES-EDE3-OFB, Binary\",\n        input: \"874d32cd7bdae52c8f61672860f715d14819c0270320a8ad71083b38bd8954bbada3c77af641590b00a678524d748668fe3dfa83f71835c411cdbdd8e73a70656324b7faaba16e1d8dba260d8f965fe7a91110134c19076f1eeb46393038c22c559fe490\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"OFB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        // play.golang.org/p/iBacN9kX_RO\n        name: \"Triple DES Decrypt: DES-EDE3-CTR, Binary\",\n        input: \"874d32cd7bdae52c254687e2d7e7093b077af2ec70878f99315f52a21ded5fb10c80a47e6271384335ac47376c758f675484fd7b8be9568aaec643f0d15cffdf3fe54ef3a1b2da50d5d8c7994d7a4a94e0a13a4d437443f0f1f39e93dd13ff06a80c66e4\",\n        expectedOutput: \"7a0e643132750e9625205bc6fb10dc848c53b7cb5a654d1242aecb6191ad3b5114727e5044a0ee11311575873c54829a80f9471ac473a0bbe5e791a23be75062f7e8f2210d998f9fbbaf3a5bb3dacd494d42d82950e3ab273f821eb979168315a80ad20f\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"CTR\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Triple DES Decrypt: DES-EDE3-ECB, Binary\",\n        input: \"aa81f23d1b3abebd68ac560e051a711c2923843beecddb0f7fe4113bd1874e73cccf3a2a494bb011e154ca2737b4d0eb5978a10316361074ed368d85d5aff5c8555ea101b0a468e58780a74c7830c561674c183c972a2b48931adf789cb16df304e169500f8c95ad\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Triple DES Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"190da55fb54b9e7dd6de05f43bf3347ef203cd34a5829b23\"},\n                    {\"option\": \"Hex\", \"string\": \"14f67ac044a84da6\"},\n                    \"ECB\", \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"RC2 Encrypt: no key\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"d3644d898b51a544f690b506c3fd0caeb7a1e6097f7ea28f69b909a4d8805c9a05f4cade8b281d3f044fa069374efb90e94723622c86afc17caee394ffbee0abe627de299208460eb981c9d56f9df885091c6c89e2ee173264b2820b8e67675214e6545a05dc0d3f\",\n        recipeConfig: [\n            {\n                \"op\": \"RC2 Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"RC2 Encrypt: RC2-CBC, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"d25e5bc6c9311ef196d6f21cc4b0274b29fcca366aba5256406e02bf4ae628398f84e7d72ad92025ede76df4752d1510fe9c3492efb1dcf0be2cd41d619e10b9dd5a2304c2efbd3598d3b87f1a21f326d45e65537563436cfb6e4a41ec3733182ddc058f96f74a6c\",\n        recipeConfig: [\n            {\n                \"op\": \"RC2 Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"eb970554bb213430f4bb4e5988a6a218\"},\n                    {\"option\": \"Hex\", \"string\": \"ae817c784a097e0c\"},\n                    \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"RC2 Encrypt: RC2-ECB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"a160bf23b2a85eaa43d26753e51aaa899f162ec0da7280fffd41b705c5309c7fef2bbb56bf261cab4eadd3a5c69e0a67d45e426d1097187cc9a959b4d979a9d40df26f3dc8d030453fe27701438b78d3ce044330b4b5dca7832537ecf40b914f1b1dc16d4e6d7229\",\n        recipeConfig: [\n            {\n                \"op\": \"RC2 Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"eb970554bb213430f4bb4e5988a6a218\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"RC2 Decrypt: no key\",\n        input: \"d3644d898b51a544f690b506c3fd0caeb7a1e6097f7ea28f69b909a4d8805c9a05f4cade8b281d3f044fa069374efb90e94723622c86afc17caee394ffbee0abe627de299208460eb981c9d56f9df885091c6c89e2ee173264b2820b8e67675214e6545a05dc0d3f\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"RC2 Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"RC2 Decrypt: RC2-CBC, Binary\",\n        input: \"d25e5bc6c9311ef196d6f21cc4b0274b29fcca366aba5256406e02bf4ae628398f84e7d72ad92025ede76df4752d1510fe9c3492efb1dcf0be2cd41d619e10b9dd5a2304c2efbd3598d3b87f1a21f326d45e65537563436cfb6e4a41ec3733182ddc058f96f74a6c\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"RC2 Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"eb970554bb213430f4bb4e5988a6a218\"},\n                    {\"option\": \"Hex\", \"string\": \"ae817c784a097e0c\"},\n                    \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"RC2 Decrypt: RC2-ECB, Binary\",\n        input: \"a160bf23b2a85eaa43d26753e51aaa899f162ec0da7280fffd41b705c5309c7fef2bbb56bf261cab4eadd3a5c69e0a67d45e426d1097187cc9a959b4d979a9d40df26f3dc8d030453fe27701438b78d3ce044330b4b5dca7832537ecf40b914f1b1dc16d4e6d7229\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"RC2 Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"eb970554bb213430f4bb4e5988a6a218\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Hex\", \"Hex\"\n                ]\n            }\n        ],\n    },\n    /*\n        The following expectedOutputs are generated with this Python script with pyCryptoDome\n\n        from Crypto.Cipher import Blowfish\n        import binascii\n\n        # Blowfish cipher parameters - key, mode, iv, segment_size, nonce\n        key = binascii.unhexlify(\"0011223344556677\")\n        mode = Blowfish.MODE_CBC\n        kwargs = {}\n        iv = binascii.unhexlify(\"ffeeddccbbaa9988\")\n        if mode in [Blowfish.MODE_CBC, Blowfish.MODE_CFB, Blowfish.MODE_OFB]:\n            kwargs = {\"iv\": iv}\n        if mode == Blowfish.MODE_CFB:\n            kwargs[\"segment_size\"] = 64\n        if mode == Blowfish.MODE_CTR:\n            nonce = binascii.unhexlify(\"0000000000000000\")\n            nonce = nonce[:7]\n            kwargs[\"nonce\"] = nonce\n\n        cipher = Blowfish.new(key, mode, **kwargs)\n\n        # Input data and padding\n        input_data = b\"The quick brown fox jumps over the lazy dog.\"\n        if mode == Blowfish.MODE_ECB or mode == Blowfish.MODE_CBC:\n            padding_len = 8-(len(input_data) & 7)\n            for i in range(padding_len):\n                input_data += bytes([padding_len])\n\n        # Encrypted text\n        cipher_text = cipher.encrypt(input_data)\n        cipher_text = binascii.hexlify(cipher_text).decode(\"UTF-8\")\n\n        print(\"Encrypted: {}\".format(cipher_text))\n    */\n    {\n        name: \"Blowfish Encrypt: ECB, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"f7784137ab1bf51546c0b120bdb7fed4509116e49283b35fab0e4292ac86251a9bf908330e3393815e3356bb26524027\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV\n                    \"ECB\", // Mode\n                    \"Raw\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: ECB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"3d1bf0e87d83782d435a0ca58179ca290184867f52295af5c0fb4dcac7c6c68942906bb421d05925cc7d9cd21532376a0f6ae4c3f008b250381ffa9624f5eb697dbd44de48cf5593ea7dbf5842238474b546ceeb29f6cf327a7d13698786b8d14451f52fb0f5760a\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV\n                    \"ECB\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: ECB, ASCII\",\n        input: \"f7784137ab1bf51546c0b120bdb7fed4509116e49283b35fab0e4292ac86251a9bf908330e3393815e3356bb26524027\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV\n                    \"ECB\", // Mode\n                    \"Hex\", // Input\n                    \"Raw\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: ECB, Binary\",\n        input: \"3d1bf0e87d83782d435a0ca58179ca290184867f52295af5c0fb4dcac7c6c68942906bb421d05925cc7d9cd21532376a0f6ae4c3f008b250381ffa9624f5eb697dbd44de48cf5593ea7dbf5842238474b546ceeb29f6cf327a7d13698786b8d14451f52fb0f5760a\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV\n                    \"ECB\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: CBC, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"398433f39e938286a35fc240521435b6972f3fe96846b54ab9351aa5fa9e10a6a94074e883d1cb36cb9657c817274b60\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CBC\", // Mode\n                    \"Raw\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: CBC, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"3b42c51465896524e66c2fd2404c8c2b4eb26c760671f131c3372d374f48283ca9a5404d3d8aabd2a886c6551393ca41c682580f1c81f16046e3bec7b59247bdfca1d40bf2ad8ede9de99cb44b36658f775999d37776b3b1a085b9530e54ece69e1875e1bdc8cdcf\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CBC\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: CBC, ASCII\",\n        input: \"398433f39e938286a35fc240521435b6972f3fe96846b54ab9351aa5fa9e10a6a94074e883d1cb36cb9657c817274b60\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CBC\", // Mode\n                    \"Hex\", // Input\n                    \"Raw\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: CBC, Binary\",\n        input: \"3b42c51465896524e66c2fd2404c8c2b4eb26c760671f131c3372d374f48283ca9a5404d3d8aabd2a886c6551393ca41c682580f1c81f16046e3bec7b59247bdfca1d40bf2ad8ede9de99cb44b36658f775999d37776b3b1a085b9530e54ece69e1875e1bdc8cdcf\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CBC\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: CFB, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        // pyCryptoDome produces a different value with default settings. This is due to segment_size having\n        // a default value of 8 bits. Setting it to 64 (one full block) will yield the same result.\n        expectedOutput: \"c8ca123592570c1fcb138d4ec08f7af14ad49363245be1ac25029c8ffc508b3217e75faaa5566426180fec8f\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CFB\", // Mode\n                    \"Raw\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: CFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        // see above. pyCryptoDome produces a different value with its default settings\n        expectedOutput: \"e6ac1324d1576beab00e855de3f4ac1f5e3cbf89f4c2a743a5737895067ac5012e5bdb92477e256cc07bf691b58e721179b550e694abb0be7cbdc42586db755bf795f4338f47d356c57453afa6277e46aaeb3405f9744654a477f06c2ad92ede90555759\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CFB\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: CFB, ASCII\",\n        input: \"c8ca123592570c1fcb138d4ec08f7af14ad49363245be1ac25029c8ffc508b3217e75faaa5566426180fec8f\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        // see above. pyCryptoDome produces a different value with its default settings\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CFB\", // Mode\n                    \"Hex\", // Input\n                    \"Raw\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: CFB, Binary\",\n        input: \"e6ac1324d1576beab00e855de3f4ac1f5e3cbf89f4c2a743a5737895067ac5012e5bdb92477e256cc07bf691b58e721179b550e694abb0be7cbdc42586db755bf795f4338f47d356c57453afa6277e46aaeb3405f9744654a477f06c2ad92ede90555759\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        // see above. pyCryptoDome produces a different value with its default settings\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"CFB\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: OFB, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"c8ca123592570c1fffcee88b9823b9450dc9c48e559123c1df1984214212bae7e44114d29dba79683d10cce5\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"OFB\", // Mode\n                    \"Raw\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: OFB, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"e6ac1324d1576bea4ceb5be7691c35e4919f18be06cc2a926025ef0973222e987de7c63cd71ed3b19190ba006931d9cbdf412f5b1ac7155904ca591f693fe11aa996e17866e0de4b2eb7ff5effabf94b0f49ed159202caf72745ac2f024d86f942d83767\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"OFB\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: OFB, ASCII\",\n        input: \"c8ca123592570c1fffcee88b9823b9450dc9c48e559123c1df1984214212bae7e44114d29dba79683d10cce5\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"OFB\", // Mode\n                    \"Hex\", // Input\n                    \"Raw\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: OFB, Binary\",\n        input: \"e6ac1324d1576bea4ceb5be7691c35e4919f18be06cc2a926025ef0973222e987de7c63cd71ed3b19190ba006931d9cbdf412f5b1ac7155904ca591f693fe11aa996e17866e0de4b2eb7ff5effabf94b0f49ed159202caf72745ac2f024d86f942d83767\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"ffeeddccbbaa9988\"}, // IV\n                    \"OFB\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: CTR, ASCII\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"e2a5e0f03ad4877101c7cf83861ad93477adb57acac4bebc315a7bae34b4e6a54e5532db457a3131dcd9dda6\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    // pyCryptoDome only allows the size of the nonce to be [0,7] bytes.\n                    // Internally, it right-pads the nonce to 7 bytes long if it wasn't already 7 bytes,\n                    // and the last (8th) byte is used for counter.\n                    // Therefore a pyCryptoDome nonce of \"aabbccdd\" is equivalent to an IV of \"aabbccdd00000000\" here.\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV (nonce)\n                    \"CTR\", // Mode\n                    \"Raw\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt: CTR, Binary\",\n        input: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        expectedOutput: \"ccc3e1e179d4e084b2e27cef77255595ebfb694a9999b7ef8e661086058472dad7f3e0350fde9be87059ab43d5b800aa08be4c00f3f2e99402fe2702c39e8663dbcbb146700d63432227f1045f116bfd4b65022ca20b70427ddcfd7441cb3c75f4d3fff0\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    // See notes above\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV (nonce)\n                    \"CTR\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: CTR, ASCII\",\n        input: \"e2a5e0f03ad4877101c7cf83861ad93477adb57acac4bebc315a7bae34b4e6a54e5532db457a3131dcd9dda6\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog.\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    // See notes above\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV (nonce)\n                    \"CTR\", // Mode\n                    \"Hex\", // Input\n                    \"Raw\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Decrypt: CTR, Binary\",\n        input: \"ccc3e1e179d4e084b2e27cef77255595ebfb694a9999b7ef8e661086058472dad7f3e0350fde9be87059ab43d5b800aa08be4c00f3f2e99402fe2702c39e8663dbcbb146700d63432227f1045f116bfd4b65022ca20b70427ddcfd7441cb3c75f4d3fff0\",\n        expectedOutput: \"7a0e643132750e96d805d11e9e48e281fa39a41039286423cc1c045e5442b40bf1c3f2822bded3f9c8ef11cb25da64dda9c7ab87c246bd305385150c98f31465c2a6180fe81d31ea289b916504d5a12e1de26cb10adba84a0cb0c86f94bc14bc554f3018\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Decrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0011223344556677\"}, // Key\n                    // See notes above\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV (nonce)\n                    \"CTR\", // Mode\n                    \"Hex\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt with variable key length: CBC, ASCII, 4 bytes\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"823f337a53ecf121aa9ec1b111bd5064d1d7586abbdaaa0c8fd0c6cc43c831c88bf088ee3e07287e3f36cf2e45f9c7e6\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00112233\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV\n                    \"CBC\", // Mode\n                    \"Raw\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Blowfish Encrypt with variable key length: CBC, ASCII, 42 bytes\",\n        input: \"The quick brown fox jumps over the lazy dog.\",\n        expectedOutput: \"19f5a68145b34321cfba72226b0f33922ce44dd6e7869fe328db64faae156471216f12ed2a37fd0bdd7cebf867b3cff0\",\n        recipeConfig: [\n            {\n                \"op\": \"Blowfish Encrypt\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead\"}, // Key\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"}, // IV\n                    \"CBC\", // Mode\n                    \"Raw\", // Input\n                    \"Hex\" // Output\n                ]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/DateTime.mjs",
    "content": "/**\n * DateTime tests.\n *\n * @author bwhitn [brian.m.whitney@outlook.com]\n *\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Filetime to Unix\",\n        input: \"129207366395297693\",\n        expectedOutput: \"1276263039529769300\",\n        recipeConfig: [\n            {\n                op: \"Windows Filetime to UNIX Timestamp\",\n                args: [\"Nanoseconds (ns)\", \"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Unix to Filetime\",\n        input: \"1276263039529769300\",\n        expectedOutput: \"129207366395297693\",\n        recipeConfig: [\n            {\n                op: \"UNIX Timestamp to Windows Filetime\",\n                args: [\"Nanoseconds (ns)\", \"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"DateTime Delta Positive\",\n        input: \"20/02/2024 13:36:00\",\n        expectedOutput: \"20/02/2024 13:37:00\",\n        recipeConfig: [\n            {\n                op: \"DateTime Delta\",\n                args: [\"Standard date and time\", \"DD/MM/YYYY HH:mm:ss\", \"Add\", 0, 0, 1, 0],\n            },\n        ],\n    },\n    {\n        name: \"DateTime Delta Negative\",\n        input: \"20/02/2024 14:37:00\",\n        expectedOutput: \"20/02/2024 13:37:00\",\n        recipeConfig: [\n            {\n                op: \"DateTime Delta\",\n                args: [\"Standard date and time\", \"DD/MM/YYYY HH:mm:ss\", \"Subtract\", 0, 1, 0, 0],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/DefangIP.mjs",
    "content": "/**\n * DefangIP tests.\n *\n * @author h345983745\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Defang IP: Valid IPV4\",\n        input: \"192.168.1.1\",\n        expectedOutput: \"192[.]168[.]1[.]1\",\n        recipeConfig: [\n            {\n                op: \"Defang IP Addresses\",\n                args: [],\n            },\n        ],\n    }, {\n        name: \"Defang IP: Valid IPV6\",\n        input: \"2001:0db8:85a3:0000:0000:8a2e:0370:7343\",\n        expectedOutput: \"2001[:]0db8[:]85a3[:]0000[:]0000[:]8a2e[:]0370[:]7343\",\n        recipeConfig: [\n            {\n                op: \"Defang IP Addresses\",\n                args: [],\n            },\n        ],\n    }, {\n        name: \"Defang IP: Valid IPV6 Shorthand\",\n        input: \"2001:db8:3c4d:15::1a2f:1a2b\",\n        expectedOutput: \"2001[:]db8[:]3c4d[:]15[:][:]1a2f[:]1a2b\",\n        recipeConfig: [\n            {\n                op: \"Defang IP Addresses\",\n                args: [],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/DisassembleARM.mjs",
    "content": "/**\n * Disassemble ARM tests.\n *\n * @author MedjedThomasXM\n *\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    // ==================== ARM32 TESTS ====================\n    {\n        name: \"Disassemble ARM: ARM32 NOP (mov r0, r0)\",\n        input: \"00 00 a0 e1\",\n        expectedMatch: /mov\\s+r0,\\s*r0/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 bx lr\",\n        input: \"1e ff 2f e1\",\n        expectedMatch: /bx\\s+lr/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 push {fp, lr}\",\n        input: \"00 48 2d e9\",\n        expectedMatch: /push\\s+\\{fp,\\s*lr\\}/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 add fp, sp, #4\",\n        input: \"04 b0 8d e2\",\n        expectedMatch: /add\\s+fp,\\s*sp/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 ldr r0, [r1]\",\n        input: \"00 00 91 e5\",\n        expectedMatch: /ldr\\s+r0,\\s*\\[r1\\]/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 str r0, [r1]\",\n        input: \"00 00 81 e5\",\n        expectedMatch: /str\\s+r0,\\s*\\[r1\\]/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 bl (branch link)\",\n        input: \"00 00 00 eb\",\n        expectedMatch: /bl\\s+/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 mul r0, r1, r2\",\n        input: \"91 02 00 e0\",\n        expectedMatch: /mul\\s+r0,\\s*r1,\\s*r2/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n\n    // ==================== ARM32 THUMB TESTS ====================\n    {\n        name: \"Disassemble ARM: Thumb mov r0, r0\",\n        input: \"00 46\",\n        expectedMatch: /mov\\s+r0,\\s*r0/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"Thumb\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: Thumb bx lr\",\n        input: \"70 47\",\n        expectedMatch: /bx\\s+lr/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"Thumb\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: Thumb push {r4, lr}\",\n        input: \"10 b5\",\n        expectedMatch: /push\\s+\\{r4,\\s*lr\\}/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"Thumb\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: Thumb pop {r4, pc}\",\n        input: \"10 bd\",\n        expectedMatch: /pop\\s+\\{r4,\\s*pc\\}/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"Thumb\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n\n    // ==================== ARM64 TESTS ====================\n    {\n        name: \"Disassemble ARM: ARM64 ret\",\n        input: \"c0 03 5f d6\",\n        expectedMatch: /ret/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 mov x0, #0\",\n        input: \"00 00 80 d2\",\n        expectedMatch: /mov[z]?\\s+x0,\\s*#0/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 stp x29, x30, [sp, #-16]!\",\n        input: \"fd 7b bf a9\",\n        expectedMatch: /stp\\s+x29,\\s*x30,\\s*\\[sp/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 ldp x29, x30, [sp], #16\",\n        input: \"fd 7b c1 a8\",\n        expectedMatch: /ldp\\s+x29,\\s*x30,\\s*\\[sp\\]/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 add x0, x1, x2\",\n        input: \"20 00 02 8b\",\n        expectedMatch: /add\\s+x0,\\s*x1,\\s*x2/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 sub x0, x1, x2\",\n        input: \"20 00 02 cb\",\n        expectedMatch: /sub\\s+x0,\\s*x1,\\s*x2/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 mul x0, x1, x2\",\n        input: \"20 7c 02 9b\",\n        expectedMatch: /mul\\s+x0,\\s*x1,\\s*x2/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 ldr x0, [x1]\",\n        input: \"20 00 40 f9\",\n        expectedMatch: /ldr\\s+x0,\\s*\\[x1\\]/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 str x0, [x1]\",\n        input: \"20 00 00 f9\",\n        expectedMatch: /str\\s+x0,\\s*\\[x1\\]/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 bl (branch link)\",\n        input: \"00 00 00 94\",\n        expectedMatch: /bl\\s+/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 cbz x0\",\n        input: \"00 00 00 b4\",\n        expectedMatch: /cbz\\s+x0/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 cbnz x0\",\n        input: \"00 00 00 b5\",\n        expectedMatch: /cbnz\\s+x0/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 sub sp, sp, #0x20\",\n        input: \"ff 83 00 d1\",\n        expectedMatch: /sub\\s+sp,\\s*sp/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 add sp, sp, #0x20\",\n        input: \"ff 83 00 91\",\n        expectedMatch: /add\\s+sp,\\s*sp/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n\n    // ==================== MULTI-INSTRUCTION TESTS ====================\n    {\n        name: \"Disassemble ARM: ARM32 multiple instructions\",\n        input: \"00 48 2d e9 04 b0 8d e2 00 00 a0 e1 00 88 bd e8\",\n        expectedMatch: /push.*\\n.*add.*\\n.*mov.*\\n.*pop/s,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM64 function prologue/epilogue\",\n        input: \"fd 7b bf a9 fd 03 00 91 00 00 80 52 fd 7b c1 a8 c0 03 5f d6\",\n        expectedMatch: /stp.*\\n.*mov.*\\n.*mov.*\\n.*ldp.*\\n.*ret/s,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n\n    // ==================== ADDRESS TESTS ====================\n    {\n        name: \"Disassemble ARM: ARM64 with start address 0x1000\",\n        input: \"c0 03 5f d6\",\n        expectedMatch: /0x00001000/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 4096, true, true],\n            },\n        ],\n    },\n    {\n        name: \"Disassemble ARM: ARM32 with start address 0x8000\",\n        input: \"00 00 a0 e1\",\n        expectedMatch: /0x00008000/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Little Endian\", 32768, true, true],\n            },\n        ],\n    },\n\n    // ==================== ENDIANNESS TESTS ====================\n    {\n        name: \"Disassemble ARM: ARM32 Big Endian\",\n        input: \"e1 a0 00 00\",\n        expectedMatch: /mov\\s+r0,\\s*r0/,\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM (32-bit)\", \"ARM\", \"Big Endian\", 0, true, true],\n            },\n        ],\n    },\n\n    // ==================== EDGE CASES ====================\n    {\n        name: \"Disassemble ARM: Empty input\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Disassemble ARM\",\n                args: [\"ARM64 (AArch64)\", \"ARM\", \"Little Endian\", 0, true, true],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/DropNthBytes.mjs",
    "content": "/**\n * @author Oshawk [oshawk@protonmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n/**\n * Drop nth bytes tests\n */\nTestRegister.addTests([\n    {\n        name: \"Drop nth bytes: Nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 0, false],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Nothing (apply to each line)\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 0, true],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Basic single line\",\n        input: \"0123456789\",\n        expectedOutput: \"1235679\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 0, false],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Basic single line (apply to each line)\",\n        input: \"0123456789\",\n        expectedOutput: \"1235679\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 0, true],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Complex single line\",\n        input: \"0123456789\",\n        expectedOutput: \"01234678\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 5, false],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Complex single line (apply to each line)\",\n        input: \"0123456789\",\n        expectedOutput: \"01234678\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 5, true],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Basic multi line\",\n        input: \"01234\\n56789\",\n        expectedOutput: \"123\\n5689\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 0, false],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Basic multi line (apply to each line)\",\n        input: \"01234\\n56789\",\n        expectedOutput: \"123\\n678\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 0, true],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Complex multi line\",\n        input: \"01234\\n56789\",\n        expectedOutput: \"012345679\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 5, false],\n            },\n        ],\n    },\n    {\n        name: \"Drop nth bytes: Complex multi line (apply to each line)\",\n        input: \"012345\\n6789ab\",\n        expectedOutput: \"01234\\n6789a\",\n        recipeConfig: [\n            {\n                op: \"Drop nth bytes\",\n                args: [4, 5, true],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ECDSA.mjs",
    "content": "/**\n * ECDSA tests.\n *\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport {ALL_BYTES, ASCII_TEXT, UTF8_TEXT} from \"../../samples/Ciphers.mjs\";\n\nconst SOME_HEX_BYTES = \"cdb23f958e018418621d9e489b7bba0f0c481f604eba2eb1ea35e38f99490cc0\";\nconst SOME_BASE64_BYTES = \"zbI/lY4BhBhiHZ5Im3u6DwxIH2BOui6x6jXjj5lJDMA=\";\n\nconst P256 = {\n    // openssl ecparam -name prime256v1 -genkey -noout -out p256.priv.key\n    privateKeyPkcs1: `-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49\nAwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC\na6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==\n-----END EC PRIVATE KEY-----`,\n    privateKeyPkcs8: `-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw\nYBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC\n6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p\n-----END PRIVATE KEY-----`,\n\n    // openssl ec -in p256.priv.key -pubout -out p256.pub.key\n    publicKey: `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ\ngusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==\n-----END PUBLIC KEY-----`,\n\n    signature: {\n        sha256: {\n            asn1: \"3046022100e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127022100b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d\",\n            p1363: \"e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d\",\n            jws: \"4GkFYIovp9vanihMKnlZ37aPtSel8AOy15df8TUUUSe2uqJTeTM0-Lk-od1iK8YAEk2AkLq9gH7-P3e4syQ4jQ\",\n            json: `{\"r\":\"00e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127\",\"s\":\"00b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d\"}`\n        }\n    }\n};\n\n// openssl pkcs8 -topk8 -in p256.priv.key -out p256.enc-priv.key -v2 des3 -v2prf hmacWithSHA1 -passout pass:Test1234\n/* const PEM_PRIV_P256_ENCRYPTED_PASS = \"Test1234\";\nconst PEM_PRIV_P256_ENCRYPTED = `-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAg+4ckqI9Q9ZAICCAAw\nDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEOnMUW15Hn/ub0OcCCj9lksEgZCk\nkxaK4d430lZHovcA4ZeKTt94QcfjnIHRk65aZt93l17l52pv6n/srs3aRo/n5RV+\nwZ5sTLF0925ZQWJB5cIhzc8KQIvguGCX1znLQJJaRHyYOUXIN77AKEfALKAinBit\n25paDnbXAqGn1CR3UwFWUZZW+c3UEhWhmpghQpS1tIl0KI6IAvnrGIdw2kKIouo=\n-----END ENCRYPTED PRIVATE KEY-----`;*/\n\nconst P384 = {\n    privateKeyPkcs8: `-----BEGIN PRIVATE KEY-----\nMIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAYo22xn2kZjN8MInom\nNDsgD/zhpUwnCYch634jUgO59fN9m2lR5ekaI1XABHz39rihZANiAAQwXoCsPOLv\nNn2STUs/hpL41CQveSL3WUmJ4QdtD7UFCl1mBO6ME0xSUgIQTUNkHt5k9CpOq3x9\nr+LG5+GcisoLn7R54R+bRoGp/p1ZBeuBXoCgthvs+RFoT3OewUmA8oQ=\n-----END PRIVATE KEY-----`,\n    publicKey: `-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMF6ArDzi7zZ9kk1LP4aS+NQkL3ki91lJ\nieEHbQ+1BQpdZgTujBNMUlICEE1DZB7eZPQqTqt8fa/ixufhnIrKC5+0eeEfm0aB\nqf6dWQXrgV6AoLYb7PkRaE9znsFJgPKE\n-----END PUBLIC KEY-----`\n};\n\nconst P521 = {\n    privateKeyPkcs8: `-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAifBaJDqNwOtKgThc\nFU34GzPQ73ubOQg9dnighpVGwA3b/KwCifimCNKDmKnXJaE04mEcxg8yzcFKausF\n5I8o206hgYkDgYYABAGwpkwrBBlZOdx4u9mxqYxJvtzAHaFFAzl21WQVbAjyrqXe\nnFPMkhbFpEEWr1ualPYKQkHe14AX33iU3fQ9MlBkgAAripsPbiKggAaog74cUERo\nqbrUFZwMbptGgovpE6pU93h7A1wb3Vtw9DZQCgiNbwzMbdsft+p2RJ8iSxWEC6Gd\nmw==\n-----END PRIVATE KEY-----`,\n    publicKey: `-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBsKZMKwQZWTnceLvZsamMSb7cwB2h\nRQM5dtVkFWwI8q6l3pxTzJIWxaRBFq9bmpT2CkJB3teAF994lN30PTJQZIAAK4qb\nD24ioIAGqIO+HFBEaKm61BWcDG6bRoKL6ROqVPd4ewNcG91bcPQ2UAoIjW8MzG3b\nH7fqdkSfIksVhAuhnZs=\n-----END PUBLIC KEY-----`\n};\n\nconst PEM_PPRIV_RSA512 = `-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL\nNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF\nF7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL\nWQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p\n6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf\nRWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw\nNSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o=\n-----END RSA PRIVATE KEY-----`;\nconst PEM_PUB_RSA512 = `-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM\nYKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==\n-----END PUBLIC KEY-----`;\n\nTestRegister.addTests([\n    {\n        name: \"ECDSA Sign/Verify: P-256 with MD5\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"MD5\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"MD5\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA1\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-1\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-1\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA256\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-256\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA384\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-384\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-384\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA512\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-512\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-512\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify:: Using a private key in PKCS#8 format works\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs8, \"SHA-256\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-384 with SHA384\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P384.privateKeyPkcs8, \"SHA-384\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-384\", P384.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-521 with SHA512\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P521.privateKeyPkcs8, \"SHA-512\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-512\", P521.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n\n    // ECDSA Sign\n    {\n        name: \"ECDSA Sign: Using public key fails\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Provided key is not a private key.\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.publicKey, \"SHA-256\", \"ASN.1 HEX\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign: Using an RSA key fails\",\n        input: ASCII_TEXT,\n        expectedOutput: \"Provided key is not an EC key.\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [PEM_PPRIV_RSA512, \"SHA-256\", \"ASN.1 HEX\"]\n            }\n        ]\n    },\n\n    // ECDSA Verify\n    {\n        name: \"ECDSA Verify: P-256 with SHA256 (ASN.1 signature)\",\n        input: P256.signature.sha256.asn1,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"Auto\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Verify: P-256 with SHA256 (P1363 signature)\",\n        input: P256.signature.sha256.p1363,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"Auto\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Verify: P-256 with SHA256 (JWS signature)\",\n        input: P256.signature.sha256.jws,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"Auto\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Verify: P-256 with SHA256 (JSON signature)\",\n        input: P256.signature.sha256.json,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"Auto\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Verify: JSON signature missing r\",\n        input: JSON.stringify({s: JSON.parse(P256.signature.sha256.json).s}),\n        expectedOutput: 'No \"r\" value in the signature JSON',\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"Auto\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Verify: JSON signature missing s\",\n        input: JSON.stringify({r: JSON.parse(P256.signature.sha256.json).r}),\n        expectedOutput: 'No \"s\" value in the signature JSON',\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"Auto\", \"SHA-256\", P256.publicKey, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Verify: Using private key fails\",\n        input: P256.signature.sha256.asn1,\n        expectedOutput: \"Provided key is not a public key.\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", P256.privateKeyPkcs1, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Verify: Using an RSA key fails\",\n        input: P256.signature.sha256.asn1,\n        expectedOutput: \"Provided key is not an EC key.\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", PEM_PUB_RSA512, ASCII_TEXT, \"Raw\"]\n            }\n        ]\n    },\n\n    // ECDSA Signatur Conversion\n    {\n        name: \"ECDSA Signature Conversion: ASN.1 To ASN.1\",\n        input: P256.signature.sha256.asn1,\n        expectedOutput: P256.signature.sha256.asn1,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"ASN.1 HEX\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: ASN.1 To P1363\",\n        input: P256.signature.sha256.asn1,\n        expectedOutput: P256.signature.sha256.p1363,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"P1363 HEX\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: ASN.1 To JWS\",\n        input: P256.signature.sha256.asn1,\n        expectedOutput: P256.signature.sha256.jws,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"JSON Web Signature\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: ASN.1 To JSON\",\n        input: P256.signature.sha256.asn1,\n        expectedOutput: P256.signature.sha256.json,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"Raw JSON\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: P1363 To ASN.1\",\n        input: P256.signature.sha256.p1363,\n        expectedOutput: P256.signature.sha256.asn1,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"ASN.1 HEX\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: P1363 To P1363\",\n        input: P256.signature.sha256.p1363,\n        expectedOutput: P256.signature.sha256.p1363,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"P1363 HEX\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: P1363 To JWS\",\n        input: P256.signature.sha256.p1363,\n        expectedOutput: P256.signature.sha256.jws,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"JSON Web Signature\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: P1363 To JSON\",\n        input: P256.signature.sha256.p1363,\n        expectedOutput: P256.signature.sha256.json,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"Raw JSON\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: JSON To ASN.1\",\n        input: P256.signature.sha256.json,\n        expectedOutput: P256.signature.sha256.asn1,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"ASN.1 HEX\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: JSON To P1363\",\n        input: P256.signature.sha256.json,\n        expectedOutput: P256.signature.sha256.p1363,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"P1363 HEX\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: JSON To JWS\",\n        input: P256.signature.sha256.json,\n        expectedOutput: P256.signature.sha256.jws,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"JSON Web Signature\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Signature Conversion: JSON To JSON\",\n        input: P256.signature.sha256.json,\n        expectedOutput: P256.signature.sha256.json,\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Signature Conversion\",\n                \"args\": [\"Auto\", \"Raw JSON\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA256 UTF8\",\n        input: UTF8_TEXT,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-256\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", P256.publicKey, UTF8_TEXT, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA256 bytes raw\",\n        input: ALL_BYTES,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-256\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", P256.publicKey, ALL_BYTES, \"Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA256 bytes hex\",\n        input: SOME_HEX_BYTES,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-256\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", P256.publicKey, SOME_HEX_BYTES, \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"ECDSA Sign/Verify: P-256 with SHA256 bytes Base64\",\n        input: SOME_BASE64_BYTES,\n        expectedOutput: \"Verified OK\",\n        recipeConfig: [\n            {\n                \"op\": \"From Base64\",\n                \"args\": [\"A-Za-z0-9+/=\", true]\n            },\n            {\n                \"op\": \"ECDSA Sign\",\n                \"args\": [P256.privateKeyPkcs1, \"SHA-256\", \"ASN.1 HEX\"]\n            },\n            {\n                \"op\": \"ECDSA Verify\",\n                \"args\": [\"ASN.1 HEX\", \"SHA-256\", P256.publicKey, SOME_BASE64_BYTES, \"Base64\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ELFInfo.mjs",
    "content": "/**\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport {ELF32_LE, ELF32_BE, ELF64_LE, ELF64_BE} from \"../../samples/Executables.mjs\";\n\nconst ELF32_LE_OUTPUT = \"============================== ELF Header ==============================\\nMagic:                        \\x7fELF\\nFormat:                       32-bit\\nEndianness:                   Little\\nVersion:                      1\\nABI:                          System V\\nABI Version:                  0\\nType:                         Executable File\\nInstruction Set Architecture: x86\\nELF Version:                  1\\nEntry Point:                  0x8062150\\nEntry PHOFF:                  0x34\\nEntry SHOFF:                  0x54\\nFlags:                        00000000\\nELF Header Size:              52 bytes\\nProgram Header Size:          32 bytes\\nProgram Header Entries:       1\\nSection Header Size:          40 bytes\\nSection Header Entries:       3\\nSection Header Names:         0\\n\\n============================== Program Header ==============================\\nProgram Header Type:          Program Header Table\\nOffset Of Segment:            52\\nVirtual Address of Segment:   134512692\\nPhysical Address of Segment:  134512692\\nSize of Segment:              256 bytes\\nSize of Segment in Memory:    256 bytes\\nFlags:                        Execute,Read\\n\\n============================== Section Header ==============================\\nType:                         String Table\\nSection Name:                 .shstrab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        204\\nSection Size:                 28\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         Symbol Table\\nSection Name:                 .symtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        230\\nSection Size:                 16\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         String Table\\nSection Name:                 .strtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        245\\nSection Size:                 4\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\n============================== Symbol Table ==============================\\nSymbol Name:                  test\";\nconst ELF32_BE_OUTPUT = \"============================== ELF Header ==============================\\nMagic:                        \\x7fELF\\nFormat:                       32-bit\\nEndianness:                   Big\\nVersion:                      1\\nABI:                          System V\\nABI Version:                  0\\nType:                         Executable File\\nInstruction Set Architecture: x86\\nELF Version:                  1\\nEntry Point:                  0x8062150\\nEntry PHOFF:                  0x34\\nEntry SHOFF:                  0x54\\nFlags:                        00000000\\nELF Header Size:              52 bytes\\nProgram Header Size:          32 bytes\\nProgram Header Entries:       1\\nSection Header Size:          40 bytes\\nSection Header Entries:       3\\nSection Header Names:         0\\n\\n============================== Program Header ==============================\\nProgram Header Type:          Program Header Table\\nOffset Of Segment:            52\\nVirtual Address of Segment:   134512692\\nPhysical Address of Segment:  134512692\\nSize of Segment:              256 bytes\\nSize of Segment in Memory:    256 bytes\\nFlags:                        Execute,Read\\n\\n============================== Section Header ==============================\\nType:                         String Table\\nSection Name:                 .shstrab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        204\\nSection Size:                 28\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         Symbol Table\\nSection Name:                 .symtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        230\\nSection Size:                 16\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         String Table\\nSection Name:                 .strtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        245\\nSection Size:                 4\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\n============================== Symbol Table ==============================\\nSymbol Name:                  test\";\nconst ELF64_LE_OUTPUT = \"============================== ELF Header ==============================\\nMagic:                        \\x7fELF\\nFormat:                       64-bit\\nEndianness:                   Little\\nVersion:                      1\\nABI:                          System V\\nABI Version:                  0\\nType:                         Executable File\\nInstruction Set Architecture: AMD x86-64\\nELF Version:                  1\\nEntry Point:                  0x8062150\\nEntry PHOFF:                  0x40\\nEntry SHOFF:                  0x78\\nFlags:                        00000000\\nELF Header Size:              64 bytes\\nProgram Header Size:          56 bytes\\nProgram Header Entries:       1\\nSection Header Size:          64 bytes\\nSection Header Entries:       3\\nSection Header Names:         0\\n\\n============================== Program Header ==============================\\nProgram Header Type:          Program Header Table\\nFlags:                        Execute,Read\\nOffset Of Segment:            52\\nVirtual Address of Segment:   134512692\\nPhysical Address of Segment:  134512692\\nSize of Segment:              256 bytes\\nSize of Segment in Memory:    256 bytes\\n\\n============================== Section Header ==============================\\nType:                         String Table\\nSection Name:                 .shstrab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        312\\nSection Size:                 28\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         Symbol Table\\nSection Name:                 .symtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        336\\nSection Size:                 16\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         String Table\\nSection Name:                 .strtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        361\\nSection Size:                 4\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\n============================== Symbol Table ==============================\\nSymbol Name:                  test\";\nconst ELF64_BE_OUTPUT = \"============================== ELF Header ==============================\\nMagic:                        \\x7fELF\\nFormat:                       64-bit\\nEndianness:                   Big\\nVersion:                      1\\nABI:                          System V\\nABI Version:                  0\\nType:                         Executable File\\nInstruction Set Architecture: AMD x86-64\\nELF Version:                  1\\nEntry Point:                  0x8062150\\nEntry PHOFF:                  0x40\\nEntry SHOFF:                  0x78\\nFlags:                        00000000\\nELF Header Size:              64 bytes\\nProgram Header Size:          56 bytes\\nProgram Header Entries:       1\\nSection Header Size:          64 bytes\\nSection Header Entries:       3\\nSection Header Names:         0\\n\\n============================== Program Header ==============================\\nProgram Header Type:          Program Header Table\\nFlags:                        Execute,Read\\nOffset Of Segment:            52\\nVirtual Address of Segment:   134512692\\nPhysical Address of Segment:  134512692\\nSize of Segment:              256 bytes\\nSize of Segment in Memory:    256 bytes\\n\\n============================== Section Header ==============================\\nType:                         String Table\\nSection Name:                 .shstrab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        312\\nSection Size:                 28\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         Symbol Table\\nSection Name:                 .symtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        336\\nSection Size:                 16\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\nType:                         String Table\\nSection Name:                 .strtab\\nFlags:                        \\nSection Vaddr in memory:      0\\nOffset of the section:        361\\nSection Size:                 4\\nAssociated Section:           0\\nSection Extra Information:    0\\n\\n============================== Symbol Table ==============================\\nSymbol Name:                  test\";\n\nTestRegister.addTests([\n    {\n        name: \"ELF Info invalid ELF.\",\n        input: \"\\x7f\\x00\\x00\\x00\",\n        expectedOutput: \"Invalid ELF\",\n        recipeConfig: [\n            {\n                op: \"ELF Info\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"ELF Info 32-bit ELF Little Endian.\",\n        input: ELF32_LE,\n        expectedOutput: ELF32_LE_OUTPUT,\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"],\n            },\n            {\n                op: \"ELF Info\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"ELF Info 32-bit ELF Big Endian.\",\n        input: ELF32_BE,\n        expectedOutput: ELF32_BE_OUTPUT,\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"],\n            },\n            {\n                op: \"ELF Info\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"ELF Info 64-bit ELF Little Endian.\",\n        input: ELF64_LE,\n        expectedOutput: ELF64_LE_OUTPUT,\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"],\n            },\n            {\n                op: \"ELF Info\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"ELF Info 64-bit ELF Big Endian.\",\n        input: ELF64_BE,\n        expectedOutput: ELF64_BE_OUTPUT,\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"],\n            },\n            {\n                op: \"ELF Info\",\n                args: [],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Enigma.mjs",
    "content": "/**\n * Enigma machine tests.\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        // Simplest test: A single keypress in the default position on a basic\n        // Enigma.\n        name: \"Enigma: basic wiring\",\n        input: \"G\",\n        expectedOutput: \"P\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    // Note: start on Z because it steps when the key is pressed\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"Z\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Rotor position test: single keypress, basic rotors, random start\n        // positions, no advancement of other rotors.\n        name: \"Enigma: rotor position\",\n        input: \"A\",\n        expectedOutput: \"T\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"N\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"F\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"W\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Rotor ring setting test: single keypress, basic rotors, one rotor\n        // ring offset by one, basic start position, no advancement of other\n        // rotors.\n        name: \"Enigma: rotor ring setting\",\n        input: \"A\",\n        expectedOutput: \"O\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"B\", \"Z\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Rotor ring setting test: single keypress, basic rotors, random ring\n        // settings, basic start position, no advancement of other rotors.\n        name: \"Enigma: rotor ring setting 2\",\n        input: \"A\",\n        expectedOutput: \"F\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"N\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"F\", \"A\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"W\", \"Z\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Stepping: basic configuration, enough input to cause middle rotor to\n        // step\n        name: \"Enigma: stepping\",\n        input: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        expectedOutput: \"UBDZG OWCXL TKSBT MCDLP BMUQO F\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"Z\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Ensure that we can decrypt an encrypted message.\n        name: \"Enigma: reflectivity\",\n        input: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        expectedOutput: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"Z\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            },\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"Z\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Stepping: with rotors set so we're about to trigger the double step\n        // anomaly\n        name: \"Enigma: double step anomaly\",\n        input: \"AAAAA\",\n        expectedOutput: \"EQIBM\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"D\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"U\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Stepping: with rotors set so we're about to trigger the double step\n        // anomaly\n        name: \"Enigma: double step anomaly 2\",\n        input: \"AAAA\",\n        expectedOutput: \"BRNC\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"E\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"U\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Stepping: with rotors set so we're about to trigger the double step\n        // anomaly\n        name: \"Enigma: double step anomaly 3\",\n        input: \"AAAAA AAA\",\n        expectedOutput: \"ZEEQI BMG\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"D\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"S\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Stepping: with a ring setting\n        name: \"Enigma: ring setting stepping\",\n        input: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        expectedOutput: \"PBMFE BOUBD ZGOWC XLTKS BTXSH I\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"H\", \"Z\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Stepping: with a ring setting and double step\n        name: \"Enigma: ring setting double step\",\n        input: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        expectedOutput: \"TEVFK UTIIW EDWVI JPMVP GDEZS P\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"Q\", \"A\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"C\", \"D\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"H\", \"F\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\",\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Four-rotor Enigma, random settings, no plugboard\n        name: \"Enigma: four rotor\",\n        input: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        expectedOutput: \"GZXGX QUSUW JPWVI GVBTU DQZNZ J\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"4-rotor\",\n                    \"LEYJVCNIXWPBQMDRTAKZGFUHOS\", \"A\", \"X\", // Beta\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"O\", \"E\",\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"P\", \"F\",\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"D\", \"Q\",\n                    \"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV\", // B thin\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Four-rotor Enigma, different wheel set, no plugboard\n        name: \"Enigma: four rotor 2\",\n        input: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        expectedOutput: \"HZJLP IKWBZ XNCWF FIHWL EROOZ C\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"4-rotor\",\n                    \"FSOKANUERHMBTIYCWLQPZXVGJD\", \"A\", \"L\", // Gamma\n                    \"JPGVOUMFYQBENHZRDKASXLICTW<AN\", \"A\", \"J\", // VI\n                    \"VZBRGITYUPSDNHLXAWMJQOFECK<A\", \"M\", \"G\", // V\n                    \"ESOVPZJAYQUIRHXLNFTGKDCMWB<K\", \"W\", \"U\", // IV\n                    \"AR BD CO EJ FN GT HK IV LM PW QZ SX UY\", // C thin\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        // Four-rotor Enigma, different wheel set, random plugboard\n        name: \"Enigma: plugboard\",\n        input: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        expectedOutput: \"GHLIM OJIUW DKLWM JGNJK DYJVD K\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"4-rotor\",\n                    \"FSOKANUERHMBTIYCWLQPZXVGJD\", \"A\", \"I\", // Gamma\n                    \"NZJHGRCXMYSWBOUFAIVLPEKQDT<AN\", \"I\", \"V\", // VII\n                    \"ESOVPZJAYQUIRHXLNFTGKDCMWB<K\", \"O\", \"O\", // IV\n                    \"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN\", \"U\", \"Z\", // VIII\n                    \"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV\", // B thin\n                    \"WN MJ LX YB FP QD US IH CE GR\"\n                ]\n            }\n        ]\n    },\n    {\n        // Decryption test on above input\n        name: \"Enigma: decryption\",\n        input: \"GHLIM OJIUW DKLWM JGNJK DYJVD K\",\n        expectedOutput: \"AAAAA AAAAA AAAAA AAAAA AAAAA A\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"4-rotor\",\n                    \"FSOKANUERHMBTIYCWLQPZXVGJD\", \"A\", \"I\", // Gamma\n                    \"NZJHGRCXMYSWBOUFAIVLPEKQDT<AN\", \"I\", \"V\", // VII\n                    \"ESOVPZJAYQUIRHXLNFTGKDCMWB<K\", \"O\", \"O\", // IV\n                    \"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN\", \"U\", \"Z\", // VIII\n                    \"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV\", // B thin\n                    \"WN MJ LX YB FP QD US IH CE GR\"\n                ]\n            }\n        ]\n    },\n    {\n        // Decryption test on real message\n        name: \"Enigma: decryption 2\",\n        input: \"LANOTCTOUARBBFPMHPHGCZXTDYGAHGUFXGEWKBLKGJWLQXXTGPJJAVTOCKZFSLPPQIHZFXOEBWIIEKFZLCLOAQJULJOYHSSMBBGWHZANVOIIPYRBRTDJQDJJOQKCXWDNBBTYVXLYTAPGVEATXSONPNYNQFUDBBHHVWEPYEYDOHNLXKZDNWRHDUWUJUMWWVIIWZXIVIUQDRHYMNCYEFUAPNHOTKHKGDNPSAKNUAGHJZSMJBMHVTREQEDGXHLZWIFUSKDQVELNMIMITHBHDBWVHDFYHJOQIHORTDJDBWXEMEAYXGYQXOHFDMYUXXNOJAZRSGHPLWMLRECWWUTLRTTVLBHYOORGLGOWUXNXHMHYFAACQEKTHSJW\",\n        expectedOutput: \"KRKRALLEXXFOLGENDESISTSOFORTBEKANNTZUGEBENXXICHHABEFOLGELNBEBEFEHLERHALTENXXJANSTERLEDESBISHERIGXNREICHSMARSCHALLSJGOERINGJSETZTDERFUEHRERSIEYHVRRGRZSSADMIRALYALSSEINENNACHFOLGEREINXSCHRIFTLSCHEVOLLMACHTUNTERWEGSXABSOFORTSOLLENSIESAEMTLICHEMASSNAHMENVERFUEGENYDIESICHAUSDERGEGENWAERTIGENLAGEERGEBENXGEZXREICHSLEITEIKKTULPEKKJBORMANNJXXOBXDXMMMDURNHFKSTXKOMXADMXUUUBOOIEXKP\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"4-rotor\",\n                    \"LEYJVCNIXWPBQMDRTAKZGFUHOS\", \"E\", \"C\", // Beta\n                    \"VZBRGITYUPSDNHLXAWMJQOFECK<A\", \"P\", \"D\", // V\n                    \"JPGVOUMFYQBENHZRDKASXLICTW<AN\", \"E\", \"S\", // VI\n                    \"FKQHTLXOCBJSPDZRAMEWNIUYGV<AN\", \"L\", \"Z\", // VIII\n                    \"AR BD CO EJ FN GT HK IV LM PW QZ SX UY\", // C thin\n                    \"AE BF CM DQ HU JN LX PR SZ VW\"\n                ]\n            }\n        ]\n    },\n    {\n        // Non-alphabet characters drop test\n        name: \"Enigma: non-alphabet drop\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"ILBDA AMTAZ MORNZ DDIOT U\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\", true\n                ]\n            }\n        ]\n    },\n    {\n        // Non-alphabet characters passthrough test\n        name: \"Enigma: non-alphabet passthrough\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"ILBDA, AMTAZ. MORN ZD D IOTU.\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\", false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: rotor validation 1\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Rotor wiring must be 26 unique uppercase letters\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQ\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: rotor validation 2\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Rotor wiring must be 26 unique uppercase letters\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQo\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: rotor validation 3\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Rotor wiring must have each letter exactly once\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQA\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: rotor validation 4\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Rotor steps must be unique\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<RR\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: rotor validation 5\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Rotor steps must be 0-26 unique uppercase letters\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<a\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    // The ring setting and positions are dropdowns in the interface so not\n    // gonna bother testing them\n    {\n        name: \"Enigma: reflector validation 1\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Reflector must have exactly 13 pairs covering every letter\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"A\", // III\n                    \"AY BR CU DH EQ FS GL IP JX KN MO\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: reflector validation 2\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Reflector must have exactly 13 pairs covering every letter\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"A\", // III\n                    \"AA BR CU DH EQ FS GL IP JX KN MO TZ VV WY\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: reflector validation 3\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Reflector connects A more than once\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"A\", // III\n                    \"AY AR CU DH EQ FS GL IP JX KN MO TZ\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Enigma: reflector validation 4\",\n        input: \"Hello, world. This is a test.\",\n        expectedOutput: \"Reflector must be a whitespace-separated list of uppercase letter pairs\",\n        recipeConfig: [\n            {\n                \"op\": \"Enigma\",\n                \"args\": [\n                    \"3-rotor\",\n                    \"\", \"A\", \"A\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\", \"A\", \"A\", // I\n                    \"AJDKSIRUXBLHWTMCQGZNPYFVOE<F\", \"A\", \"A\", // II\n                    \"BDFHJLCPRTXVZNYEIWGAKMUSQO<W\", \"A\", \"A\", // III\n                    \"AYBR CU DH EQ FS GL IP JX KN MO TZ\", // B\n                    \"\"\n                ]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ExtractAudioMetadata.mjs",
    "content": "/**\n * Extract Audio Metadata operation tests.\n *\n * @author d0s1nt\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport {\n    MP3_HEX, WAV_HEX, FLAC_HEX, AAC_HEX,\n    AC3_HEX, OGG_HEX, OPUS_HEX, WMA_HEX,\n    M4A_HEX, AIFF_HEX\n} from \"../../samples/Audio.mjs\";\n\nTestRegister.addTests([\n    // ---- MP3 ----\n    {\n        name: \"Extract Audio Metadata: MP3 container and MIME\",\n        input: MP3_HEX,\n        expectedMatch: /Container<\\/td><td>mp3<\\/td>.*MIME<\\/td><td>audio\\/mpeg<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.mp3\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: MP3 common tags (title, artist)\",\n        input: MP3_HEX,\n        expectedMatch: /Title<\\/td><td>Galway<\\/td>.*Artist<\\/td><td>Kevin MacLeod<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.mp3\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: MP3 ID3v2 frames (TIT2, TPE1, TSSE)\",\n        input: MP3_HEX,\n        expectedMatch: /ID3v2 Frames.*TIT2.*Galway.*TPE1.*Kevin MacLeod.*TSSE.*Lavf56\\.40\\.101/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.mp3\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: MP3 detections (id3v2)\",\n        input: MP3_HEX,\n        expectedMatch: /Metadata systems<\\/td><td>id3v2<\\/td>/,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.mp3\", 524288] }\n        ]\n    },\n\n    // ---- WAV ----\n    {\n        name: \"Extract Audio Metadata: WAV container and MIME\",\n        input: WAV_HEX,\n        expectedMatch: /Container<\\/td><td>wav<\\/td>.*MIME<\\/td><td>audio\\/wav<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.wav\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: WAV RIFF chunks (fmt)\",\n        input: WAV_HEX,\n        expectedMatch: /RIFF Chunks.*fmt .*16 bytes @ offset 20/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.wav\", 524288] }\n        ]\n    },\n\n    // ---- FLAC ----\n    {\n        name: \"Extract Audio Metadata: FLAC container and common tags\",\n        input: FLAC_HEX,\n        expectedMatch: /Container<\\/td><td>flac<\\/td>.*Title<\\/td><td>Galway<\\/td>.*Artist<\\/td><td>Kevin MacLeod<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.flac\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: FLAC metadata blocks (STREAMINFO, VORBIS_COMMENT)\",\n        input: FLAC_HEX,\n        expectedMatch: /FLAC Metadata Blocks.*STREAMINFO<\\/td><td>34 bytes<\\/td>.*VORBIS_COMMENT<\\/td><td>86 bytes<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.flac\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: FLAC Vorbis comments (vendor, tags)\",\n        input: FLAC_HEX,\n        expectedMatch: /Vorbis Comments.*Vendor<\\/td><td>Lavf56\\.40\\.101<\\/td>.*TITLE<\\/td><td>Galway<\\/td>.*ARTIST<\\/td><td>Kevin MacLeod<\\/td>.*ENCODER<\\/td><td>Lavf56\\.40\\.101<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.flac\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: FLAC detections\",\n        input: FLAC_HEX,\n        expectedMatch: /Metadata systems<\\/td><td>flac_metablocks, vorbis_comments<\\/td>/,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.flac\", 524288] }\n        ]\n    },\n\n    // ---- AAC ----\n    {\n        name: \"Extract Audio Metadata: AAC container and MIME\",\n        input: AAC_HEX,\n        expectedMatch: /Container<\\/td><td>aac<\\/td>.*MIME<\\/td><td>audio\\/aac<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.aac\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: AAC ADTS technical fields\",\n        input: AAC_HEX,\n        expectedMatch: /AAC ADTS.*mpeg_version<\\/td><td>MPEG-4<\\/td>.*profile<\\/td><td>LC<\\/td>.*sample_rate<\\/td><td>44100<\\/td>.*channel_description<\\/td><td>stereo<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.aac\", 524288] }\n        ]\n    },\n\n    // ---- AC3 ----\n    {\n        name: \"Extract Audio Metadata: AC3 container and MIME\",\n        input: AC3_HEX,\n        expectedMatch: /Container<\\/td><td>ac3<\\/td>.*MIME<\\/td><td>audio\\/ac3<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.ac3\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: AC3 technical fields (sample rate, bitrate, channels)\",\n        input: AC3_HEX,\n        expectedMatch: /AC3 \\(Dolby Digital\\).*sample_rate<\\/td><td>44100<\\/td>.*bitrate_kbps<\\/td><td>192<\\/td>.*channel_layout<\\/td><td>2\\.0 \\(L R\\)<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.ac3\", 524288] }\n        ]\n    },\n\n    // ---- OGG Vorbis ----\n    {\n        name: \"Extract Audio Metadata: OGG container and common tags\",\n        input: OGG_HEX,\n        expectedMatch: /Container<\\/td><td>ogg<\\/td>.*Title<\\/td><td>Galway<\\/td>.*Artist<\\/td><td>Kevin MacLeod<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.ogg\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: OGG Vorbis comments (vendor, encoder)\",\n        input: OGG_HEX,\n        expectedMatch: /Vorbis Comments.*Vendor<\\/td><td>Lavf56\\.40\\.101<\\/td>.*ENCODER<\\/td><td>Lavc56\\.60\\.100 libvorbis<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.ogg\", 524288] }\n        ]\n    },\n\n    // ---- Opus ----\n    {\n        name: \"Extract Audio Metadata: Opus container and common tags\",\n        input: OPUS_HEX,\n        expectedMatch: /Container<\\/td><td>opus<\\/td>.*Title<\\/td><td>Galway<\\/td>.*Artist<\\/td><td>Kevin MacLeod<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.opus\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: Opus Vorbis comments (vendor, encoder)\",\n        input: OPUS_HEX,\n        expectedMatch: /Vorbis Comments.*Vendor<\\/td><td>Lavf58\\.19\\.102<\\/td>.*ENCODER<\\/td><td>Lavc58\\.34\\.100 libopus<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.opus\", 524288] }\n        ]\n    },\n\n    // ---- WMA/ASF ----\n    {\n        name: \"Extract Audio Metadata: WMA container and MIME\",\n        input: WMA_HEX,\n        expectedMatch: /Container<\\/td><td>wma<\\/td>.*MIME<\\/td><td>audio\\/x-ms-wma<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.wma\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: WMA common tags (title, artist)\",\n        input: WMA_HEX,\n        expectedMatch: /Title<\\/td><td>Galway<\\/td>.*Artist<\\/td><td>Kevin MacLeod<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.wma\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: WMA ASF Content Description\",\n        input: WMA_HEX,\n        expectedMatch: /ASF Content Description.*title<\\/td><td>Galway<\\/td>.*author<\\/td><td>Kevin MacLeod<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.wma\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: WMA ASF Extended Content (encoding settings)\",\n        input: WMA_HEX,\n        expectedMatch: /ASF Extended Content.*WM\\/EncodingSettings<\\/td><td>Lavf56\\.40\\.101<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.wma\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: WMA detections\",\n        input: WMA_HEX,\n        expectedMatch: /Metadata systems<\\/td><td>asf_header, asf_content_desc, asf_ext_content_desc<\\/td>/,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.wma\", 524288] }\n        ]\n    },\n\n    // ---- M4A ----\n    {\n        name: \"Extract Audio Metadata: M4A container, MIME and brand\",\n        input: M4A_HEX,\n        expectedMatch: /Container<\\/td><td>m4a<\\/td>.*MIME<\\/td><td>audio\\/mp4<\\/td>.*Brand<\\/td><td>M4A <\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.m4a\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: M4A top-level atoms (ftyp, mdat)\",\n        input: M4A_HEX,\n        expectedMatch: /MP4 Top-Level Atoms.*ftyp<\\/td>.*mdat<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.m4a\", 524288] }\n        ]\n    },\n\n    // ---- AIFF ----\n    {\n        name: \"Extract Audio Metadata: AIFF container, MIME and brand\",\n        input: AIFF_HEX,\n        expectedMatch: /Container<\\/td><td>aiff<\\/td>.*MIME<\\/td><td>audio\\/aiff<\\/td>.*Brand<\\/td><td>AIFF<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.aiff\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: AIFF common tag (title from NAME chunk)\",\n        input: AIFF_HEX,\n        expectedMatch: /Title<\\/td><td>Galway<\\/td>/,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.aiff\", 524288] }\n        ]\n    },\n    {\n        name: \"Extract Audio Metadata: AIFF chunks (NAME)\",\n        input: AIFF_HEX,\n        expectedMatch: /AIFF Chunks.*NAME<\\/td><td>Galway<\\/td>/s,\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"None\"] },\n            { op: \"Extract Audio Metadata\", args: [\"test.aiff\", 524288] }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ExtractEmailAddresses.mjs",
    "content": "/**\n * extract email address tests.\n *\n * @author Klaxon [klaxon@veyr.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Extract email address\",\n        input: \"email@example.com\\nfirstname.lastname@example.com\\nemail@subdomain.example.com\\nfirstname+lastname@example.com\\n1234567890@example.com\\nemail@example-one.com\\n_______@example.com email@example.name\\nemail@example.museum email@example.co.jp firstname-lastname@example.com\",\n        expectedOutput: \"email@example.com\\nfirstname.lastname@example.com\\nemail@subdomain.example.com\\nfirstname+lastname@example.com\\n1234567890@example.com\\nemail@example-one.com\\n_______@example.com\\nemail@example.name\\nemail@example.museum\\nemail@example.co.jp\\nfirstname-lastname@example.com\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract email addresses\",\n                \"args\": [false]\n            },\n        ],\n    },\n    {\n        name: \"Extract email address - Display total\",\n        input: \"email@example.com\\nfirstname.lastname@example.com\\nemail@subdomain.example.com\\nfirstname+lastname@example.com\\n1234567890@example.com\\nemail@example-one.com\\n_______@example.com email@example.name\\nemail@example.museum email@example.co.jp firstname-lastname@example.com\",\n        expectedOutput: \"Total found: 11\\n\\nemail@example.com\\nfirstname.lastname@example.com\\nemail@subdomain.example.com\\nfirstname+lastname@example.com\\n1234567890@example.com\\nemail@example-one.com\\n_______@example.com\\nemail@example.name\\nemail@example.museum\\nemail@example.co.jp\\nfirstname-lastname@example.com\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract email addresses\",\n                \"args\": [true]\n            },\n        ],\n    },\n    {\n        name: \"Extract email address (Internationalized)\",\n        input: \"\\u4f0a\\u662d\\u5091@\\u90f5\\u4ef6.\\u5546\\u52d9 \\u093e\\u092e@\\u092e\\u094b\\u0939\\u0928.\\u0908\\u0928\\u094d\\u092b\\u094b\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c \\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc Jos\\u1ec5Silv\\u1ec5@googl\\u1ec5.com\\nJos\\u1ec5Silv\\u1ec5@google.com and Jos\\u1ec5Silva@google.com\\nFoO@BaR.CoM, john@192.168.10.100\\ng\\xf3mez@junk.br and Abc.123@example.com.\\nuser+mailbox/department=shipping@example.com\\n\\u7528\\u6237@\\u4f8b\\u5b50.\\u5e7f\\u544a\\n\\u0909\\u092a\\u092f\\u094b\\u0917\\u0915\\u0930\\u094d\\u0924\\u093e@\\u0909\\u0926\\u093e\\u0939\\u0930\\u0923.\\u0915\\u0949\\u092e\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c\\n\\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc\\nD\\xf6rte@S\\xf6rensen.example.com\\n\\u0430\\u0434\\u0436\\u0430\\u0439@\\u044d\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u0440\\u0443\\u0441\\ntest@xn--bcher-kva.com\",\n        expectedOutput: \"\\u4f0a\\u662d\\u5091@\\u90f5\\u4ef6.\\u5546\\u52d9\\n\\u093e\\u092e@\\u092e\\u094b\\u0939\\u0928.\\u0908\\u0928\\u094d\\u092b\\u094b\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c\\n\\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc\\nJos\\u1ec5Silv\\u1ec5@googl\\u1ec5.com\\nJos\\u1ec5Silv\\u1ec5@google.com\\nJos\\u1ec5Silva@google.com\\nFoO@BaR.CoM\\njohn@192.168.10.100\\ng\\xf3mez@junk.br\\nAbc.123@example.com\\nuser+mailbox/department=shipping@example.com\\n\\u7528\\u6237@\\u4f8b\\u5b50.\\u5e7f\\u544a\\n\\u0909\\u092a\\u092f\\u094b\\u0917\\u0915\\u0930\\u094d\\u0924\\u093e@\\u0909\\u0926\\u093e\\u0939\\u0930\\u0923.\\u0915\\u0949\\u092e\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c\\n\\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc\\nD\\xf6rte@S\\xf6rensen.example.com\\n\\u0430\\u0434\\u0436\\u0430\\u0439@\\u044d\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u0440\\u0443\\u0441\\ntest@xn--bcher-kva.com\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract email addresses\",\n                \"args\": [false]\n            },\n        ],\n    },\n    {\n        name: \"Extract email address - Display total (Internationalized)\",\n        input: \"\\u4f0a\\u662d\\u5091@\\u90f5\\u4ef6.\\u5546\\u52d9 \\u093e\\u092e@\\u092e\\u094b\\u0939\\u0928.\\u0908\\u0928\\u094d\\u092b\\u094b\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c \\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc Jos\\u1ec5Silv\\u1ec5@googl\\u1ec5.com\\nJos\\u1ec5Silv\\u1ec5@google.com and Jos\\u1ec5Silva@google.com\\nFoO@BaR.CoM, john@192.168.10.100\\ng\\xf3mez@junk.br and Abc.123@example.com.\\nuser+mailbox/department=shipping@example.com\\n\\u7528\\u6237@\\u4f8b\\u5b50.\\u5e7f\\u544a\\n\\u0909\\u092a\\u092f\\u094b\\u0917\\u0915\\u0930\\u094d\\u0924\\u093e@\\u0909\\u0926\\u093e\\u0939\\u0930\\u0923.\\u0915\\u0949\\u092e\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c\\n\\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc\\nD\\xf6rte@S\\xf6rensen.example.com\\n\\u0430\\u0434\\u0436\\u0430\\u0439@\\u044d\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u0440\\u0443\\u0441\\ntest@xn--bcher-kva.com\",\n        expectedOutput: \"Total found: 19\\n\\n\\u4f0a\\u662d\\u5091@\\u90f5\\u4ef6.\\u5546\\u52d9\\n\\u093e\\u092e@\\u092e\\u094b\\u0939\\u0928.\\u0908\\u0928\\u094d\\u092b\\u094b\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c\\n\\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc\\nJos\\u1ec5Silv\\u1ec5@googl\\u1ec5.com\\nJos\\u1ec5Silv\\u1ec5@google.com\\nJos\\u1ec5Silva@google.com\\nFoO@BaR.CoM\\njohn@192.168.10.100\\ng\\xf3mez@junk.br\\nAbc.123@example.com\\nuser+mailbox/department=shipping@example.com\\n\\u7528\\u6237@\\u4f8b\\u5b50.\\u5e7f\\u544a\\n\\u0909\\u092a\\u092f\\u094b\\u0917\\u0915\\u0930\\u094d\\u0924\\u093e@\\u0909\\u0926\\u093e\\u0939\\u0930\\u0923.\\u0915\\u0949\\u092e\\n\\u044e\\u0437\\u0435\\u0440@\\u0435\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u043a\\u043e\\u043c\\n\\u03b8\\u03c3\\u03b5\\u03c1@\\u03b5\\u03c7\\u03b1\\u03bc\\u03c0\\u03bb\\u03b5.\\u03c8\\u03bf\\u03bc\\nD\\xf6rte@S\\xf6rensen.example.com\\n\\u0430\\u0434\\u0436\\u0430\\u0439@\\u044d\\u043a\\u0437\\u0430\\u043c\\u043f\\u043b.\\u0440\\u0443\\u0441\\ntest@xn--bcher-kva.com\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract email addresses\",\n                \"args\": [true]\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ExtractHashes.mjs",
    "content": "/**\n * ExtractHashes tests.\n *\n * @author mshwed [m@ttshwed.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Extract MD5 hash\",\n        input: \"The quick brown fox jumps over the lazy dog\\n\\nMD5: 9e107d9d372bb6826bd81d3542a419d6\",\n        expectedOutput: \"9e107d9d372bb6826bd81d3542a419d6\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract hashes\",\n                \"args\": [32, false, false]\n            },\n        ],\n    },\n    {\n        name: \"Extract SHA1 hash\",\n        input: \"The quick brown fox jumps over the lazy dog\\n\\nSHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\",\n        expectedOutput: \"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract hashes\",\n                \"args\": [40, false, false]\n            },\n        ],\n    },\n    {\n        name: \"Extract SHA256 hash\",\n        input: \"The quick brown fox jumps over the lazy dog\\n\\nSHA256: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\",\n        expectedOutput: \"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract hashes\",\n                \"args\": [64, false, false]\n            },\n        ],\n    },\n    {\n        name: \"Extract SHA512 hash\",\n        input: \"The quick brown fox jumps over the lazy dog\\n\\nSHA512: 07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6\",\n        expectedOutput: \"07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract hashes\",\n                \"args\": [128, false, false]\n            },\n        ],\n    },\n    {\n        name: \"Extract all hashes\",\n        input: \"The quick brown fox jumps over the lazy dog\\n\\nMD5: 9e107d9d372bb6826bd81d3542a419d6\\nSHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\\nSHA256: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\",\n        expectedOutput: \"9e107d9d372bb6826bd81d3542a419d6\\n2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\\nd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract hashes\",\n                \"args\": [0, true, false]\n            },\n        ],\n    },\n    {\n        name: \"Extract hashes with total count\",\n        input: \"The quick brown fox jumps over the lazy dog\\n\\nMD5: 9e107d9d372bb6826bd81d3542a419d6\\nSHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\\nSHA256: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\",\n        expectedOutput: \"Total Results: 3\\n\\n9e107d9d372bb6826bd81d3542a419d6\\n2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\\nd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract hashes\",\n                \"args\": [0, true, true]\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ExtractIPAddresses.mjs",
    "content": "/**\n * ExtractIPAddresses tests.\n *\n * @author gchqdev365 [gchqdev365@outlook.com]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"ExtractIPAddress All Zeros\",\n        input: \"0.0.0.0\",\n        expectedOutput: \"0.0.0.0\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress All 10s\",\n        input: \"10.10.10.10\",\n        expectedOutput: \"10.10.10.10\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress All 10s\",\n        input: \"100.100.100.100\",\n        expectedOutput: \"100.100.100.100\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress 255s\",\n        input: \"255.255.255.255\",\n        expectedOutput: \"255.255.255.255\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress double digits\",\n        input: \"10.10.10.10 25.25.25.25 99.99.99.99\",\n        expectedOutput: \"10.10.10.10\\n25.25.25.25\\n99.99.99.99\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress 256 in middle\",\n        input: \"255.256.255.255 255.255.256.255\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress 256 at each end\",\n        input: \"256.255.255.255 255.255.255.256\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress silly example\",\n        input: \"710.65.0.456\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress longer dotted decimal\",\n        input: \"1.2.3.4.5.6.7.8\",\n        expectedOutput: \"1.2.3.4\\n5.6.7.8\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress octal valid\",\n        input: \"01.01.01.01 0123.0123.0123.0123 0377.0377.0377.0377\",\n        expectedOutput: \"01.01.01.01\\n0123.0123.0123.0123\\n0377.0377.0377.0377\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"ExtractIPAddress octal invalid\",\n        input: \"0378.01.01.01 03.0377.2.3\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Extract IP addresses\",\n                \"args\": [true, true, false, false, false, false]\n            },\n        ],\n    },\n]);\n\n"
  },
  {
    "path": "tests/operations/tests/Fernet.mjs",
    "content": "/**\n * Fernet tests.\n *\n * @author Karsten Silkenbäumer [github.com/kassi]\n * @copyright Karsten Silkenbäumer 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../TestRegister\";\n\nTestRegister.addTests([\n    {\n        name: \"Fernet Decrypt: no input\",\n        input: \"\",\n        expectedOutput: \"Error: Invalid version\",\n        recipeConfig: [\n            {\n                op: \"Fernet Decrypt\",\n                args: [\"MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=\"]\n            }\n        ],\n    },\n    {\n        name: \"Fernet Decrypt: no secret\",\n        input: \"gAAAAABce-Tycae8klRxhDX2uenJ-uwV8-A1XZ2HRnfOXlNzkKKfRxviNLlgtemhT_fd1Fw5P_zFUAjd69zaJBQyWppAxVV00SExe77ql8c5n62HYJOnoIU=\",\n        expectedOutput: \"Error: Secret must be 32 url-safe base64-encoded bytes.\",\n        recipeConfig: [\n            {\n                op: \"Fernet Decrypt\",\n                args: [\"\"]\n            }\n        ],\n    },\n    {\n        name: \"Fernet Decrypt: valid arguments\",\n        input: \"gAAAAABce-Tycae8klRxhDX2uenJ-uwV8-A1XZ2HRnfOXlNzkKKfRxviNLlgtemhT_fd1Fw5P_zFUAjd69zaJBQyWppAxVV00SExe77ql8c5n62HYJOnoIU=\",\n        expectedOutput: \"This is a secret message.\\n\",\n        recipeConfig: [\n            {\n                op: \"Fernet Decrypt\",\n                args: [\"VGhpc0lzVGhpcnR5VHdvQ2hhcmFjdGVyc0xvbmdLZXk=\"]\n            }\n        ],\n    }\n]);\n\nTestRegister.addTests([\n    {\n        name: \"Fernet Encrypt: no input\",\n        input: \"\",\n        expectedMatch: /^gAAAAABce-[\\w-]+={0,2}$/,\n        recipeConfig: [\n            {\n                op: \"Fernet Encrypt\",\n                args: [\"MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=\"]\n            }\n        ],\n    },\n    {\n        name: \"Fernet Encrypt: no secret\",\n        input: \"This is a secret message.\\n\",\n        expectedOutput: \"Error: Secret must be 32 url-safe base64-encoded bytes.\",\n        recipeConfig: [\n            {\n                op: \"Fernet Encrypt\",\n                args: [\"\"]\n            }\n        ],\n    },\n    {\n        name: \"Fernet Encrypt: valid arguments\",\n        input: \"This is a secret message.\\n\",\n        expectedMatch: /^gAAAAABce-[\\w-]+={0,2}$/,\n        recipeConfig: [\n            {\n                op: \"Fernet Encrypt\",\n                args: [\"MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=\"]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/FileTree.mjs",
    "content": "/**\n * File tree tests.\n *\n * @author sw5678\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"File Tree: basic example\",\n        \"input\": \"/test_dir1/test_file1.txt\\n/test_dir1/test_file2.txt\\n/test_dir2/test_file1.txt\",\n        \"expectedOutput\": \"test_dir1\\n|---test_file1.txt\\n|---test_file2.txt\\ntest_dir2\\n|---test_file1.txt\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"File Tree\",\n                \"args\": [\"/\", \"Line feed\"],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/FlaskSession.mjs",
    "content": "/**\n * Flask Session tests\n *\n * @author ThePlayer372-FR []\n *\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst validTokenSha1 = \"eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aZ-KEw.E_x6bOhA4GU9t72pMinJUjN-O3I\";\nconst validTokenSha256 = \"eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aab3Ew.Jsx2DOx_H9anZg0YcvhsASxQ11897EFHeQfS2oja4y8\";\n\nconst validKey = \"mysecretkey\";\nconst wrongKey = \"notTheKey\";\n\nconst outputObject = {\n    user: \"admin\",\n    role: \"superuser\",\n};\n\nconst outputVerify = {\n    valid: true,\n    payload: outputObject,\n};\n\nTestRegister.addTests([\n    {\n        name: \"Flask Session: Decode\",\n        input: validTokenSha1,\n        expectedOutput: outputObject,\n        recipeConfig: [\n            {\n                op: \"Flask Session Decode\",\n                args: [\n                    false\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Verify Sha1\",\n        input: validTokenSha1,\n        expectedOutput: outputVerify,\n        recipeConfig: [\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha1\",\n                    false,\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Verify Sha256\",\n        input: validTokenSha256,\n        expectedOutput: outputVerify,\n        recipeConfig: [\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha256\",\n                    false,\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Sign Sha1\",\n        input: outputObject,\n        expectedOutput: outputVerify,\n        recipeConfig: [\n            {\n                op: \"Flask Session Sign\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha1\"\n                ]\n            },\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha1\",\n                    false,\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Sign Sha256\",\n        input: outputObject,\n        expectedOutput: outputVerify,\n        recipeConfig: [\n            {\n                op: \"Flask Session Sign\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha256\"\n                ]\n            },\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha256\",\n                    false,\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Verify Sha1 Wrong Key\",\n        input: validTokenSha1,\n        expectedOutput: \"Invalid signature!\",\n        recipeConfig: [\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: wrongKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha1\",\n                    false,\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Verify Sha256 Wrong Key\",\n        input: validTokenSha256,\n        expectedOutput: \"Invalid signature!\",\n        recipeConfig: [\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: wrongKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"cookie-session\",\n                        option: \"UTF8\"\n                    },\n                    \"sha256\",\n                    false,\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Verify Sha1 Wrong Salt\",\n        input: validTokenSha1,\n        expectedOutput: \"Invalid signature!\",\n        recipeConfig: [\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"notTheSalt\",\n                        option: \"UTF8\"\n                    },\n                    \"sha1\",\n                    false,\n                ],\n            }\n        ]\n    },\n    {\n        name: \"Flask Session: Verify Sha256 Wrong Salt\",\n        input: validTokenSha256,\n        expectedOutput: \"Invalid signature!\",\n        recipeConfig: [\n            {\n                op: \"Flask Session Verify\",\n                args: [\n                    {\n                        string: validKey,\n                        option: \"UTF8\"\n                    },\n                    {\n                        string: \"notTheSalt\",\n                        option: \"UTF8\"\n                    },\n                    \"sha256\",\n                    false,\n                ],\n            }\n        ]\n    },\n\n]);\n"
  },
  {
    "path": "tests/operations/tests/FletcherChecksum.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Fletcher-16 Checksum: abcde\",\n        input: \"abcde\",\n        expectedOutput: \"c8f0\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-16 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-16 Checksum: abcdef\",\n        input: \"abcdef\",\n        expectedOutput: \"2057\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-16 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-16 Checksum: abcdefgh\",\n        input: \"abcdefgh\",\n        expectedOutput: \"0627\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-16 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-32 Checksum: abcde\",\n        input: \"abcde\",\n        expectedOutput: \"f04fc729\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-32 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-32 Checksum: abcdef\",\n        input: \"abcdef\",\n        expectedOutput: \"56502d2a\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-32 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-32 Checksum: abcdefgh\",\n        input: \"abcdefgh\",\n        expectedOutput: \"ebe19591\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-32 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-64 Checksum: abcde\",\n        input: \"abcde\",\n        expectedOutput: \"c8c6c527646362c6\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-64 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-64 Checksum: abcdef\",\n        input: \"abcdef\",\n        expectedOutput: \"c8c72b276463c8c6\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-64 Checksum\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Fletcher-64 Checksum: abcdefgh\",\n        input: \"abcdefgh\",\n        expectedOutput: \"312e2b28cccac8c6\",\n        recipeConfig: [\n            {\n                op: \"Fletcher-64 Checksum\",\n                args: [],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Float.mjs",
    "content": "/**\n * Float tests.\n *\n * @author tcode2k16 [tcode2k16@gmail.com]\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n\nTestRegister.addTests([\n    {\n        name: \"To Float: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Auto\"]\n            },\n            {\n                op: \"To Float\",\n                args: [\"Big Endian\", \"Float (4 bytes)\", \"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"To Float (Big Endian, 4 bytes): 0.5\",\n        input: \"3f0000003f000000\",\n        expectedOutput: \"0.5 0.5\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Auto\"]\n            },\n            {\n                op: \"To Float\",\n                args: [\"Big Endian\", \"Float (4 bytes)\", \"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"To Float (Little Endian, 4 bytes): 0.5\",\n        input: \"0000003f0000003f\",\n        expectedOutput: \"0.5 0.5\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Auto\"]\n            },\n            {\n                op: \"To Float\",\n                args: [\"Little Endian\", \"Float (4 bytes)\", \"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"To Float (Big Endian, 8 bytes): 0.5\",\n        input: \"3fe00000000000003fe0000000000000\",\n        expectedOutput: \"0.5 0.5\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Auto\"]\n            },\n            {\n                op: \"To Float\",\n                args: [\"Big Endian\", \"Double (8 bytes)\", \"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"To Float (Little Endian, 8 bytes): 0.5\",\n        input: \"000000000000e03f000000000000e03f\",\n        expectedOutput: \"0.5 0.5\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Auto\"]\n            },\n            {\n                op: \"To Float\",\n                args: [\"Little Endian\", \"Double (8 bytes)\", \"Space\"]\n            }\n        ]\n    },\n    {\n        name: \"From Float: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Float\",\n                args: [\"Big Endian\", \"Float (4 bytes)\", \"Space\"]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"From Float (Big Endian, 4 bytes): 0.5\",\n        input: \"0.5 0.5\",\n        expectedOutput: \"3f0000003f000000\",\n        recipeConfig: [\n            {\n                op: \"From Float\",\n                args: [\"Big Endian\", \"Float (4 bytes)\", \"Space\"]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"From Float (Little Endian, 4 bytes): 0.5\",\n        input: \"0.5 0.5\",\n        expectedOutput: \"0000003f0000003f\",\n        recipeConfig: [\n            {\n                op: \"From Float\",\n                args: [\"Little Endian\", \"Float (4 bytes)\", \"Space\"]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"From Float (Big Endian, 8 bytes): 0.5\",\n        input: \"0.5 0.5\",\n        expectedOutput: \"3fe00000000000003fe0000000000000\",\n        recipeConfig: [\n            {\n                op: \"From Float\",\n                args: [\"Big Endian\", \"Double (8 bytes)\", \"Space\"]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"From Float (Little Endian, 8 bytes): 0.5\",\n        input: \"0.5 0.5\",\n        expectedOutput: \"000000000000e03f000000000000e03f\",\n        recipeConfig: [\n            {\n                op: \"From Float\",\n                args: [\"Little Endian\", \"Double (8 bytes)\", \"Space\"]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Fork.mjs",
    "content": "/**\n * Fork tests\n *\n * @author tlwr [toby@toby.codes]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Fork: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Fork\",\n                args: [\"\\n\", \"\\n\", false],\n            },\n        ],\n    },\n    {\n        name: \"Fork, Merge: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Fork\",\n                args: [\"\\n\", \"\\n\", false],\n            },\n            {\n                op: \"Merge\",\n                args: [true],\n            },\n        ],\n    },\n    {\n        name: \"Fork, (expect) Error, Merge\",\n        input: \"1,2,3,4\\n\\n3,4,5,6\",\n        expectedOutput: \"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\",\n        recipeConfig: [\n            {\n                op: \"Fork\",\n                args: [\"\\n\\n\", \"\\n\\n\", false],\n            },\n            {\n                op: \"Set Union\",\n                args: [\"\\n\\n\", \",\"],\n            },\n            {\n                op: \"Merge\",\n                args: [true],\n            },\n        ],\n    },\n    {\n        name: \"Fork, Conditional Jump, Encodings\",\n        input: \"Some data with a 1 in it\\nSome data with a 2 in it\",\n        expectedOutput: \"U29tZSBkYXRhIHdpdGggYSAxIGluIGl0\\n53 6f 6d 65 20 64 61 74 61 20 77 69 74 68 20 61 20 32 20 69 6e 20 69 74\",\n        recipeConfig: [\n            {\"op\": \"Fork\", \"args\": [\"\\\\n\", \"\\\\n\", false]},\n            {\"op\": \"Conditional Jump\", \"args\": [\"1\", false, \"skipReturn\", \"10\"]},\n            {\"op\": \"To Hex\", \"args\": [\"Space\"]},\n            {\"op\": \"Return\", \"args\": []},\n            {\"op\": \"Label\", \"args\": [\"skipReturn\"]},\n            {\"op\": \"To Base64\", \"args\": [\"A-Za-z0-9+/=\"]}\n        ]\n    },\n    {\n        name: \"Fork, Partial Merge\",\n        input: \"Hello World\",\n        expectedOutput: \"48656c6c6f 576f726c64\",\n        recipeConfig: [\n            { \"op\": \"Fork\",   \"args\": [\" \", \" \", false] },\n            { \"op\": \"Fork\",   \"args\": [\"l\", \"l\", false] },\n            { \"op\": \"Merge\",  \"args\": [false] },\n            { \"op\": \"To Hex\", \"args\": [\"None\", 0] },\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/FromDecimal.mjs",
    "content": "/**\n * From Decimal tests\n *\n * @author qistoph\n * @copyright Crown Copyright 2018\n * @licence Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"From Decimal\",\n        input: \"83 97 109 112 108 101 32 84 101 120 116\",\n        expectedOutput: \"Sample Text\",\n        recipeConfig: [\n            {\n                op: \"From Decimal\",\n                args: [\"Space\", false]\n            },\n        ],\n    },\n    {\n        name: \"From Decimal with negatives\",\n        input: \"-130,-140,-152,-151,115,33,0,-1\",\n        expectedOutput: \"~this!\\u0000\\u00ff\",\n        recipeConfig: [\n            {\n                op: \"From Decimal\",\n                args: [\"Comma\", true]\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/GOST.mjs",
    "content": "/**\n * GOST tests.\n *\n * The GOST library already includes a range of tests for the correctness of\n * the algorithms. These tests are intended only to confirm that the library\n * has been correctly integrated into CyberChef.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"GOST Encrypt: 1989\",\n        input: \"Hello, World!\",\n        expectedOutput: \"f124ac5c0853870906dbaf9b56\",\n        recipeConfig: [\n            {\n                op: \"GOST Encrypt\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"00112233\" },\n                    { \"option\": \"Hex\", \"string\": \"0011223344556677\" },\n                    \"Raw\",\n                    \"Hex\",\n                    \"GOST 28147 (1989)\",\n                    \"E-SC\",\n                    \"OFB\",\n                    \"CP\",\n                    \"ZERO\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"GOST Encrypt: Kuznyechik\",\n        input: \"Hello, World!\",\n        expectedOutput: \"8673d490dfa4a66d5e3ff00ba316724f\",\n        recipeConfig: [\n            {\n                op: \"GOST Encrypt\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"00112233\" },\n                    { \"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\" },\n                    \"Raw\",\n                    \"Hex\",\n                    \"GOST R 34.12 (Kuznyechik, 2015)\",\n                    \"E-SC\",\n                    \"CBC\",\n                    \"CP\",\n                    \"PKCS5\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"GOST Decrypt: 1989\",\n        input: \"f124ac5c0853870906dbaf9b56\",\n        expectedOutput: \"Hello, World!\",\n        recipeConfig: [\n            {\n                op: \"GOST Decrypt\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"00112233\" },\n                    { \"option\": \"Hex\", \"string\": \"0011223344556677\" },\n                    \"Hex\",\n                    \"Raw\",\n                    \"GOST 28147 (1989)\",\n                    \"E-SC\",\n                    \"OFB\",\n                    \"CP\",\n                    \"ZERO\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"GOST Decrypt: Kuznyechik\",\n        input: \"8673d490dfa4a66d5e3ff00ba316724f\",\n        expectedOutput: \"Hello, World!\\0\\0\\0\",\n        recipeConfig: [\n            {\n                op: \"GOST Decrypt\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"00112233\" },\n                    { \"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\" },\n                    \"Hex\",\n                    \"Raw\",\n                    \"GOST R 34.12 (Kuznyechik, 2015)\",\n                    \"E-TEST\",\n                    \"CBC\",\n                    \"CP\",\n                    \"PKCS5\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"GOST Sign\",\n        input: \"Hello, World!\",\n        expectedOutput: \"810d0c40e965\",\n        recipeConfig: [\n            {\n                op: \"GOST Sign\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"00112233\" },\n                    { \"option\": \"Hex\", \"string\": \"0011223344556677\" },\n                    \"Raw\",\n                    \"Hex\",\n                    \"GOST 28147 (1989)\",\n                    \"E-C\",\n                    48\n                ]\n            }\n        ],\n    },\n    {\n        name: \"GOST Verify\",\n        input: \"Hello, World!\",\n        expectedOutput: \"The signature matches\",\n        recipeConfig: [\n            {\n                op: \"GOST Verify\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"00112233\" },\n                    { \"option\": \"Hex\", \"string\": \"00112233445566778899aabbccddeeff\" },\n                    { \"option\": \"Hex\", \"string\": \"42b77fb3d6f6bf04\" },\n                    \"Raw\",\n                    \"GOST R 34.12 (Kuznyechik, 2015)\",\n                    \"E-TEST\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"GOST Key Wrap\",\n        input: \"Hello, World!123\",\n        expectedOutput: \"0bb706e92487fceef97589911faeb28200000000000000000000000000000000\\r\\n6b7bfd16\",\n        recipeConfig: [\n            {\n                op: \"GOST Key Wrap\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"00112233\" },\n                    { \"option\": \"Hex\", \"string\": \"0011223344556677\" },\n                    \"Raw\",\n                    \"Hex\",\n                    \"GOST R 34.12 (Magma, 2015)\",\n                    \"E-TEST\",\n                    \"CP\"\n                ]\n            }\n        ],\n    },\n    {\n        name: \"GOST Key Unwrap\",\n        input: \"c8e58458a42d21974d50103d59b469f2c8e58458a42d21974d50103d59b469f2\\r\\na32a1575\",\n        expectedOutput: \"0123456789abcdef0123456789abcdef\",\n        recipeConfig: [\n            {\n                op: \"GOST Key Unwrap\",\n                args: [\n                    { \"option\": \"Hex\", \"string\": \"\" },\n                    { \"option\": \"Latin1\", \"string\": \"00112233\" },\n                    \"Hex\",\n                    \"Raw\",\n                    \"GOST 28147 (1989)\",\n                    \"E-Z\",\n                    \"CP\"\n                ]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/GenerateAllChecksums.mjs",
    "content": "/**\n * GenerateAllChecksums tests.\n *\n * @author r4mos [2k95ljkhg@mozmail.com]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst CHECK_STRING = \"123456789\";\n\nTestRegister.addTests([\n    {\n        name: \"Full generate all checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-3/GSM:                4\nCRC-3/ROHC:               6\nCRC-4/G-704:              7\nCRC-4/INTERLAKEN:         b\nCRC-4/ITU:                7\nCRC-5/EPC:                00\nCRC-5/EPC-C1G2:           00\nCRC-5/G-704:              07\nCRC-5/ITU:                07\nCRC-5/USB:                19\nCRC-6/CDMA2000-A:         0d\nCRC-6/CDMA2000-B:         3b\nCRC-6/DARC:               26\nCRC-6/G-704:              06\nCRC-6/GSM:                13\nCRC-6/ITU:                06\nCRC-7/MMC:                75\nCRC-7/ROHC:               53\nCRC-7/UMTS:               61\nCRC-8:                    f4\nCRC-8/8H2F:               df\nCRC-8/AES:                97\nCRC-8/AUTOSAR:            df\nCRC-8/BLUETOOTH:          26\nCRC-8/CDMA2000:           da\nCRC-8/DARC:               15\nCRC-8/DVB-S2:             bc\nCRC-8/EBU:                97\nCRC-8/GSM-A:              37\nCRC-8/GSM-B:              94\nCRC-8/HITAG:              b4\nCRC-8/I-432-1:            a1\nCRC-8/I-CODE:             7e\nCRC-8/ITU:                a1\nCRC-8/LTE:                ea\nCRC-8/MAXIM:              a1\nCRC-8/MAXIM-DOW:          a1\nCRC-8/MIFARE-MAD:         99\nCRC-8/NRSC-5:             f7\nCRC-8/OPENSAFETY:         3e\nCRC-8/ROHC:               d0\nCRC-8/SAE-J1850:          4b\nCRC-8/SAE-J1850-ZERO:     37\nCRC-8/SMBUS:              f4\nCRC-8/TECH-3250:          97\nCRC-8/WCDMA:              25\nFletcher-8:               0c\nCRC-10/ATM:               199\nCRC-10/CDMA2000:          233\nCRC-10/GSM:               12a\nCRC-10/I-610:             199\nCRC-11/FLEXRAY:           5a3\nCRC-11/UMTS:              061\nCRC-12/3GPP:              daf\nCRC-12/CDMA2000:          d4d\nCRC-12/DECT:              f5b\nCRC-12/GSM:               b34\nCRC-12/UMTS:              daf\nCRC-13/BBC:               04fa\nCRC-14/DARC:              082d\nCRC-14/GSM:               30ae\nCRC-15/CAN:               059e\nCRC-15/MPT1327:           2566\nCRC-16:                   bb3d\nCRC-16/A:                 bf05\nCRC-16/ACORN:             31c3\nCRC-16/ARC:               bb3d\nCRC-16/AUG-CCITT:         e5cc\nCRC-16/AUTOSAR:           29b1\nCRC-16/B:                 906e\nCRC-16/BLUETOOTH:         2189\nCRC-16/BUYPASS:           fee8\nCRC-16/CCITT:             2189\nCRC-16/CCITT-FALSE:       29b1\nCRC-16/CCITT-TRUE:        2189\nCRC-16/CCITT-ZERO:        31c3\nCRC-16/CDMA2000:          4c06\nCRC-16/CMS:               aee7\nCRC-16/DARC:              d64e\nCRC-16/DDS-110:           9ecf\nCRC-16/DECT-R:            007e\nCRC-16/DECT-X:            007f\nCRC-16/DNP:               ea82\nCRC-16/EN-13757:          c2b7\nCRC-16/EPC:               d64e\nCRC-16/EPC-C1G2:          d64e\nCRC-16/GENIBUS:           d64e\nCRC-16/GSM:               ce3c\nCRC-16/I-CODE:            d64e\nCRC-16/IBM:               bb3d\nCRC-16/IBM-3740:          29b1\nCRC-16/IBM-SDLC:          906e\nCRC-16/IEC-61158-2:       a819\nCRC-16/ISO-HDLC:          906e\nCRC-16/ISO-IEC-14443-3-A: bf05\nCRC-16/ISO-IEC-14443-3-B: 906e\nCRC-16/KERMIT:            2189\nCRC-16/LHA:               bb3d\nCRC-16/LJ1200:            bdf4\nCRC-16/LTE:               31c3\nCRC-16/M17:               772b\nCRC-16/MAXIM:             44c2\nCRC-16/MAXIM-DOW:         44c2\nCRC-16/MCRF4XX:           6f91\nCRC-16/MODBUS:            4b37\nCRC-16/NRSC-5:            a066\nCRC-16/OPENSAFETY-A:      5d38\nCRC-16/OPENSAFETY-B:      20fe\nCRC-16/PROFIBUS:          a819\nCRC-16/RIELLO:            63d0\nCRC-16/SPI-FUJITSU:       e5cc\nCRC-16/T10-DIF:           d0db\nCRC-16/TELEDISK:          0fb3\nCRC-16/TMS37157:          26b1\nCRC-16/UMTS:              fee8\nCRC-16/USB:               b4c8\nCRC-16/V-41-LSB:          2189\nCRC-16/V-41-MSB:          31c3\nCRC-16/VERIFONE:          fee8\nCRC-16/X-25:              906e\nCRC-16/XMODEM:            31c3\nCRC-16/ZMODEM:            31c3\nFletcher-16:              1ede\nCRC-17/CAN-FD:            04f03\nCRC-21/CAN-FD:            0ed841\nCRC-24/BLE:               c25a56\nCRC-24/FLEXRAY-A:         7979bd\nCRC-24/FLEXRAY-B:         1f23b8\nCRC-24/INTERLAKEN:        b4f3e6\nCRC-24/LTE-A:             cde703\nCRC-24/LTE-B:             23ef52\nCRC-24/OPENPGP:           21cf02\nCRC-24/OS-9:              200fa5\nCRC-30/CDMA:              04c34abf\nCRC-31/PHILIPS:           0ce9e46c\nAdler-32:                 091e01de\nCRC-32:                   cbf43926\nCRC-32/AAL5:              fc891918\nCRC-32/ADCCP:             cbf43926\nCRC-32/AIXM:              3010bf7f\nCRC-32/AUTOSAR:           1697d06a\nCRC-32/BASE91-C:          e3069283\nCRC-32/BASE91-D:          87315576\nCRC-32/BZIP2:             fc891918\nCRC-32/C:                 e3069283\nCRC-32/CASTAGNOLI:        e3069283\nCRC-32/CD-ROM-EDC:        6ec2edc4\nCRC-32/CKSUM:             765e7680\nCRC-32/D:                 87315576\nCRC-32/DECT-B:            fc891918\nCRC-32/INTERLAKEN:        e3069283\nCRC-32/ISCSI:             e3069283\nCRC-32/ISO-HDLC:          cbf43926\nCRC-32/JAMCRC:            340bc6d9\nCRC-32/MEF:               d2c22f51\nCRC-32/MPEG-2:            0376e6e7\nCRC-32/NVME:              e3069283\nCRC-32/PKZIP:             cbf43926\nCRC-32/POSIX:             765e7680\nCRC-32/Q:                 3010bf7f\nCRC-32/SATA:              cf72afe8\nCRC-32/V-42:              cbf43926\nCRC-32/XFER:              bd0be338\nCRC-32/XZ:                cbf43926\nFletcher-32:              df09d509\nCRC-40/GSM:               d4164fc646\nCRC-64/ECMA-182:          6c40df5f0b497347\nCRC-64/GO-ECMA:           995dc9bbdf1939fa\nCRC-64/GO-ISO:            b90956c775a41001\nCRC-64/MS:                75d4b74f024eceea\nCRC-64/NVME:              ae8b14860a799888\nCRC-64/REDIS:             e9c6d914c4b8d9ca\nCRC-64/WE:                62ec59e3f1a4f00a\nCRC-64/XZ:                995dc9bbdf1939fa\nFletcher-64:              0d0803376c6a689f\nCRC-82/DARC:              09ea83f625023801fd612\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"All\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate all checksums without name\",\n        input: CHECK_STRING,\n        expectedOutput: `4\n6\n7\nb\n7\n00\n00\n07\n07\n19\n0d\n3b\n26\n06\n13\n06\n75\n53\n61\nf4\ndf\n97\ndf\n26\nda\n15\nbc\n97\n37\n94\nb4\na1\n7e\na1\nea\na1\na1\n99\nf7\n3e\nd0\n4b\n37\nf4\n97\n25\n0c\n199\n233\n12a\n199\n5a3\n061\ndaf\nd4d\nf5b\nb34\ndaf\n04fa\n082d\n30ae\n059e\n2566\nbb3d\nbf05\n31c3\nbb3d\ne5cc\n29b1\n906e\n2189\nfee8\n2189\n29b1\n2189\n31c3\n4c06\naee7\nd64e\n9ecf\n007e\n007f\nea82\nc2b7\nd64e\nd64e\nd64e\nce3c\nd64e\nbb3d\n29b1\n906e\na819\n906e\nbf05\n906e\n2189\nbb3d\nbdf4\n31c3\n772b\n44c2\n44c2\n6f91\n4b37\na066\n5d38\n20fe\na819\n63d0\ne5cc\nd0db\n0fb3\n26b1\nfee8\nb4c8\n2189\n31c3\nfee8\n906e\n31c3\n31c3\n1ede\n04f03\n0ed841\nc25a56\n7979bd\n1f23b8\nb4f3e6\ncde703\n23ef52\n21cf02\n200fa5\n04c34abf\n0ce9e46c\n091e01de\ncbf43926\nfc891918\ncbf43926\n3010bf7f\n1697d06a\ne3069283\n87315576\nfc891918\ne3069283\ne3069283\n6ec2edc4\n765e7680\n87315576\nfc891918\ne3069283\ne3069283\ncbf43926\n340bc6d9\nd2c22f51\n0376e6e7\ne3069283\ncbf43926\n765e7680\n3010bf7f\ncf72afe8\ncbf43926\nbd0be338\ncbf43926\ndf09d509\nd4164fc646\n6c40df5f0b497347\n995dc9bbdf1939fa\nb90956c775a41001\n75d4b74f024eceea\nae8b14860a799888\ne9c6d914c4b8d9ca\n62ec59e3f1a4f00a\n995dc9bbdf1939fa\n0d0803376c6a689f\n09ea83f625023801fd612\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"All\", false]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 3 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-3/GSM:                4\nCRC-3/ROHC:               6\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"3\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 4 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-4/G-704:              7\nCRC-4/INTERLAKEN:         b\nCRC-4/ITU:                7\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"4\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 5 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-5/EPC:                00\nCRC-5/EPC-C1G2:           00\nCRC-5/G-704:              07\nCRC-5/ITU:                07\nCRC-5/USB:                19\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"5\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 6 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-6/CDMA2000-A:         0d\nCRC-6/CDMA2000-B:         3b\nCRC-6/DARC:               26\nCRC-6/G-704:              06\nCRC-6/GSM:                13\nCRC-6/ITU:                06\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"6\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 7 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-7/MMC:                75\nCRC-7/ROHC:               53\nCRC-7/UMTS:               61\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"7\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 8 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-8:                    f4\nCRC-8/8H2F:               df\nCRC-8/AES:                97\nCRC-8/AUTOSAR:            df\nCRC-8/BLUETOOTH:          26\nCRC-8/CDMA2000:           da\nCRC-8/DARC:               15\nCRC-8/DVB-S2:             bc\nCRC-8/EBU:                97\nCRC-8/GSM-A:              37\nCRC-8/GSM-B:              94\nCRC-8/HITAG:              b4\nCRC-8/I-432-1:            a1\nCRC-8/I-CODE:             7e\nCRC-8/ITU:                a1\nCRC-8/LTE:                ea\nCRC-8/MAXIM:              a1\nCRC-8/MAXIM-DOW:          a1\nCRC-8/MIFARE-MAD:         99\nCRC-8/NRSC-5:             f7\nCRC-8/OPENSAFETY:         3e\nCRC-8/ROHC:               d0\nCRC-8/SAE-J1850:          4b\nCRC-8/SAE-J1850-ZERO:     37\nCRC-8/SMBUS:              f4\nCRC-8/TECH-3250:          97\nCRC-8/WCDMA:              25\nFletcher-8:               0c\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"8\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 10 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-10/ATM:               199\nCRC-10/CDMA2000:          233\nCRC-10/GSM:               12a\nCRC-10/I-610:             199\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"10\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 11 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-11/FLEXRAY:           5a3\nCRC-11/UMTS:              061\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"11\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 12 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-12/3GPP:              daf\nCRC-12/CDMA2000:          d4d\nCRC-12/DECT:              f5b\nCRC-12/GSM:               b34\nCRC-12/UMTS:              daf\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"12\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 13 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-13/BBC:               04fa\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"13\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 14 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-14/DARC:              082d\nCRC-14/GSM:               30ae\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"14\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 15 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-15/CAN:               059e\nCRC-15/MPT1327:           2566\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"15\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 16 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-16:                   bb3d\nCRC-16/A:                 bf05\nCRC-16/ACORN:             31c3\nCRC-16/ARC:               bb3d\nCRC-16/AUG-CCITT:         e5cc\nCRC-16/AUTOSAR:           29b1\nCRC-16/B:                 906e\nCRC-16/BLUETOOTH:         2189\nCRC-16/BUYPASS:           fee8\nCRC-16/CCITT:             2189\nCRC-16/CCITT-FALSE:       29b1\nCRC-16/CCITT-TRUE:        2189\nCRC-16/CCITT-ZERO:        31c3\nCRC-16/CDMA2000:          4c06\nCRC-16/CMS:               aee7\nCRC-16/DARC:              d64e\nCRC-16/DDS-110:           9ecf\nCRC-16/DECT-R:            007e\nCRC-16/DECT-X:            007f\nCRC-16/DNP:               ea82\nCRC-16/EN-13757:          c2b7\nCRC-16/EPC:               d64e\nCRC-16/EPC-C1G2:          d64e\nCRC-16/GENIBUS:           d64e\nCRC-16/GSM:               ce3c\nCRC-16/I-CODE:            d64e\nCRC-16/IBM:               bb3d\nCRC-16/IBM-3740:          29b1\nCRC-16/IBM-SDLC:          906e\nCRC-16/IEC-61158-2:       a819\nCRC-16/ISO-HDLC:          906e\nCRC-16/ISO-IEC-14443-3-A: bf05\nCRC-16/ISO-IEC-14443-3-B: 906e\nCRC-16/KERMIT:            2189\nCRC-16/LHA:               bb3d\nCRC-16/LJ1200:            bdf4\nCRC-16/LTE:               31c3\nCRC-16/M17:               772b\nCRC-16/MAXIM:             44c2\nCRC-16/MAXIM-DOW:         44c2\nCRC-16/MCRF4XX:           6f91\nCRC-16/MODBUS:            4b37\nCRC-16/NRSC-5:            a066\nCRC-16/OPENSAFETY-A:      5d38\nCRC-16/OPENSAFETY-B:      20fe\nCRC-16/PROFIBUS:          a819\nCRC-16/RIELLO:            63d0\nCRC-16/SPI-FUJITSU:       e5cc\nCRC-16/T10-DIF:           d0db\nCRC-16/TELEDISK:          0fb3\nCRC-16/TMS37157:          26b1\nCRC-16/UMTS:              fee8\nCRC-16/USB:               b4c8\nCRC-16/V-41-LSB:          2189\nCRC-16/V-41-MSB:          31c3\nCRC-16/VERIFONE:          fee8\nCRC-16/X-25:              906e\nCRC-16/XMODEM:            31c3\nCRC-16/ZMODEM:            31c3\nFletcher-16:              1ede\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"16\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 17 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-17/CAN-FD:            04f03\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"17\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 21 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-21/CAN-FD:            0ed841\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"21\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 24 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-24/BLE:               c25a56\nCRC-24/FLEXRAY-A:         7979bd\nCRC-24/FLEXRAY-B:         1f23b8\nCRC-24/INTERLAKEN:        b4f3e6\nCRC-24/LTE-A:             cde703\nCRC-24/LTE-B:             23ef52\nCRC-24/OPENPGP:           21cf02\nCRC-24/OS-9:              200fa5\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"24\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 30 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-30/CDMA:              04c34abf\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"30\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 31 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-31/PHILIPS:           0ce9e46c\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"31\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 32 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `Adler-32:                 091e01de\nCRC-32:                   cbf43926\nCRC-32/AAL5:              fc891918\nCRC-32/ADCCP:             cbf43926\nCRC-32/AIXM:              3010bf7f\nCRC-32/AUTOSAR:           1697d06a\nCRC-32/BASE91-C:          e3069283\nCRC-32/BASE91-D:          87315576\nCRC-32/BZIP2:             fc891918\nCRC-32/C:                 e3069283\nCRC-32/CASTAGNOLI:        e3069283\nCRC-32/CD-ROM-EDC:        6ec2edc4\nCRC-32/CKSUM:             765e7680\nCRC-32/D:                 87315576\nCRC-32/DECT-B:            fc891918\nCRC-32/INTERLAKEN:        e3069283\nCRC-32/ISCSI:             e3069283\nCRC-32/ISO-HDLC:          cbf43926\nCRC-32/JAMCRC:            340bc6d9\nCRC-32/MEF:               d2c22f51\nCRC-32/MPEG-2:            0376e6e7\nCRC-32/NVME:              e3069283\nCRC-32/PKZIP:             cbf43926\nCRC-32/POSIX:             765e7680\nCRC-32/Q:                 3010bf7f\nCRC-32/SATA:              cf72afe8\nCRC-32/V-42:              cbf43926\nCRC-32/XFER:              bd0be338\nCRC-32/XZ:                cbf43926\nFletcher-32:              df09d509\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"32\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 40 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-40/GSM:               d4164fc646\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"40\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 64 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-64/ECMA-182:          6c40df5f0b497347\nCRC-64/GO-ECMA:           995dc9bbdf1939fa\nCRC-64/GO-ISO:            b90956c775a41001\nCRC-64/MS:                75d4b74f024eceea\nCRC-64/NVME:              ae8b14860a799888\nCRC-64/REDIS:             e9c6d914c4b8d9ca\nCRC-64/WE:                62ec59e3f1a4f00a\nCRC-64/XZ:                995dc9bbdf1939fa\nFletcher-64:              0d0803376c6a689f\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"64\", true]\n            }\n        ]\n    },\n    {\n        name: \"Full generate 82 bits checksums with name\",\n        input: CHECK_STRING,\n        expectedOutput: `CRC-82/DARC:              09ea83f625023801fd612\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all checksums\",\n                \"args\": [\"82\", true]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/GenerateAllHashes.mjs",
    "content": "/**\n * GenerateAllHashes tests.\n *\n * @author john19696 [john19696@protonmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Full generate all hashes\",\n        input: \"test\",\n        expectedOutput: `MD2:          dd34716876364a02d0195e2fb9ae2d1b\nMD4:          db346d691d7acc4dc2625db19f9e3f52\nMD5:          098f6bcd4621d373cade4e832627b4f6\nMD6:          93c8a7d0ff132f325138a82b2baa98c12a7c9ac982feb6c5b310a1ca713615bd\nSHA0:         f8d3b312442a67706057aeb45b983221afb4f035\nSHA1:         a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\nSHA2 224:     90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809\nSHA2 256:     9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\nSHA2 384:     768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9\nSHA2 512:     ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff\nSHA3 224:     3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b\nSHA3 256:     36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80\nSHA3 384:     e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd\nSHA3 512:     9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14\nKeccak 224:   3be30a9ff64f34a5861116c5198987ad780165f8366e67aff4760b5e\nKeccak 256:   9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658\nKeccak 384:   53d0ba137307d4c2f9b6674c83edbd58b70c0f4340133ed0adc6fba1d2478a6a03b7788229e775d2de8ae8c0759d0527\nKeccak 512:   1e2e9fc2002b002d75198b7503210c05a1baac4560916a3c6d93bcce3a50d7f00fd395bf1647b9abb8d1afcc9c76c289b0c9383ba386a956da4b38934417789e\nShake 128:    d3b0aa9cd8b7255622cebc631e867d4093d6f6010191a53973c45fec9b07c774\nShake 256:    b54ff7255705a71ee2925e4a3e30e41aed489a579d5595e0df13e32e1e4dd202a7c7f68b31d6418d9845eb4d757adda6ab189e1bb340db818e5b3bc725d992fa\nRIPEMD-128:   f1abb5083c9ff8a9dbbca9cd2b11fead\nRIPEMD-160:   5e52fee47e6b070565f74372468cdc699de89107\nRIPEMD-256:   fe0289110d07daeee9d9500e14c57787d9083f6ba10e6bcb256f86bb4fe7b981\nRIPEMD-320:   3b0a2e841e589cf583634a5dd265d2b5d497c4cc44b241e34e0f62d03e98c1b9dc72970b9bc20eb5\nHAS-160:      cb15e491eec6e769771d1f811315139c93071084\nWhirlpool-0:  d50ff71342b521974bae166539871922669afcfc7181250ebbae015c317ebb797173a69e7a05afd11099a9f0918159cd5bc88434d3ca44513d7263caea9244fe\nWhirlpool-T:  e6b4aa087751b4428171777f1893ba585404c7e0171787720eba0d8bccd710dc2c42f874c572bfae4cedabf50f2c80bf923805d4e31c504b86ca3bc59265e7dd\nWhirlpool:    b913d5bbb8e461c2c5961cbe0edcdadfd29f068225ceb37da6defcf89849368f8c6c2eb6a4c4ac75775d032a0ecfdfe8550573062b653fe92fc7b8fb3b7be8d6\nBLAKE2b-128:  44a8995dd50b6657a037a7839304535b\nBLAKE2b-160:  a34fc3b6d2cce8beb3216c2bbb5e55739e8121ed\nBLAKE2b-256:  928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202\nBLAKE2b-384:  8a84b8666c8fcfb69f2ec41f578d7c85fbdb504ea6510fb05b50fcbf7ed8153c77943bc2da73abb136834e1a0d4f22cb\nBLAKE2b-512:  a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a896c0411f38312e1d66e0bf16386c86a89bea572\nBLAKE2s-128:  e9ddd9926b9dcb382e09be39ba403d2c\nBLAKE2s-160:  d6197dabec2bd6f4ff303b8e519e8f15d42a453d\nBLAKE2s-256:  f308fc02ce9172ad02a7d75800ecfc027109bc67987ea32aba9b8dcc7b10150e\nStreebog-256: 12a50838191b5504f1e5f2fd078714cf6b592b9d29af99d0b10d8d02881c3857\nStreebog-512: 7200bf5dea560f0d7960d07fdc8874ad9f3b86ece2e45f5502ae2e176f2c928e0e581152281f5aee818318bed7cbe6aa69999589234723ceb33175598365b5c8\nGOST:         ee67303696d205ddd2b2363e8e01b4b7199a80957d94d7678eaad3fc834c5a27\nLM Hash:      01FC5A6BE7BC6929AAD3B435B51404EE\nNT Hash:      0CB6948805F797BF2A82807973B89537\nSSDEEP:       3:Hn:Hn\nCTPH:         A:E:E\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all hashes\",\n                \"args\": [\"All\", true]\n            }\n        ]\n    },\n    {\n        name: \"Hashes with length 32\",\n        input: \"test\",\n        expectedOutput: `MD2:          dd34716876364a02d0195e2fb9ae2d1b\nMD4:          db346d691d7acc4dc2625db19f9e3f52\nMD5:          098f6bcd4621d373cade4e832627b4f6\nRIPEMD-128:   f1abb5083c9ff8a9dbbca9cd2b11fead\nBLAKE2b-128:  44a8995dd50b6657a037a7839304535b\nBLAKE2s-128:  e9ddd9926b9dcb382e09be39ba403d2c\nLM Hash:      01FC5A6BE7BC6929AAD3B435B51404EE\nNT Hash:      0CB6948805F797BF2A82807973B89537\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all hashes\",\n                \"args\": [\"128\", true]\n            }\n        ]\n    },\n    {\n        name: \"Hashes without names\",\n        input: \"test\",\n        expectedOutput: `93c8a7d0ff132f325138a82b2baa98c12a7c9ac982feb6c5b310a1ca713615bd\n9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80\n9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658\nd3b0aa9cd8b7255622cebc631e867d4093d6f6010191a53973c45fec9b07c774\nfe0289110d07daeee9d9500e14c57787d9083f6ba10e6bcb256f86bb4fe7b981\n928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202\nf308fc02ce9172ad02a7d75800ecfc027109bc67987ea32aba9b8dcc7b10150e\n12a50838191b5504f1e5f2fd078714cf6b592b9d29af99d0b10d8d02881c3857\nee67303696d205ddd2b2363e8e01b4b7199a80957d94d7678eaad3fc834c5a27\n`,\n        recipeConfig: [\n            {\n                \"op\": \"Generate all hashes\",\n                \"args\": [\"256\", false]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/GenerateDeBruijnSequence.mjs",
    "content": "/**\n * De Brujin Sequence tests.\n *\n * @author gchq77703 [gchq77703@gchq.gov.uk]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Generate De Bruijn Sequence: Small Sequence\",\n        input: \"\",\n        expectedOutput: \"00010111\",\n        recipeConfig: [\n            {\n                \"op\": \"Generate De Bruijn Sequence\",\n                \"args\": [2, 3]\n            }\n        ]\n    },\n    {\n        name: \"Generate De Bruijn Sequence: Long Sequence\",\n        input: \"\",\n        expectedOutput: \"0000010000200003000110001200013000210002200023000310003200033001010010200103001110011200113001210012200123001310013200133002010020200203002110021200213002210022200223002310023200233003010030200303003110031200313003210032200323003310033200333010110101201013010210102201023010310103201033011020110301111011120111301121011220112301131011320113301202012030121101212012130122101222012230123101232012330130201303013110131201313013210132201323013310133201333020210202202023020310203202033021030211102112021130212102122021230213102132021330220302211022120221302221022220222302231022320223302303023110231202313023210232202323023310233202333030310303203033031110311203113031210312203123031310313203133032110321203213032210322203223032310323203233033110331203313033210332203323033310333203333111112111131112211123111321113311212112131122211223112321123311312113131132211323113321133312122121231213212133122131222212223122321223312313123221232312332123331313213133132221322313232132331332213323133321333322222322233223232233323233233333\",\n        recipeConfig: [\n            {\n                \"op\": \"Generate De Bruijn Sequence\",\n                \"args\": [4, 5]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/GenerateQRCode.mjs",
    "content": "/**\n * Generate QR Code tests\n *\n * @author GCHQDeveloper581\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Generate QR Code : PNG\",\n        input: \"Hello world!\",\n        expectedOutput: \"89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 91 00 00 00 91 08 00 00 00 00 e6 b3 05 ff 00 00 01 1a 49 44 41 54 78 da ed da 41 12 83 20 0c 05 50 ef 7f e9 76 dd 05 f4 47 6c c4 ce 63 e5 8c 0c be 4d 24 24 1c af dd c6 41 44 44 44 44 44 44 44 44 44 f4 9f a2 e3 fb 98 cf 2b ad 42 44 d4 2a 1a 07 c3 e7 37 83 a7 d2 37 88 88 1a 44 c3 18 1a 46 e7 ca 2a 44 44 7b 88 4a f3 88 88 1e 23 9a ef 09 44 44 fb 8a 82 b7 c3 3c fe 8e 8c 8d 88 e8 b2 33 6d ba b3 f4 9d b2 89 88 16 eb 90 f3 a5 ef a8 8c 12 11 55 f3 a3 61 a2 93 e6 4c c3 45 89 88 ba 44 a5 e4 e7 64 5d 9d 88 a8 5f 14 74 82 d2 8a 64 5a b4 24 22 6a 10 a5 2d cf 79 3f e9 6c 53 89 88 e8 a7 a2 79 4f a8 b4 b3 ac 57 6b 88 88 ae 15 a5 de b9 bc 14 70 44 44 3f 13 ad d4 21 03 ea d9 6a 0d 11 d1 15 a2 e0 ff 5f 47 07 36 22 a2 06 d1 4a d2 1f 1c 94 89 88 b6 14 95 ee d5 12 11 3d 50 14 74 8c 82 70 24 22 ea 12 05 6f d3 4b 2c 4b d5 1a 22 a2 cb 44 2b 69 7d e9 5e 00 11 51 97 e8 d6 41 44 44 44 44 44 44 44 44 44 f4 7c d1 1b 1c 52 72 cb 26 c8 c7 0b 00 00 00 00 49 45 4e 44 ae 42 60 82\",\n        recipeConfig: [\n            {\n                \"op\": \"Generate QR Code\",\n                \"args\": [\"PNG\", 5, 4, \"Medium\"]\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\"Space\", 0]\n            }\n        ],\n    },\n    {\n        name: \"Generate QR Code : SVG\",\n        input: \"Hello world!\",\n        expectedOutput: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"145\" height=\"145\" viewBox=\"0 0 29 29\"><path d=\"M4 4h7v7h-7zM12 4h5v2h-1v-1h-1v1h1v1h1v1h-2v3h-1v-1h-1v-1h1v-4h-1v1h-1zM18 4h7v7h-7zM5 5v5h5v-5zM19 5v5h5v-5zM6 6h3v3h-3zM20 6h3v3h-3zM12 7h1v1h-1zM12 10h1v1h1v1h-2zM16 10h1v1h-1zM4 12h1v1h-1zM6 12h2v2h-1v1h1v-1h3v1h-1v1h-2v1h-2v-1h-1v-1h-1v-1h2zM9 12h3v2h-1v-1h-2zM14 12h1v2h-2v-1h1zM18 12h1v1h2v-1h1v1h1v1h-3v2h-1v-2h-1v1h-1v1h-1v-3h2zM23 12h2v3h-1v1h-1v-2h1v-1h-1zM12 14h1v1h-1zM11 15h1v2h-2v-1h1zM14 15h1v1h-1zM21 15h1v1h-1zM15 16h1v2h-1v1h2v-1h1v-1h-1v-1h2v1h2v2h-1v1h-1v-1h-1v1h-2v1h-1v-1h-1v1h-1v-1h-1v-3h3zM24 16h1v2h-1zM22 17h1v1h-1zM4 18h7v7h-7zM13 18v1h1v-1zM5 19v5h5v-5zM21 19h2v1h1v2h-1v1h-1v-1h-1zM6 20h3v3h-3zM12 21h1v2h-1zM14 21h1v1h-1zM16 21h1v2h-1zM18 21h1v1h-1zM19 22h1v1h1v1h-1v1h-3v-2h2zM13 23h1v1h-1zM15 23h1v2h-2v-1h1zM24 23h1v1h-1zM12 24h1v1h-1zM22 24h1v1h-1z\"/></svg>',\n        recipeConfig: [\n            {\n                \"op\": \"Generate QR Code\",\n                \"args\": [\"SVG\", 5, 4, \"Medium\"]\n            },\n        ],\n    },\n    {\n        name: \"Generate QR Code : EPS\",\n        input: \"Hello world!\",\n        expectedOutput: \"%!PS-Adobe-3.0 EPSF-3.0%%BoundingBox: 0 0 315 315/h { 0 rlineto } bind def/v { 0 exch neg rlineto } bind def/M { neg 30 add moveto } bind def/z { closepath } bind def9 9 scale5 0 M 7 h 7 v -7 h z13 0 M 1 h 1 v -1 h z16 0 M 2 h 1 v -1 h 1 v 1 h 1 v -2 h -1 v -1 h 2 v -1 h -3 v 2 h z20 0 M 2 h 1 v -2 h z23 0 M 7 h 7 v -7 h z6 1 M 5 v 5 h -5 v z24 1 M 5 v 5 h -5 v z7 2 M 3 h 3 v -3 h z19 2 M 1 h 1 v -1 h z21 2 M 1 h 2 v -1 h z25 2 M 3 h 3 v -3 h z13 4 M 1 h 3 v -1 h z17 4 M 4 h 1 v 1 h 2 v -1 h -1 v -1 h 1 v -1 h -1 v -1 h -1 v -1 h z15 6 M 1 h 1 v -1 h z17 6 M 1 h 1 v -1 h z16 7 M 1 h 1 v -1 h z18 7 M 1 h 1 v -1 h z20 7 M 1 h 2 v 1 h 1 v -2 h -1 v -1 h -1 v 1 h z6 8 M 7 h 1 v 2 h 1 v -2 h 1 v -2 h -1 v 1 h -1 v -4 h 1 v -1 h -1 v -1 h z17 8 M 1 h 1 v -1 h z24 8 M 2 h 1 v -1 h 1 v -1 h z29 8 M 1 h 1 v -1 h z27 9 M 1 h 1 v -1 h z6 10 M 1 h 1 v 1 h 1 v -1 h 1 v -1 h z8 10 M 2 h 5 v 1 h 1 v -3 h 1 v -1 h -4 v 1 h 1 v 1 h -1 v -1 h -1 v 1 h -1 v -1 h z16 10 M 2 h 4 v 1 h -1 v 2 h 1 v 3 h -1 v -3 h -2 v 1 h 1 v 1 h -1 v 1 h 1 v 4 h -2 v 2 h 3 v -2 h 1 v -1 h -1 v -2 h 1 v 2 h 1 v -1 h 1 v 2 h 2 v 1 h -1 v 1 h 3 v -1 h -1 v -2 h -2 v -1 h 2 v 1 h 1 v 2 h 1 v -2 h 2 v -1 h -1 v -1 h -1 v -1 h 1 v -1 h -1 v -1 h 2 v -1 h -1 v -1 h -1 v 1 h -2 v -1 h -1 v -1 h 1 v 1 h 2 v -1 h -1 v -1 h 1 v -1 h -3 v 1 h -1 v -1 h 1 v -1 h -1 v -1 h -1 v 4 h 1 v 1 h -2 v -3 h -1 v -1 h 1 v -1 h -1 v -1 h 2 v -1 h 1 v -2 h -1 v 1 h -1 v -1 h -1 v 1 h -1 v 1 h -1 v 1 h 1 v 1 h -1 v 1 h 1 v 1 h -1 v -1 h z22 10 M 1 h 1 v -1 h z25 10 M 2 h 1 v -2 h z19 11 M 1 h 1 v -1 h z11 12 M 1 h 1 v -1 h z5 13 M 1 h 4 v -1 h z28 14 M 2 h 2 v -1 h -1 v -1 h z21 15 M 1 v 2 h -1 v z13 17 M 1 h 1 v 2 h 1 v -2 h 1 v 1 h 1 v 1 h 1 v -2 h 1 v 2 h 2 v -1 h -1 v -2 h z22 17 M 3 v 3 h -3 v z5 18 M 7 h 7 v -7 h z23 18 M 1 h 1 v -1 h z6 19 M 5 v 5 h -5 v z7 20 M 3 h 3 v -3 h z29 21 M 1 h 4 v -3 h -1 v 2 h z17 22 M 2 h 3 v -2 h -1 v 1 h -1 v -1 h z24 22 M 1 h 1 v 1 h 1 v -1 h 1 v -3 h -1 v 1 h -1 v 1 h z20 23 M 1 h 2 v -1 h zfill%%EOF\",\n        recipeConfig: [\n            {\n                \"op\": \"Generate QR Code\",\n                \"args\": [\"EPS\", 6, 5, \"Quartile\"]\n            },\n            {\n                \"op\": \"Remove whitespace\",\n                \"args\": [false, true, true, false, false, false]\n            },\n        ],\n    },\n    {\n        name: \"Generate QR Code : PDF\",\n        input: \"Hello world!\",\n        expectedOutput: \"%PDF-1.01 0 obj << /Type /Catalog /Pages 2 0 R >> endobj2 0 obj << /Type /Pages /Count 1 /Kids [ 3 0 R ] >> endobj3 0 obj << /Type /Page /Parent 2 0 R /Resources <<>> /Contents 4 0 R /MediaBox [ 0 0 261 261 ] >> endobj4 0 obj << /Length 1837 >> stream9 0 0 9 0 0 cm4 25 m 11 25 l 11 18 l 4 18 l h12 25 m 14 25 l 14 23 l 13 23 l 13 24 l 12 24 l h16 25 m 17 25 l 17 22 l 16 22 l h18 25 m 25 25 l 25 18 l 18 18 l h5 24 m 5 19 l 10 19 l 10 24 l h19 24 m 19 19 l 24 19 l 24 24 l h6 23 m 9 23 l 9 20 l 6 20 l h12 23 m 13 23 l 13 21 l 15 21 l 15 20 l 12 20 l h14 23 m 15 23 l 15 22 l 14 22 l h20 23 m 23 23 l 23 20 l 20 20 l h15 22 m 16 22 l 16 21 l 15 21 l h12 19 m 13 19 l 13 18 l 12 18 l h14 19 m 15 19 l 15 13 l 13 13 l 13 11 l 12 11 l 12 14 l 14 14 l 14 15 l 11 15 l 11 16 l 12 16 l 12 17 l 13 17 l 13 16 l 14 16 l 14 17 l 13 17 l 13 18 l 14 18 l h16 19 m 17 19 l 17 18 l 16 18 l h4 17 m 8 17 l 8 16 l 10 16 l 10 15 l 11 15 l 11 14 l 10 14 l 10 13 l 11 13 l 11 12 l 9 12 l 9 15 l 8 15 l 8 13 l 6 13 l 6 15 l 7 15 l 7 16 l 4 16 l h10 17 m 11 17 l 11 16 l 10 16 l h17 17 m 18 17 l 18 16 l 20 16 l 20 17 l 23 17 l 23 15 l 20 15 l 20 13 l 19 13 l 19 15 l 18 15 l 18 14 l 17 14 l 17 13 l 16 13 l 16 16 l 17 16 l h24 17 m 25 17 l 25 14 l 24 14 l 24 13 l 23 13 l 23 15 l 24 15 l h21 14 m 22 14 l 22 13 l 21 13 l h15 13 m 16 13 l 16 11 l 15 11 l h17 13 m 19 13 l 19 12 l 21 12 l 21 10 l 20 10 l 20 9 l 19 9 l 19 10 l 18 10 l 18 9 l 16 9 l 16 8 l 15 8 l 15 10 l 17 10 l 17 11 l 18 11 l 18 12 l 17 12 l h24 13 m 25 13 l 25 11 l 24 11 l h22 12 m 23 12 l 23 11 l 22 11 l h4 11 m 11 11 l 11 4 l 4 4 l h14 11 m 15 11 l 15 10 l 14 10 l h5 10 m 5 5 l 10 5 l 10 10 l h13 10 m 14 10 l 14 9 l 13 9 l h21 10 m 23 10 l 23 9 l 24 9 l 24 7 l 23 7 l 23 6 l 22 6 l 22 7 l 21 7 l h6 9 m 9 9 l 9 6 l 6 6 l h12 8 m 15 8 l 15 7 l 13 7 l 13 6 l 16 6 l 16 4 l 15 4 l 15 5 l 14 5 l 14 4 l 12 4 l h16 8 m 17 8 l 17 6 l 16 6 l h18 8 m 19 8 l 19 7 l 18 7 l h19 7 m 20 7 l 20 6 l 21 6 l 21 5 l 20 5 l 20 4 l 17 4 l 17 6 l 19 6 l h24 6 m 25 6 l 25 5 l 24 5 l h22 5 m 23 5 l 23 4 l 22 4 l hfendstreamendobjxref0 50000000000 65535 f 0000000010 00000 n 0000000059 00000 n 0000000118 00000 n 0000000223 00000 n trailer << /Root 1 0 R /Size 5 >>startxref2111%%EOF\",\n        recipeConfig: [\n            {\n                \"op\": \"Generate QR Code\",\n                \"args\": [\"PDF\", 5, 4, \"Low\"]\n            },\n            {\n                \"op\": \"Remove whitespace\",\n                \"args\": [false, true, true, false, false, false]\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/GetAllCasings.mjs",
    "content": "/**\n * GetAllCasings tests.\n *\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"All casings of test\",\n        input: \"test\",\n        expectedOutput: \"test\\nTest\\ntEst\\nTEst\\nteSt\\nTeSt\\ntESt\\nTESt\\ntesT\\nTesT\\ntEsT\\nTEsT\\nteST\\nTeST\\ntEST\\nTEST\",\n        recipeConfig: [\n            {\n                \"op\": \"Get All Casings\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"All casings of t\",\n        input: \"t\",\n        expectedOutput: \"t\\nT\",\n        recipeConfig: [\n            {\n                \"op\": \"Get All Casings\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"All casings of null\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Get All Casings\",\n                \"args\": []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Gunzip.mjs",
    "content": "/**\n * Gunzip Tests.\n *\n * @author n1073645 [n1073645@gmail.com]\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Gunzip: No comment, no checksum and no filename\",\n        input: \"1f8b0800f7c8f85d00ff0dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000\",\n        expectedOutput: \"The quick brown fox jumped over the slow dog\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Gunzip\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Gunzip: No comment, no checksum and filename\",\n        input: \"1f8b080843c9f85d00ff66696c656e616d65000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000\",\n        expectedOutput: \"The quick brown fox jumped over the slow dog\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Gunzip\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Gunzip: Has a comment, no checksum and has a filename\",\n        input: \"1f8b08186fc9f85d00ff66696c656e616d6500636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000\",\n        expectedOutput: \"The quick brown fox jumped over the slow dog\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Gunzip\",\n                args: []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Gzip.mjs",
    "content": "/**\n * Gzip Tests.\n *\n * @author n1073645 [n1073645@gmail.com]\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Gzip: No comment, no checksum and no filename\",\n        input: \"The quick brown fox jumped over the slow dog\",\n        expectedOutput: \"0dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000\",\n        recipeConfig: [\n            {\n                op: \"Gzip\",\n                args: [\"Dynamic Huffman Coding\", \"\", \"\", false]\n            },\n            {\n                op: \"Drop bytes\",\n                args: [0, 10, false]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"Gzip: No comment, no checksum and has a filename\",\n        input: \"The quick brown fox jumped over the slow dog\",\n        expectedOutput: \"636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000\",\n        recipeConfig: [\n            {\n                op: \"Gzip\",\n                args: [\"Dynamic Huffman Coding\", \"comment\", \"\", false]\n            },\n            {\n                op: \"Drop bytes\",\n                args: [0, 10, false]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"Gzip: Has a comment, no checksum and no filename\",\n        input: \"The quick brown fox jumped over the slow dog\",\n        expectedOutput: \"636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000\",\n        recipeConfig: [\n            {\n                op: \"Gzip\",\n                args: [\"Dynamic Huffman Coding\", \"\", \"comment\", false]\n            },\n            {\n                op: \"Drop bytes\",\n                args: [0, 10, false]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"Gzip: Has a comment, no checksum and has a filename\",\n        input: \"The quick brown fox jumped over the slow dog\",\n        expectedOutput: \"66696c656e616d6500636f6d6d656e74000dc9dd0180200804e0556ea8262848fb3dc588c6a7e76faa8aeedb726036c68d951f76bf9a0af8aae1f97d9c0c084b02509cbf8c2c000000\",\n        recipeConfig: [\n            {\n                op: \"Gzip\",\n                args: [\"Dynamic Huffman Coding\", \"filename\", \"comment\", false]\n            },\n            {\n                op: \"Drop bytes\",\n                args: [0, 10, false]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/HASSH.mjs",
    "content": "/**\n * HASSH tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"HASSH Client Fingerprint\",\n        input: \"000003140814c639665f5425dcb80bf9f0a048380a410000007e6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d6473730000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e73650000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e736500000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d39360000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c6962000000000000000000000000000000000000000000\",\n        expectedOutput: \"21b457a327ce7a2d4fce5ef2c42400bd\",\n        recipeConfig: [\n            {\n                \"op\": \"HASSH Client Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n    {\n        name: \"HASSH Server Fingerprint\",\n        input: \"0000027c0b142c7bb93a1da21c9e54f5862e60a5597c000000596469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d647373000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d637472000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d63747200000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d3936000000096e6f6e652c7a6c6962000000096e6f6e652c7a6c6962000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"f430cd6761697a6a658ee1d45ed22e49\",\n        recipeConfig: [\n            {\n                \"op\": \"HASSH Server Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/HKDF.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 1\",\n        \"input\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\",\n        \"expectedOutput\": \"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c\"},\n                    {\"option\": \"Hex\", \"string\": \"f0f1f2f3f4f5f6f7f8f9\"},\n                    \"SHA256\", \"with salt\", 42,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 2\",\n        \"input\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f\",\n        \"expectedOutput\": \"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf\"},\n                    {\"option\": \"Hex\", \"string\": \"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff\"},\n                    \"SHA256\", \"with salt\", 82,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 3\",\n        \"input\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\",\n        \"expectedOutput\": \"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"SHA256\", \"with salt\", 42,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 4\",\n        \"input\": \"0b0b0b0b0b0b0b0b0b0b0b\",\n        \"expectedOutput\": \"085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c\"},\n                    {\"option\": \"Hex\", \"string\": \"f0f1f2f3f4f5f6f7f8f9\"},\n                    \"SHA1\", \"with salt\", 42,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 5\",\n        \"input\": \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f\",\n        \"expectedOutput\": \"0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf\"},\n                    {\"option\": \"Hex\", \"string\": \"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff\"},\n                    \"SHA1\", \"with salt\", 82,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 6\",\n        \"input\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\",\n        \"expectedOutput\": \"0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"SHA1\", \"with salt\", 42,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 7\",\n        \"input\": \"0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c\",\n        \"expectedOutput\": \"2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"SHA1\", \"no salt\", 42,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: RFC5869 Test Case 1 with skip extract\",\n        \"input\": \"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5\",\n        \"expectedOutput\": \"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"f0f1f2f3f4f5f6f7f8f9\"},\n                    \"SHA256\", \"skip\", 42,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"HKDF: too large L\",\n        \"input\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\",\n        \"expectedOutput\": \"L too large (maximum length for SHA256 is 8160)\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"],\n            },\n            {\n                \"op\": \"Derive HKDF key\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"000102030405060708090a0b0c\"},\n                    {\"option\": \"Hex\", \"string\": \"f0f1f2f3f4f5f6f7f8f9\"},\n                    \"SHA256\", \"with salt\", 8161,\n                ],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Hash.mjs",
    "content": "/**\n * Hash tests.\n *\n * @author Matt C [matt@artemisbot.uk]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"MD2\",\n        input: \"Hello, World!\",\n        expectedOutput: \"1c8f1e6a94aaa7145210bf90bb52871a\",\n        recipeConfig: [\n            {\n                \"op\": \"MD2\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"MD4\",\n        input: \"Hello, World!\",\n        expectedOutput: \"94e3cb0fa9aa7a5ee3db74b79e915989\",\n        recipeConfig: [\n            {\n                \"op\": \"MD4\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"MD5\",\n        input: \"Hello, World!\",\n        expectedOutput: \"65a8e27d8879283831b664bd8b7f0ad4\",\n        recipeConfig: [\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"MD6\",\n        input: \"Hello, World!\",\n        expectedOutput: \"ce5effce32637e6b8edaacc9284b873c3fd4e66f9779a79df67eb4a82dda8230\",\n        recipeConfig: [\n            {\n                \"op\": \"MD6\",\n                \"args\": [256, 64, \"\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA0\",\n        input: \"Hello, World!\",\n        expectedOutput: \"5a5588f0407c6ae9a988758e76965f841b299229\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA0\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"SHA1\",\n        input: \"Hello, World!\",\n        expectedOutput: \"0a0a9f2a6772942557ab5355d76af442f8f65e01\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA1\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"SHA2 224\",\n        input: \"Hello, World!\",\n        expectedOutput: \"72a23dfa411ba6fde01dbfabf3b00a709c93ebf273dc29e2d8b261ff\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"224\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 384\",\n        input: \"Hello, World!\",\n        expectedOutput: \"5485cc9b3365b4305dfb4e8337e0a598a574f8242bf17289e0dd6c20a3cd44a089de16ab4ab308f63e44b1170eb5f515\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"384\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 512\",\n        input: \"Hello, World!\",\n        expectedOutput: \"374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 512/224\",\n        input: \"Hello, World!\",\n        expectedOutput: \"766745f058e8a0438f19de48ae56ea5f123fe738af39bca050a7547a\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"512/224\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 512/256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"0686f0a605973dc1bf035d1e2b9bad1985a0bff712ddd88abd8d2593e5f99030\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"512/256\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 224\",\n        input: \"Hello, World!\",\n        expectedOutput: \"853048fb8b11462b6100385633c0cc8dcdc6e2b8e376c28102bc84f2\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"224\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 384\",\n        input: \"Hello, World!\",\n        expectedOutput: \"aa9ad8a49f31d2ddcabbb7010a1566417cff803fef50eba239558826f872e468c5743e7f026b0a8e5b2d7a1cc465cdbe\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"384\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 512\",\n        input: \"Hello, World!\",\n        expectedOutput: \"38e05c33d7b067127f217d8c856e554fcff09c9320b8a5979ce2ff5d95dd27ba35d1fba50c562dfd1d6cc48bc9c5baa4390894418cc942d968f97bcb659419ed\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"Keccak 224\",\n        input: \"Hello, World!\",\n        expectedOutput: \"4eaaf0e7a1e400efba71130722e1cb4d59b32afb400e654afec4f8ce\",\n        recipeConfig: [\n            {\n                \"op\": \"Keccak\",\n                \"args\": [\"224\"]\n            }\n        ]\n    },\n    {\n        name: \"Keccak 384\",\n        input: \"Hello, World!\",\n        expectedOutput: \"4d60892fde7f967bcabdc47c73122ae6311fa1f9be90d721da32030f7467a2e3db3f9ccb3c746483f9d2b876e39def17\",\n        recipeConfig: [\n            {\n                \"op\": \"Keccak\",\n                \"args\": [\"384\"]\n            }\n        ]\n    },\n    {\n        name: \"Keccak 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f\",\n        recipeConfig: [\n            {\n                \"op\": \"Keccak\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"Keccak 512\",\n        input: \"Hello, World!\",\n        expectedOutput: \"eda765576c84c600ed7f5d97510e92703b61f5215def2a161037fd9dd1f5b6ed4f86ce46073c0e3f34b52de0289e9c618798fff9dd4b1bfe035bdb8645fc6e37\",\n        recipeConfig: [\n            {\n                \"op\": \"Keccak\",\n                \"args\": [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"Shake 128\",\n        input: \"Hello, World!\",\n        expectedOutput: \"2bf5e6dee6079fad604f573194ba8426bd4d30eb13e8ba2edae70e529b570cbd\",\n        recipeConfig: [\n            {\n                \"op\": \"Shake\",\n                \"args\": [\"128\", 256]\n            }\n        ]\n    },\n    {\n        name: \"Shake 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"b3be97bfd978833a65588ceae8a34cf59e95585af62063e6b89d0789f372424e8b0d1be4f21b40ce5a83a438473271e0661854f02d431db74e6904d6c347d757\",\n        recipeConfig: [\n            {\n                \"op\": \"Shake\",\n                \"args\": [\"256\", 512]\n            }\n        ]\n    },\n    {\n        name: \"RIPEMD 128\",\n        input: \"Hello, World!\",\n        expectedOutput: \"67f9fe75ca2886dc76ad00f7276bdeba\",\n        recipeConfig: [\n            {\n                \"op\": \"RIPEMD\",\n                \"args\": [\"128\"]\n            }\n        ]\n    },\n    {\n        name: \"RIPEMD 160\",\n        input: \"Hello, World!\",\n        expectedOutput: \"527a6a4b9a6da75607546842e0e00105350b1aaf\",\n        recipeConfig: [\n            {\n                \"op\": \"RIPEMD\",\n                \"args\": [\"160\"]\n            }\n        ]\n    },\n    {\n        name: \"RIPEMD 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"567750c6d34dcba7ae038a80016f3ca3260ec25bfdb0b68bbb8e730b00b2447d\",\n        recipeConfig: [\n            {\n                \"op\": \"RIPEMD\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"RIPEMD 320\",\n        input: \"Hello, World!\",\n        expectedOutput: \"f9832e5bb00576fc56c2221f404eb77addeafe49843c773f0df3fc5a996d5934f3c96e94aeb80e89\",\n        recipeConfig: [\n            {\n                \"op\": \"RIPEMD\",\n                \"args\": [\"320\"]\n            }\n        ]\n    },\n    {\n        name: \"HAS-160\",\n        input: \"Hello, World!\",\n        expectedOutput: \"8f6dd8d7c8a04b1cb3831adc358b1e4ac2ed5984\",\n        recipeConfig: [\n            {\n                \"op\": \"HAS-160\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"Whirlpool-0\",\n        input: \"Hello, World!\",\n        expectedOutput: \"1c327026f565a0105a827efbfb3d3635cdb042c0aabb8416e96deb128e6c5c8684b13541cf31c26c1488949df050311c6999a12eb0e7002ad716350f5c7700ca\",\n        recipeConfig: [\n            {\n                \"op\": \"Whirlpool\",\n                \"args\": [\"Whirlpool-0\"]\n            }\n        ]\n    },\n    {\n        name: \"Whirlpool-T\",\n        input: \"Hello, World!\",\n        expectedOutput: \"16c581089b6a6f356ae56e16a63a4c613eecd82a2a894b293f5ee45c37a31d09d7a8b60bfa7e414bd4a7166662cea882b5cf8c96b7d583fc610ad202591bcdb1\",\n        recipeConfig: [\n            {\n                \"op\": \"Whirlpool\",\n                \"args\": [\"Whirlpool-T\"]\n            }\n        ]\n    },\n    {\n        name: \"Whirlpool\",\n        input: \"Hello, World!\",\n        expectedOutput: \"3d837c9ef7bb291bd1dcfc05d3004af2eeb8c631dd6a6c4ba35159b8889de4b1ec44076ce7a8f7bfa497e4d9dcb7c29337173f78d06791f3c3d9e00cc6017f0b\",\n        recipeConfig: [\n            {\n                \"op\": \"Whirlpool\",\n                \"args\": [\"Whirlpool\"]\n            }\n        ]\n    },\n    {\n        name: \"Snefru 2 128\",\n        input: \"Hello, World!\",\n        expectedOutput: \"a4ad2b8848580511d0884fb4233a7e7a\",\n        recipeConfig: [\n            {\n                \"op\": \"Snefru\",\n                \"args\": [\"128\", \"2\"]\n            }\n        ]\n    },\n    {\n        name: \"Snefru 4 128\",\n        input: \"Hello, World!\",\n        expectedOutput: \"d154eae2c9ffbcd2e1bdaf0b84736126\",\n        recipeConfig: [\n            {\n                \"op\": \"Snefru\",\n                \"args\": [\"128\", \"4\"]\n            }\n        ]\n    },\n    {\n        name: \"Snefru 8 128\",\n        input: \"Hello, World!\",\n        expectedOutput: \"6f3d55b69557abb0a3c4e9de9d29ba5d\",\n        recipeConfig: [\n            {\n                \"op\": \"Snefru\",\n                \"args\": [\"128\", \"8\"]\n            }\n        ]\n    },\n    {\n        name: \"Snefru 2 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"65736daba648de28ef4c4a316b4684584ecf9f22ddb5c457729e6bf0f40113c4\",\n        recipeConfig: [\n            {\n                \"op\": \"Snefru\",\n                \"args\": [\"256\", \"2\"]\n            }\n        ]\n    },\n    {\n        name: \"Snefru 4 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"71b0ea4b3e33f2e58bcc67c8a8de060b99ec0107355bbfdc18d8f65f0194ffcc\",\n        recipeConfig: [\n            {\n                \"op\": \"Snefru\",\n                \"args\": [\"256\", \"4\"]\n            }\n        ]\n    },\n    {\n        name: \"Snefru 8 256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"255cd401414c79588cf689e8d5ff0536a2cfab83fcae36e654f202b09bc4b8a7\",\n        recipeConfig: [\n            {\n                \"op\": \"Snefru\",\n                \"args\": [\"256\", \"8\"]\n            }\n        ]\n    },\n    {\n        name: \"SM3 256 64\",\n        input: \"Hello, World!\",\n        expectedOutput: \"7ed26cbf0bee4ca7d55c1e64714c4aa7d1f163089ef5ceb603cd102c81fbcbc5\",\n        recipeConfig: [\n            {\n                \"op\": \"SM3\",\n                \"args\": [\"256\", \"64\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: SHA256\",\n        input: \"Hello, World!\",\n        expectedOutput: \"52589bd80ccfa4acbb3f9512dfaf4f700fa5195008aae0b77a9e47dcca75beac\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Latin1\", \"string\": \"test\"}, \"SHA256\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 1 SHA-224\",\n        input: \"Hi There\",\n        expectedOutput: \"896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\"}, \"SHA224\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 1 SHA-256\",\n        input: \"Hi There\",\n        expectedOutput: \"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\"}, \"SHA256\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 1 SHA-384\",\n        input: \"Hi There\",\n        expectedOutput: \"afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\"}, \"SHA384\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 1 SHA-512\",\n        input: \"Hi There\",\n        expectedOutput: \"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\"}, \"SHA512\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 2 SHA-224\",\n        input: \"what do ya want for nothing?\",\n        expectedOutput: \"a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"4a656665\"}, \"SHA224\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 2 SHA-256\",\n        input: \"what do ya want for nothing?\",\n        expectedOutput: \"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"4a656665\"}, \"SHA256\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 2 SHA-384\",\n        input: \"what do ya want for nothing?\",\n        expectedOutput: \"af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"4a656665\"}, \"SHA384\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 2 SHA-512\",\n        input: \"what do ya want for nothing?\",\n        expectedOutput: \"164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"4a656665\"}, \"SHA512\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 3 SHA-224\",\n        input: \"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\n        expectedOutput: \"7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA224\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 3 SHA-256\",\n        input: \"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\n        expectedOutput: \"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA256\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 3 SHA-384\",\n        input: \"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\n        expectedOutput: \"88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA384\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 3 SHA-512\",\n        input: \"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\n        expectedOutput: \"fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA512\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 4 SHA-224\",\n        input: \"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\n        expectedOutput: \"6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0102030405060708090a0b0c0d0e0f10111213141516171819\"}, \"SHA224\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 4 SHA-256\",\n        input: \"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\n        expectedOutput: \"82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0102030405060708090a0b0c0d0e0f10111213141516171819\"}, \"SHA256\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 4 SHA-384\",\n        input: \"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\n        expectedOutput: \"3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0102030405060708090a0b0c0d0e0f10111213141516171819\"}, \"SHA384\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 4 SHA-512\",\n        input: \"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\n        expectedOutput: \"b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"0102030405060708090a0b0c0d0e0f10111213141516171819\"}, \"SHA512\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 6 SHA-224\",\n        input: \"Test Using Larger Than Block-Size Key - Hash Key First\",\n        expectedOutput: \"95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA224\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 6 SHA-256\",\n        input: \"Test Using Larger Than Block-Size Key - Hash Key First\",\n        expectedOutput: \"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA256\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 6 SHA-384\",\n        input: \"Test Using Larger Than Block-Size Key - Hash Key First\",\n        expectedOutput: \"4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA384\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 6 SHA-512\",\n        input: \"Test Using Larger Than Block-Size Key - Hash Key First\",\n        expectedOutput: \"80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA512\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 7 SHA-224\",\n        input: \"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.\",\n        expectedOutput: \"3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA224\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 7 SHA-256\",\n        input: \"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.\",\n        expectedOutput: \"9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA256\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 7 SHA-384\",\n        input: \"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.\",\n        expectedOutput: \"6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA384\"]\n            }\n        ]\n    },\n    {\n        name: \"HMAC: RFC4231 Test Case 7 SHA-512\",\n        input: \"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.\",\n        expectedOutput: \"e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58\",\n        recipeConfig: [\n            {\n                \"op\": \"HMAC\",\n                \"args\": [{\"option\": \"Hex\", \"string\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}, \"SHA512\"]\n            }\n        ]\n    },\n    {\n        name: \"MD5: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"4f4f02e2646545aa8fc42f613c9aa068\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"SHA1: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"2c5400aaee7e8ad4cad29bfbdf8d566924e5442c\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA1\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"SHA2 224: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"66c166eba2529ecc44a7b7b218a64a8e3892f873c8d231e8e3c1ef3d\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"224\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 256: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"186ffd22c3af83995afa4a0316023f81a7f8834fd16bd2ed358c7b1b8182ba41\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 384: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"2a6369ffec550ea0bfb810b3b8246b7d6b7f060edfae88441f0f242b98b91549aa4ff407de38c6d03b5f377434ad2f36\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"384\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 512: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"544ae686522c05b70d12b460b5b39ea0a758eb4027333edbded7e2b3f467aa605804f71f54db61a7bbe50e6e7898510635efd6721fd418a9ea4d05b286d12806\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 224: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"e2c07562ee8c2d73e3dd309efea257159abd0948ebc14619bab9ffb3\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"224\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 256: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"55a55275387586afd1ed64757c9ee7ad1d96ca81a5b7b742c40127856ee78a2d\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 384: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"39f8796dd697dc39e5a943817833793f2c29dc0d1adc7037854c0fb51e135c6bd26b113240c4fb1e3fcc16ff8690c91a\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"384\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 512: Complex bytes\",\n        input: \"10dc10e32010de10d010dc10d810d910d010e12e\",\n        expectedOutput: \"ee9061bed83b1ad1e2fc4a4bac72a5a65a23a0fa55193b808af0a3e2013b718a5a3e40474765b4f93d1b2747401058a5b58099cc890a159db92b2ea816287add\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"MD5: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"2e93ee2b5b2a337ccb678c7db12eff1b\",\n        recipeConfig: [\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"SHA1: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"87f483b1515dce672be044bf183ae8103e3b2d4b\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA1\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"SHA2 224: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"563ca57b500157717961a5fa87ce42c6db76a488c98ea9c28d620770\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"224\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 256: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"36abbb4622ffff06aa3e3cea266765601b21457bb3755a0a2cf0a206422863c1\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 384: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"140b929391a66c9a943bcd60e6964f0d19526d3bc9ba020fbb29aae51cddb8e63a78784d8770f1d36335bf4efff8c131\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"384\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA2 512: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"04a7887c400bf647b7c67b9a0f1ada70d176348b5afdfebea184f7e62748849828669c7b5160be99455fdbf625589bd1689c003bc06ef60c39607d825a2f8838\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA2\",\n                \"args\": [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 224: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"b3ffc9620949f879cb561fb240452494e2566cb4e4f701a85715e14f\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"224\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 256: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"b5f247d725b46546c832502cd07bccb5d4de0c41a6665d3944ed2cc55cd9d156\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 384: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"93e87b9aa8c9c47eba146adac357c525b418b71677f6db01d1c760d87b058682e639c8d43a8bfe91529cecd9800700e3\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"384\"]\n            }\n        ]\n    },\n    {\n        name: \"SHA3 512: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"1fbc484b5184982561795162757717474eebc846ca9f10029a75a54cdd897a7b48d1db42f2478fa1d5d213a0dd7de71c809cb19c60581ba57e7289d29408fb36\",\n        recipeConfig: [\n            {\n                \"op\": \"SHA3\",\n                \"args\": [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"Bcrypt compare: dolphin\",\n        input: \"dolphin\",\n        expectedOutput: \"Match: dolphin\",\n        recipeConfig: [\n            {\n                op: \"Bcrypt compare\",\n                args: [\"$2a$10$qyon0LQCmMxpFFjwWH6Qh.dDdhqntQh./IN0RXCc3XIMILuOYZKgK\"]\n            }\n        ]\n    },\n    {\n        name: \"Scrypt: RFC test vector 1\",\n        input: \"\",\n        expectedOutput: \"77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906\",\n        recipeConfig: [\n            {\n                op: \"Scrypt\",\n                args: [\n                    {\n                        \"option\": \"Latin1\",\n                        \"string\": \"\"\n                    },\n                    16, 1, 1, 64\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Scrypt: RFC test vector 2\",\n        input: \"password\",\n        expectedOutput: \"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640\",\n        recipeConfig: [\n            {\n                op: \"Scrypt\",\n                args: [\n                    {\n                        \"option\": \"Latin1\",\n                        \"string\": \"NaCl\"\n                    },\n                    1024, 8, 16, 64\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Scrypt: RFC test vector 3\",\n        input: \"pleaseletmein\",\n        expectedOutput: \"7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887\",\n        recipeConfig: [\n            {\n                op: \"Scrypt\",\n                args: [\n                    {\n                        \"option\": \"Latin1\",\n                        \"string\": \"SodiumChloride\"\n                    },\n                    16384, 8, 1, 64\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Streebog-256: Test Case 1\",\n        input: \"\",\n        expectedOutput: \"3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb\",\n        recipeConfig: [\n            {\n                op: \"Streebog\",\n                args: [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"Streebog-256: Test Case 2\",\n        input: \"The quick brown fox jumps over the lazy dog\",\n        expectedOutput: \"3e7dea7f2384b6c5a3d0e24aaa29c05e89ddd762145030ec22c71a6db8b2c1f4\",\n        recipeConfig: [\n            {\n                op: \"Streebog\",\n                args: [\"256\"]\n            }\n        ]\n    },\n    {\n        name: \"Streebog-512: Test Case 1\",\n        input: \"\",\n        expectedOutput: \"8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a\",\n        recipeConfig: [\n            {\n                op: \"Streebog\",\n                args: [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"Streebog-512: Test Case 2\",\n        input: \"The quick brown fox jumps over the lazy dog\",\n        expectedOutput: \"d2b793a0bb6cb5904828b5b6dcfb443bb8f33efc06ad09368878ae4cdc8245b97e60802469bed1e7c21a64ff0b179a6a1e0bb74d92965450a0adab69162c00fe\",\n        recipeConfig: [\n            {\n                op: \"Streebog\",\n                args: [\"512\"]\n            }\n        ]\n    },\n    {\n        name: \"GOST R 34.11-94: Test Case 1\",\n        input: \"\",\n        expectedOutput: \"981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0\",\n        recipeConfig: [\n            {\n                op: \"GOST Hash\",\n                args: [\"GOST 28147 (1994)\", \"256\", \"D-A\"]\n            }\n        ]\n    },\n    {\n        name: \"GOST R 34.11-94: Test Case 2\",\n        input: \"This is message, length=32 bytes\",\n        expectedOutput: \"2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb\",\n        recipeConfig: [\n            {\n                op: \"GOST Hash\",\n                args: [\"GOST 28147 (1994)\", \"256\", \"D-A\"]\n            }\n        ]\n    },\n    /* { // This takes a LONG time to run (over a minute usually).\n        name: \"Scrypt: RFC test vector 4\",\n        input: \"pleaseletmein\",\n        expectedOutput: \"2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4\",\n        recipeConfig: [\n            {\n                op: \"Scrypt\",\n                args: [\n                    {\n                        \"option\": \"Latin1\",\n                        \"string\": \"SodiumChloride\"\n                    },\n                    1048576, 8, 1, 64\n                ]\n            }\n        ]\n    }, */\n    {\n        name: \"Argon2\",\n        input: \"argon2password\",\n        expectedOutput: \"$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$s43my9eBljQADuF/LWCG8vGqwAJzOorKQ0Yog8jFvbw\",\n        recipeConfig: [\n            {\n                op: \"Argon2\",\n                args: [\n                    {\"option\": \"UTF8\", \"string\": \"somesalt\"},\n                    3,\n                    4096,\n                    1,\n                    32,\n                    \"Argon2i\",\n                    \"Encoded hash\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Argon2 compare\",\n        input: \"argon2password\",\n        expectedOutput: \"Match: argon2password\",\n        recipeConfig: [\n            {\n                op: \"Argon2 compare\",\n                args: [\n                    \"$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$s43my9eBljQADuF/LWCG8vGqwAJzOorKQ0Yog8jFvbw\"\n                ]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/HaversineDistance.mjs",
    "content": "/**\n * Haversine distance tests.\n *\n * @author Dachande663 [dachande663@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Haversine distance\",\n        input: \"51.487263,-0.124323, 38.9517,-77.1467\",\n        expectedOutput: \"5902542.836307819\",\n        recipeConfig: [\n            {\n                \"op\": \"Haversine distance\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Haversine distance, zero distance\",\n        input: \"51.487263,-0.124323, 51.487263,-0.124323\",\n        expectedOutput: \"0\",\n        recipeConfig: [\n            {\n                \"op\": \"Haversine distance\",\n                \"args\": []\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Hex.mjs",
    "content": "import TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"ASCII to Hex stream\",\n        input: \"aberystwyth\",\n        expectedOutput: \"6162657279737477797468\",\n        recipeConfig: [\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\n                    \"None\",\n                    0\n                ]\n            },\n        ]\n    },\n    {\n        name: \"ASCII to Hex with colon deliminator \",\n        input: \"aberystwyth\",\n        expectedOutput: \"61:62:65:72:79:73:74:77:79:74:68\",\n        recipeConfig: [\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\n                    \"Colon\",\n                    0\n                ]\n            }\n        ]\n    },\n    {\n        name: \"ASCII to 0x Hex with comma\",\n        input: \"aberystwyth\",\n        expectedOutput: \"0x61,0x62,0x65,0x72,0x79,0x73,0x74,0x77,0x79,0x74,0x68\",\n        recipeConfig: [\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\n                    \"0x with comma\",\n                    0\n                ]\n            }\n        ]\n    },\n    {\n        name: \"ASCII to Hex with percent deliminator\",\n        input: \"aberystwyth\",\n        expectedOutput: \"%61%62%65%72%79%73%74%77%79%74%68\",\n        recipeConfig: [\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\n                    \"Percent\",\n                    0\n                ]\n            }\n        ]\n    },\n    {\n        name: \"ASCII to 0x Hex with comma and line breaks\",\n        input: \"aberystwyth\",\n        expectedOutput: \"0x61,0x62,0x65,0x72,\\n0x79,0x73,0x74,0x77,\\n0x79,0x74,0x68\",\n        recipeConfig: [\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\n                    \"0x with comma\",\n                    4\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Hex stream to UTF-8\",\n        input: \"e69591e69591e5ada9e5ad90\",\n        expectedOutput: \"救救孩子\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\n                    \"Auto\"\n                ]\n            }\n        ]\n\n    },\n    {\n        name: \"Multiline 0x hex to ASCII\",\n        input: \"0x49,0x20,0x73,0x61,0x77,0x20,0x6d,0x79,0x73,0x65,0x6c,0x66,0x20,0x73,0x69,\\\n0x74,0x74,0x69,0x6e,0x67,0x20,0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x63,0x72,\\\n0x6f,0x74,0x63,0x68,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x74,0x68,0x69,\\\n0x73,0x20,0x66,0x69,0x67,0x20,0x74,0x72,0x65,0x65,0x2c,0x20,0x73,0x74,0x61,\\\n0x72,0x76,0x69,0x6e,0x67,0x20,0x74,0x6f,0x20,0x64,0x65,0x61,0x74,0x68,0x2c,\\\n0x20,0x6a,0x75,0x73,0x74,0x20,0x62,0x65,0x63,0x61,0x75,0x73,0x65,0x20,0x49,\\\n0x20,0x63,0x6f,0x75,0x6c,0x64,0x6e,0x27,0x74,0x20,0x6d,0x61,0x6b,0x65,0x20,\\\n0x75,0x70,0x20,0x6d,0x79,0x20,0x6d,0x69,0x6e,0x64,0x20,0x77,0x68,0x69,0x63,\\\n0x68,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x66,0x69,0x67,0x73,0x20,0x49,\\\n0x20,0x77,0x6f,0x75,0x6c,0x64,0x20,0x63,0x68,0x6f,0x6f,0x73,0x65,0x2e\",\n        expectedOutput: \"I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\n                    \"Auto\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"0x with Comma to Ascii\",\n        input: \"0x74,0x65,0x73,0x74,0x20,0x73,0x74,0x72,0x69,0x6e,0x67\",\n        expectedOutput: \"test string\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\n                    \"0x with comma\"\n                ]\n            }\n        ]\n\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Hexdump.mjs",
    "content": "/**\n * Hexdump tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nTestRegister.addTests([\n    {\n        name: \"Hexdump: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Hexdump\",\n                args: [16, false, false]\n            },\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"Hexdump: Hello, World!\",\n        input: \"Hello, World!\",\n        expectedOutput: \"Hello, World!\",\n        recipeConfig: [\n            {\n                op: \"To Hexdump\",\n                args: [16, false, false]\n            },\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"Hexdump: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"ნუ პანიკას\",\n        recipeConfig: [\n            {\n                op: \"To Hexdump\",\n                args: [16, false, false]\n            },\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"Hexdump: All bytes\",\n        input: ALL_BYTES,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"To Hexdump\",\n                args: [16, false, false]\n            },\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"To Hexdump: UTF-8\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: `00000000  e1 83 9c e1 83 a3 20 e1 83 9e e1 83 90 e1 83 9c  |á..á.£ á..á..á..|\n00000010  e1 83 98 e1 83 99 e1 83 90 e1 83 a1              |á..á..á..á.¡|`,\n        recipeConfig: [\n            {\n                op: \"To Hexdump\",\n                args: [16, false, false]\n            }\n        ],\n    },\n    {\n        name: \"To Hexdump: All bytes\",\n        input: ALL_BYTES,\n        expectedOutput: `00000000  00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f  |................|\n00000010  10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f  |................|\n00000020  20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f  | !\"#$%&'()*+,-./|\n00000030  30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f  |0123456789:;<=>?|\n00000040  40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  |@ABCDEFGHIJKLMNO|\n00000050  50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f  |PQRSTUVWXYZ[\\\\]^_|\n00000060  60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f  |\\`abcdefghijklmno|\n00000070  70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f  |pqrstuvwxyz{|}~.|\n00000080  80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f  |................|\n00000090  90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f  |................|\n000000a0  a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af  |\\xa0¡¢£¤¥¦§¨©ª«¬.®¯|\n000000b0  b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf  |°±²³´µ¶·¸¹º»¼½¾¿|\n000000c0  c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf  |ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ|\n000000d0  d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df  |ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß|\n000000e0  e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef  |àáâãäåæçèéêëìíîï|\n000000f0  f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff  |ðñòóôõö÷øùúûüýþÿ|`,\n        recipeConfig: [\n            {\n                op: \"To Hexdump\",\n                args: [16, false, false]\n            }\n        ],\n    },\n    {\n        name: \"From Hexdump: xxd\",\n        input: `00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f  ................\n00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f  ................\n00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f   !\"#$%&'()*+,-./\n00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f  0123456789:;<=>?\n00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f  @ABCDEFGHIJKLMNO\n00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f  PQRSTUVWXYZ[\\\\]^_\n00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f  \\`abcdefghijklmno\n00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f  pqrstuvwxyz{|}~.\n00000080: 8081 8283 8485 8687 8889 8a8b 8c8d 8e8f  ................\n00000090: 9091 9293 9495 9697 9899 9a9b 9c9d 9e9f  ................\n000000a0: a0a1 a2a3 a4a5 a6a7 a8a9 aaab acad aeaf  ................\n000000b0: b0b1 b2b3 b4b5 b6b7 b8b9 babb bcbd bebf  ................\n000000c0: c0c1 c2c3 c4c5 c6c7 c8c9 cacb cccd cecf  ................\n000000d0: d0d1 d2d3 d4d5 d6d7 d8d9 dadb dcdd dedf  ................\n000000e0: e0e1 e2e3 e4e5 e6e7 e8e9 eaeb eced eeef  ................\n000000f0: f0f1 f2f3 f4f5 f6f7 f8f9 fafb fcfd feff  ................`,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"From Hexdump: xxd format, odd number of bytes\",\n        input: \"00000000: 6162 6364 65                             abcde\",\n        expectedOutput: \"abcde\",\n        recipeConfig: [\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"From Hexdump: Wireshark\",\n        input: `00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f ........ ........\n00000010  10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f ........ ........\n00000020  20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f  !\"#$%&' ()*+,-./\n00000030  30 31 32 33 34 35 36 37  38 39 3a 3b 3c 3d 3e 3f 01234567 89:;<=>?\n00000040  40 41 42 43 44 45 46 47  48 49 4a 4b 4c 4d 4e 4f @ABCDEFG HIJKLMNO\n00000050  50 51 52 53 54 55 56 57  58 59 5a 5b 5c 5d 5e 5f PQRSTUVW XYZ[\\\\]^_\n00000060  60 61 62 63 64 65 66 67  68 69 6a 6b 6c 6d 6e 6f \\`abcdefg hijklmno\n00000070  70 71 72 73 74 75 76 77  78 79 7a 7b 7c 7d 7e 7f pqrstuvw xyz{|}~.\n00000080  80 81 82 83 84 85 86 87  88 89 8a 8b 8c 8d 8e 8f ........ ........\n00000090  90 91 92 93 94 95 96 97  98 99 9a 9b 9c 9d 9e 9f ........ ........\n000000A0  a0 a1 a2 a3 a4 a5 a6 a7  a8 a9 aa ab ac ad ae af ........ ........\n000000B0  b0 b1 b2 b3 b4 b5 b6 b7  b8 b9 ba bb bc bd be bf ........ ........\n000000C0  c0 c1 c2 c3 c4 c5 c6 c7  c8 c9 ca cb cc cd ce cf ........ ........\n000000D0  d0 d1 d2 d3 d4 d5 d6 d7  d8 d9 da db dc dd de df ........ ........\n000000E0  e0 e1 e2 e3 e4 e5 e6 e7  e8 e9 ea eb ec ed ee ef ........ ........\n000000F0  f0 f1 f2 f3 f4 f5 f6 f7  f8 f9 fa fb fc fd fe ff ........ ........\n`,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"From Hexdump: Wireshark alt\",\n        input: `0000   00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n0010   10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f\n0020   20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f\n0030   30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f\n0040   40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f\n0050   50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f\n0060   60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f\n0070   70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f\n0080   80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f\n0090   90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f\n00a0   a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af\n00b0   b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf\n00c0   c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf\n00d0   d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df\n00e0   e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef\n00f0   f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff`,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"From Hexdump: 010\",\n        input: `0000h: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F  ................ \n0010h: 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F  ................ \n0020h: 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F   !\"#$%&'()*+,-./ \n0030h: 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F  0123456789:;<=>? \n0040h: 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F  @ABCDEFGHIJKLMNO \n0050h: 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F  PQRSTUVWXYZ[\\\\]^_ \n0060h: 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F  \\`abcdefghijklmno \n0070h: 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F  pqrstuvwxyz{|}~ \n0080h: 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F  €.‚ƒ„…†‡ˆ‰Š‹Œ...\n0090h: 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F  .‘’“”•–—˜™š›œ.žŸ\n00A0h: A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF  \\xa0¡¢£¤¥¦§¨©ª«¬­®¯ \n00B0h: B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF  °±²³´µ¶·¸¹º»¼½¾¿ \n00C0h: C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF  ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ \n00D0h: D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF  ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß \n00E0h: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF  àáâãäåæçèéêëìíîï \n00F0h: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF  ðñòóôõö÷øùúûüýþÿ`,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"From Hexdump: Linux hexdump\",\n        input: `00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|\n00000010  10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f  |................|\n00000020  20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f  | !\"#$%&'()*+,-./|\n00000030  30 31 32 33 34 35 36 37  38 39 3a 3b 3c 3d 3e 3f  |0123456789:;<=>?|\n00000040  40 41 42 43 44 45 46 47  48 49 4a 4b 4c 4d 4e 4f  |@ABCDEFGHIJKLMNO|\n00000050  50 51 52 53 54 55 56 57  58 59 5a 5b 5c 5d 5e 5f  |PQRSTUVWXYZ[\\\\]^_|\n00000060  60 61 62 63 64 65 66 67  68 69 6a 6b 6c 6d 6e 6f  |\\`abcdefghijklmno|\n00000070  70 71 72 73 74 75 76 77  78 79 7a 7b 7c 7d 7e 7f  |pqrstuvwxyz{|}~.|\n00000080  80 81 82 83 84 85 86 87  88 89 8a 8b 8c 8d 8e 8f  |................|\n00000090  90 91 92 93 94 95 96 97  98 99 9a 9b 9c 9d 9e 9f  |................|\n000000a0  a0 a1 a2 a3 a4 a5 a6 a7  a8 a9 aa ab ac ad ae af  |................|\n000000b0  b0 b1 b2 b3 b4 b5 b6 b7  b8 b9 ba bb bc bd be bf  |................|\n000000c0  c0 c1 c2 c3 c4 c5 c6 c7  c8 c9 ca cb cc cd ce cf  |................|\n000000d0  d0 d1 d2 d3 d4 d5 d6 d7  d8 d9 da db dc dd de df  |................|\n000000e0  e0 e1 e2 e3 e4 e5 e6 e7  e8 e9 ea eb ec ed ee ef  |................|\n000000f0  f0 f1 f2 f3 f4 f5 f6 f7  f8 f9 fa fb fc fd fe ff  |................|\n00000100`,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                op: \"From Hexdump\",\n                args: []\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/IPv6Transition.mjs",
    "content": "/**\n * IPv6Transition tests.\n *\n * @author jb30795\n *\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"IPv6 Transition: IPv4 to IPv6\",\n        input: \"198.51.100.7\",\n        expectedOutput: \"6to4: 2002:c633:6407::/48\\nIPv4 Mapped: ::ffff:c633:6407\\nIPv4 Translated: ::ffff:0:c633:6407\\nNat 64: 64:ff9b::c633:6407\",\n        recipeConfig: [\n            {\n                op: \"IPv6 Transition Addresses\",\n                args: [true, false],\n            },\n        ],\n    }, {\n        name: \"IPv6 Transition: IPv4 /24 Range to IPv6\",\n        input: \"198.51.100.0/24\",\n        expectedOutput: \"6to4: 2002:c633:6400::/40\\nIPv4 Mapped: ::ffff:c633:6400/120\\nIPv4 Translated: ::ffff:0:c633:6400/120\\nNat 64: 64:ff9b::c633:6400/120\",\n        recipeConfig: [\n            {\n                op: \"IPv6 Transition Addresses\",\n                args: [false, false],\n            },\n        ],\n    }, {\n        name: \"IPv6 Transition: IPv4 to IPv6 Remove headers\",\n        input: \"198.51.100.7\",\n        expectedOutput: \"2002:c633:6407::/48\\n::ffff:c633:6407\\n::ffff:0:c633:6407\\n64:ff9b::c633:6407\",\n        recipeConfig: [\n            {\n                op: \"IPv6 Transition Addresses\",\n                args: [true, true],\n            },\n        ],\n    }, {\n        name: \"IPv6 Transition: IPv6 to IPv4\",\n        input: \"64:ff9b::c633:6407\",\n        expectedOutput: \"IPv4: 198.51.100.7\",\n        recipeConfig: [\n            {\n                op: \"IPv6 Transition Addresses\",\n                args: [true, false],\n            },\n        ],\n    }, {\n        name: \"IPv6 Transition: MAC to EUI-64\",\n        input: \"a1:b2:c3:d4:e5:f6\",\n        expectedOutput: \"EUI-64 Interface ID: a3b2:c3ff:fed4:e5f6\",\n        recipeConfig: [\n            {\n                op: \"IPv6 Transition Addresses\",\n                args: [true, false],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Image.mjs",
    "content": "/**\n * Image operation tests.\n *\n * @author tlwr [toby@toby.codes]\n * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport { GIF_ANIMATED_HEX, PNG_HEX, JPG_B64, EXIF_JPG_HEX, NO_EXIF_JPG_HEX } from \"../../samples/Images.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Render Image: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            { op: \"Render Image\", args: [\"Raw\"] }\n        ]\n    },\n    {\n        name: \"Render Image: raw gif\",\n        input: GIF_ANIMATED_HEX,\n        expectedOutput: \"<img src='data:image/gif;base64,R0lGODlhDwAPALMLAEJCQv/nAP/vAP/OAAAAAP+1AP+cAP//lP//EP//xv//7////wAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFMgALACwAAAAADwAPAAAEWnBJCWqdeFWVzhkXBnAIEgTCYAAT0J3oma7UERCEgOIp65o4GU9VOZgESFRyUCjGYjoUs3J86nRTwEBAkHkJRM22q+QOQJQzFxcMp8/wgrsFKNgLK1aGYtFPIgAh+QQFCgALACwDAAkACQABAAAEBTBIMWUEACH5BAUKAAsALAQACgAHAAEAAAQFMKw5QwQAIfkEBQoACwAsBAAEAAcAAQAABAVQBClDBAAh+QQFCgALACwDAAQACQACAAAECjAQIoKYV1xZSQQAIfkEBQoACwAsAwAEAAkAAgAABAkwCEkpIUFeEQEAIfkEBTIACwAsBAAEAAcAAQAABAWQkEVlBAAh+QQFGQALACwDAAQACAACAAAECZCssJYMUtQbAQAh+QQFGQALACwDAAQACQACAAAECTAIQmSgEotJIgAh+QQFCgALACwEAAQACAACAAAECXAFseoksmIRAQAh+QQFCgALACwDAAQACQACAAAECjAQIoKYV1xZSQQAIfkEBQoACwAsAwAEAAkAAgAABAkwCEkpIUFeEQEAIfkEBTIACwAsBAAEAAcAAQAABAWQkEVlBAAh+QQFGQALACwEAAMABwADAAAECpCQRSW9VIgrQgQAIfkEBRkACwAsAwADAAgAAgAABAmQrLCWFJLaGQEAIfkEBRkACwAsAwADAAkAAgAABArwBEJEmCQIqnEEACH5BAUZAAsALAUAAwAHAAMAAAQKMIRFxaCYEoJ3BAAh+QQFHgALACwEAAQACAACAAAECZAssZYUMtQbAQAh+QQFCgALACwEAAoABwABAAAEBZCsOUkEACH5BAUKAAsALAMACQAJAAEAAAQFkKxJF4kAOw=='>\",\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"Space\"] },\n            { op: \"Render Image\", args: [\"Raw\"] }\n        ]\n    },\n    {\n        name: \"Render Image: hex png\",\n        input: PNG_HEX,\n        expectedOutput: \"<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAFGElEQVRYw8VXXWwUVRT+7szszKzb3bbglqXbzbbQbYDQYJBGbWlTRUq1KtCmVYmECsHQKjE8mJrwYoIPpmIQEw2aaLA2PDRhATVpCxiLcYPBP0RQbIEVulNatt3SYX9mf2auD7QVlt3uLq3xPE3mnnvPd8/5zs8luD9hOzs7l1sslvJQKMR6PJ4fWlpafgWgAWAA0MnvlELSNEimDt65c6dpx44dB3JychplWUYgEGAAUFmWvxofHz9nNpvrwuHw72vXrt0KQJ0tAOJ0Oh8pKSlpFQRhmaZpAQB6o9FYBgATExPaJAAAAKWUEkKIqqq3Kisr7R0dHY+Lojhvw4YNnyXzyEwAiMvlai0sLNxHCNElUogHcKcoijIoCIIVADwez9aNGzd+nkiPS2bd6XSustvtSY2nElEUbVOO0bTkdGCS3d7hcLQwDKPDLCUcDt/YvHnz0WTeTgaAEQRheUoCEZKSxIIgLDh+/PhvPT09exOBSBYCSilV5iqLRFG0q6r62OSF1XQ8oAUCgVNpnE3TDIMUCoV+TJQJyQCgu7v7w2g0OjSjdUppOhxYt27d8tra2l2JACcFsHv37uEzZ848qyhK/2xIKAhCXmdn5wvJ6gAz0+bGxsZf1qxZU6FpWjRTw6FQyE0pVSmlmqZpkUzTcFrcbreSbqynRFXVYFVV1cMDAwP1kiS9Ul9ffzCZLof/QDRNUyilyqZNm75MpcvgfxZmrlItrkCl3Y7TCUHI5XJ9YzQan4pfGBoawvDwcCICngIQnSsAuuLiYgfLsvcs8DyPnJycRBxYDIBNxwszAli2wCC+uHrJq+y1n4sIc3e09PMKaLbeSCVJopIk3bUWi8Uc1dXVT/b19XXPppYz7euXHHji0ZXbBV53z7ai6q24FuW1n/r7GafTCVVV46tkNBqNlvb29v51XyR8adVCq9nAvTxw+QpijDD9X1ZULFxVD8ODdgBAbm4uyp4uQzg3/O/AyLLgef57n8/nq6mpKWYTxS9VCErMhkoCcBOyjIKKLTDoBby+Zw+sS1dA9akoWxjGoNtNqMGA/cb9sDXYUMVVoXSkFIQQuFyucpPJdEKn05U2NDQ0d3V1fZG2B1oqbEXWbOGDaSWGhd5kRlnZavRf+ANF+fkIyTKCExPE7/djm20bBoODWCQsgtlshiiKvlgs5uZ5fgUhhHAcl5eRB5bmGXaxDJkf/397UxO2NzVB1TQEJtNvdHQU5cZylGeVA6HpBjTm9XrbrFbrsUAg8OmhQ4fez4QDjI4jK1O0WIjcbexjY2OJ0pDJyspaPQmmoqampigTAFTTMJKixSJyu+RRWZYRPxYoirLIZDK9AQAcxy21WCyvZRIC6vVHPn6A168nBAnZyzIMDGYzHKWlYCwWJBgNSSQSOSvL8ttGo/E5SZL2ZcSBt3ovn3jnGUdrXhb/LgDTHa6FJMsw6PVgOQ7nLl4kPX19NBaLwWAwqHV1dSzLsgQAotHot0eOHDkM4PD9VEL65tcDnzQ9ZDlWNF9fy/H6/QCyGYaBRa/HjUuXEBobgzoyQm02G83Pz4fNZmOnpmSe5wclSXpvrt6G5MLJk88XFxbu5XU6650Lf16/rl32epm4YWTo5s2brc3NzcfSeuWmo/RRR8d5TdMOLlm8+JbA84Ucy+YCwKjfT8eDQTJp2BMMBvedPn16S1tb29m0W3fGAwTD8N91dS2zFxSs+Nvny77i9fr9fv/59vb2s1evXo1ket4/oXgLrXNMZZcAAAAASUVORK5CYII='>\",\n        recipeConfig: [\n            { op: \"Render Image\", args: [\"Hex\"] }\n        ]\n    },\n    {\n        name: \"Render Image: base64 jpg\",\n        input: JPG_B64,\n        expectedOutput: \"<img src='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQVFBQUFBQUFP/AABEIACAAIAMBEQACEQEDEQH/xAAbAAACAQUAAAAAAAAAAAAAAAAGBwkAAQIDCP/EAC4QAAIBAwIFAQcFAQAAAAAAAAECAwQFEQYSAAcIITFxIjJBQlJhwQkUFSORE//EABwBAAEFAQEBAAAAAAAAAAAAAAYAAgQFBwgDAf/EADARAAECBAQEBAYDAQAAAAAAAAECEQMEBSEABhIxQVFhoRMicZEUMnKBwdEVUrFC/9oADAMBAAIRAxEAPwCTPU+qDZ3jo6REnuUy71WQ4SJPG98d8Z7ADuT6EgSrlc/jiJaWAVHUHAPypG2pTXZ7AC6i4BDEi3kZETAMWKWQOW5PIfk8PYYDqif9y264XKrrZD5AnaGMeiIQMeuT9+M0jTJjHVOzK4iuiihP2SggN9Wo8ycESEaA0CGlI9AT7qfsw6YqmnFMwe33GropB4BnaaM+qOSP8wfvw6BM+CrVJTC4avqUtP3SsqDemk8iMJadYaPDSoegB90sfd/TBjpjU5u7yUdWiQ3GJd5EZ9iVPG9M98Z7EHuCR5yCdJolc/kSZaZATHSHt8qhtqS92exBukkAkggkdnpES4EWEXQbX3B5H8Hj7jEX/Vf1Xc3eTHUPqakm1HHTaZuGysskcNrp3V6YFoiju6FiyMhBAb5g3beBxT02VpmY/iJxST4mtSVeYuyCUp9BpD8nKuL4kzMSZp5hwQfKACLc7nv2bA5yz/UZ1fe9W2qyXOwUurDcqlKSCOywtR1gduynEkjRP385MQUZJOBwPZgyZLS8pEnJeaMMIDnXdLDqkBQ6WUSbAYnSFYiLiphRIYU9rWPe3cYy5m/qPatseqbrZLVp2k0q9tqWpJ0vkbVlWXXs3sxSLHH38ENKGGCDgjhZfyZLx5WHOTE0YgWHGiyWO11Ak9bJI2wp+sRERFQocPS1r3PYt3OC3pD6sObHO/n3Z6c32kk0xakasu8clrjT+pmWJY43UBgzFye5IxGx77cG6q0tTsufDTUMHxPESlPm4KISt+mk++nESUiTFQ8SEW06STbiLjv2fD16oemzTvO6WosGpoqilnt9VJNb7nQsq1FOshDEKWBBVht3KQQdo8FQRhs1WqpkrME1Dl2YqJ0lylSVEqSeFwDuNi4uHwZiVlqvIwlxN2FxuCLH/MKrp46NNBcnNe19xpbpcNRaqtka7P5IIgpI5lYCWONQM7gJE3kkZWRRghuPLMmdatXpFEGIhMOCs/8ALnUUnYknhYt1BNmxHkaXLSMXxASVDnwxr6jOjHQnN3Xdtu1XdblpzUt4YwN/GxrKlX/yiLGSRCp27URUMmQuTGpyzLl2Wc7VWhyS5eGhMSDDv5nGnUWYF7uS+lifmOwOPk9S5aejeIVFKjy44anS50zac5HVEVj00tVV1Nyqopbhcq5w086xkkA7QFVVBfCgfMckk54fArdTztX5SFMABIUCEpcBKQQpR4lyE7k8hbEn4SWpEjFWjdtzxOw7nHXWt9DQ6qRJ4isNwhG1JG911+lvwfhk8bpnLJ0LM0IRYJCJhAYE7Ef1VxZ9jdr2L4B6XVFSBKFXQe3UYTuqeVUFynhe8Wab93TKyQV9M0kM8KtjcI6iIh1DbVyFYZ2jPgcczR6RXqEtUGLLrAO/l1oLbHZSD0e4fg+DFM3LzI1IWPdj+8W0vyop7dUyy2izVD1s6CKW4VcktRUSICSqvUTMzlQSSFLYGTgcKBSa9XFJgwZdZHDyaEDrslAPc9cJU1Ly3mWse7n94cWiNCxaWR6iVlmuEq7WdfdRfpX8n44HHS+TMmw8swjGjELmFhiRskf1TxZ9zZ7WDYD6pVVT5CE2QO55nH//2Q=='>\",\n        recipeConfig: [\n            { op: \"Render Image\", args: [\"Base64\"] }\n        ]\n    },\n    {\n        name: \"Extract EXIF: nothing\",\n        input: \"\",\n        expectedOutput: \"Found 0 tags.\\n\",\n        recipeConfig: [\n            {\n                op: \"Extract EXIF\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Extract EXIF: hello world text (error)\",\n        input: \"hello world\",\n        expectedOutput: \"Could not extract EXIF data from image: Error: Invalid JPEG section offset\",\n        recipeConfig: [\n            {\n                op: \"Extract EXIF\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Extract EXIF: meerkat jpeg\",\n        input: EXIF_JPG_HEX,\n        expectedOutput: [\n            \"Found 28 tags.\",\n            \"\",\n            \"Make: SONY\",\n            \"Model: DSC-H5\",\n            \"XResolution: 70\",\n            \"YResolution: 70\",\n            \"ResolutionUnit: 2\",\n            \"Software: Pictomio 1.2.31.0\",\n            \"ModifyDate: 1278286273\",\n            \"ExposureTime: 0.008\",\n            \"FNumber: 3.7\",\n            \"ExposureProgram: 3\",\n            \"ISO: 200\",\n            \"DateTimeOriginal: 1220275486\",\n            \"CreateDate: 1220275486\",\n            \"ShutterSpeedValue: 6.965784\",\n            \"ApertureValue: 3.775051\",\n            \"ExposureCompensation: 0.3\",\n            \"MaxApertureValue: 3\",\n            \"MeteringMode: 5\",\n            \"LightSource: 10\",\n            \"Flash: 16\",\n            \"FocalLength: 72\",\n            \"CustomRendered: 0\",\n            \"ExposureMode: 1\",\n            \"WhiteBalance: 1\",\n            \"SceneCaptureType: 0\",\n            \"Contrast: 0\",\n            \"Saturation: 0\",\n            \"Sharpness: 0\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Extract EXIF\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Extract EXIF: avatar jpeg\",\n        input: NO_EXIF_JPG_HEX,\n        expectedOutput: \"Found 0 tags.\\n\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Extract EXIF\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Remove EXIF: hello world text (error)\",\n        input: \"hello world\",\n        expectedOutput: \"Could not remove EXIF data from image: Given data is not jpeg.\",\n        recipeConfig: [\n            {\n                op: \"Remove EXIF\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Remove EXIF: meerkat jpeg (has EXIF)\",\n        input: EXIF_JPG_HEX,\n        expectedOutput: \"Found 0 tags.\\n\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Remove EXIF\",\n                args: [],\n            },\n            {\n                op: \"Extract EXIF\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Extract EXIF: avatar jpeg (has no EXIF)\",\n        input: NO_EXIF_JPG_HEX,\n        expectedOutput: \"Found 0 tags.\\n\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Remove EXIF\",\n                args: [],\n            },\n            {\n                op: \"Extract EXIF\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Extract RGBA\",\n        input: \"424d460400000000000036040000280000000400000004000000010008000000000010000000120b0000120b0000000100000001000000c8000000cf000000d7000000df000000e7000000ef000000f7000000ff000083000000ac000000d5000000ff000000000083000000ac000000d5000000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f070d05030b01090c040e060008020a\",\n        expectedOutput: \"0 200 0 0 0 131 0 215 0 0 0 213 131 0 0 0 231 0 213 0 0 0 247 0 0 223 0 0 0 255 0 207 0 0 0 172 255 0 0 0 255 0 172 0 0 0 239 0\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Extract RGBA\",\n                args: [\" \", false]\n            }\n        ]\n    },\n    {\n        name: \"Extract LSB\",\n        input: PNG_HEX,\n        expectedOutput: \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000208000000000000000000008000000000000000000000000248000000200240000000208908000000200240000000200821000000200240000000061249000000240000000000209b69000001a49b00000000a204a1200001a49b00000009800414000001a49b0000000035db6c00000094924000000086dffc20000df6dec8000001e10014a0000df6dec800002564924b00000df6dec80000009a6db20000007edb4124804177fffba0002fffff69249044e0924bc4002fffff6924905fb2db6d04002fffff692490416d2490040001bfffcc92030dbffffdc00037fffffdb6d302c6db6d700037fffffdb6d327eb6db6148037fffffdb6d30db4000014800dffffeb6d9aefffffff640\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Extract LSB\",\n                args: [\"B\", \"G\", \"A\", \"\", \"Column\", 2]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"View Bit Plane\",\n        input: PNG_HEX,\n        expectedOutput: \"89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000140494441547801c5c1416ea3400000c1ee11ffff726fe6808410186ce26c95fde0432a154f0cdea4b2aa505151519954ee1a5c50995454aea8ac54ae2c5ca8982a3ea132551c199c507942e58ec1898adf50f1cae084ca15952b2a152a47067f40a5e2c8e00f54a81c199ca8b85271a542a5e2c8e005159527542ace0c5ea8a8f844c54ae5ccc217555c197c41c55d83ff6cf0052a772ddca052b1a752b1a772d7c2432a4f2c3c50f1d4c20b2a1593ca918a4965afe2cac29b2a562a93ca56c55d0b2754b62a269555c554b15251a9b8637040e5884ac54a654ba5a2624be5cce040c5918a55c55ec5a4a232a9549c197c48655239523155bc3278a862af624be5ccc2072aaea854a8549c5978834a85ca5ec5918a57ec076f50a958a9546ca94c1557ec071754a68a2d958a270637544c2a2abf69e1a68a95ca54b152a978d73f2e08bd57b6f839a00000000049454e44ae426082\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"View Bit Plane\",\n                args: [\"Green\", 3]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"Randomize Colour Palette\",\n        \"input\": PNG_HEX,\n        expectedOutput: \"89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af4000004f3494441547801c5c10b50cf0700c0f16fbfff8f0ae5f157ed9f1e446358ab28b3913f29b3f89f65deab9859c624cafbf557b6eb3662799c8a285c541479ed3c125d2d2cba52e45d4da8bc66b99d1ebbff6ddd75ddeff7efef75fb7c8c5cb5b10dbc869ccbd1ac4f0c67dfc55cba1f9ec32dd303bc0e854aadd1f20ace5f29e1c9346b4c3982aada03b3285b540f4b099f664ca5720321a34c582466b2f38f3e1842a1526bb4e811efe64a56403a465dc270a998cb517b47e62f7842870b6aaabc5b519627a253784e606a8e25470fbbe358a0c2fd692f9c9668b8bda084e77d5f2247448f790f42a9b9da915d415a6020508a3e49e207e8ec7961cb9594545657e5e26e0fdaf9df5014998a141119f16eae24b4db84f7d775bc8e3e3e9e24f12fabaccf282210292232ce6a5e12dab58e37656be7cf8893916498d72345404681d7365ae217b18b969495261019998f8569145204e47c3e99b7a58f8f278fa76e478a808c522f23de165b3b7f3aee9c8e14011956e9b6841c29419f5d8bfd6889ad9d3fc1c9c654be08428a808cfc9a0c02e7d711973a823751569ac0c79dd72347408fb9ee3d102f4c616d9c82579596dd8df1b5c58caf2d26625d2fe488b420b16c129e3d7825534ccbb01a3881f0803354adba49c5853ce488bc037ba66b48c93586e20298885e02ff338177c0a1ed600c256200973699b0f130cd9dafcd20c87a3bcd9d29d90b1462081103c4d444d1bee2139aab9b7c87ec50479adbd47f13a0c610227a94af8a6565ce118ce72dc1618d3b4ded6f678465840dd905bd519f3e455305e67eb4f1db8ed94fdeb444448f0dbb94740bcec029db9fa636fc99cbf176a708b9e44198af3573469b323cb992469694b031bd133b2a955cb2a8461f0119a3b6f810fcac81619ea1c42cbf43a3711393d8727b1ee37ad6a263b1388cc8a872264cc8a7d1c97116d407ede1ab9c44ac3de610e5f32372142ab5468b84d091d984d79a90147e8dfb8322e8bcf71cdf370ce07ae6158606ee479c164c8794ddf88f76664c5d3d96579ee171db85d9917524b996102e2e65d882753c4c28e690714f9e3ffa1b2902122a0e6571a3c8864657b304ee2e4d6579e1038e0879cc0c716765c67ec2ee1d63cbaa9154db0760ffcc8e0fd7f643d3ba9c31ce69fc3c68124507bae060eb8379dc45e4884850aa4730ca2e97e68a572ca178055c7ff411cb56c4002bf86ef6717c53f94f3c3a4ff72590d6fa09ef134ae0a983ec1efc2b720424fc55b8107dbea8fe81134ecee8687c4d68ae62de45bc636ad099782b126b8f39c81190b0f292127d5295cb98f2b415aa2a055dcb2fb035b081a686f48ee5f4d37ee80c9d91498855007204240cc9ea89efeabec871ec5443c381b1980c9a4f456d1233a38d68ce667c2ee7c2927019e1ce8bb84ce48848f832d10bb1f50dd6540d03d268d4677d39ed1d52514407d3b6d8830327dc49b0984574507b3a3b0573fb5806f762afa37379790e7783ab0849ee0f66fd91a350a9355a24d4bb754239ca02df597598d52dc5c2bb339503cde95213cff3a089240d7f48fd522f9c943eec1db088e53b0412631ea193dec188e4b3a7d9d6eb535a62e4aa8d6da005bfe4d9125de082dbcd6f69ca6ecc66c2166ca329eb198ee4bf5780a5ba3b861030c05cd732eea4acc3c42106671f0d52ac6738727064325b8ff5c752dd1d4329546a8d1603bc2c51f2bbf3356eae2c64b379374c964561b5f53c36d65e14fd3683c7f7179232d686bdf9777915ff00ec08ae8ecb66a3370000000049454e44ae426082\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Randomize Colour Palette\",\n                args: [\"myseed\"]\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\"]\n            }\n        ]\n    },\n    /* { This operation only works in a browser\n        name: \"Optical Character Recognition\",\n        input: \"iVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAIAAABqhmJGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAASuSURBVHhe7dftVdswAIbRzsVAzMM0XabDUCOUxLYsWW4Jp+/pvf9w9GH76CHw4x2IJWAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAI9p8G/PbyY8rL2686g8t+vnqHTyfgIYfvz/26veTXn/UKX8+f0EU9bHrtu/6KfAN/AwEXAj7lFf2TBFw4nae8on+SgIvJ01n/KLzpDK+L3bT/Ap4O+HC+V12mTH+M3gzcLbIY/EO6HfxYp13k09nb6r3UqcdnjoCL3ll72J26h+35Oxy2XvZ0wOLaXq9v2+F1UC+7RZtMZ/DnfX1lwDOPzwUCLo7O2trtDK8H3M/iqoc6bj1subT68XTA/F7bGJooyzKbhTvLPHY8eJLHlbNX1DqYUVfdXbqwJjsCLsans37aNNJM6w68OR0wv9f9ymKw3k67yn2ZZpHlg3a3zis60s6oV+ZvlzMCLoanc3Dsdt9TdWT/lM8OmNjr5KY72jmzq1zfrbvXtVtmRMDF8HTWcgaaqIrD1U4G/MFewxrW262s5jS/Fzpmdts6mnHy+Fwl4GJ0OjsNrG1P/y7CNo3+gEt7jW56MVprNed7A/5w+n6YJ+BieDpnj/jO6pweTz0acGWvmZveL9XOmd3x6wKuTt8PEwRczLRw4eje1XX7c/cDruw1uuneOu2c4aOvzI57mJhRh1xZlQ0BF+Oz9vcF96fuB1zYa7R2b5mD6/XSwdfg8snj4q21+W/L02dfzIxhQMDFyTm6Hd7m+JYP7rPKT5sRuzhOBywm91rUkYc3fV9ltchtr8VmzuGOdfDB9N1tFYefNfdXLmyGjNZkhoCLUQufVqd/7z7rUcLW/XieDvg0s9difNOdRV5ePibt5vTuazusWbF9rs2E5v4mH58LBFyMW7g5OID7s9cMuTygmt9rcNPb5MrAz0lHc3Z9Ht7XZsxqxO36ZtLR/c0+PpMEzLOc/4LhrwmYZ6lfywJ+JgHzJPr9DgLmi23/zdXvcwmYL7YKWL1PJ2AIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmAIJmCI9f7+G6yFxVg/GyYwAAAAAElFTkSuQmCC\",\n        expectedOutput: \"Tesseract.js\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"From Base64\",\n                \"args\": [\"A-Za-z0-9+/=\", true]\n            },\n            {\n                \"op\": \"Optical Character Recognition\",\n                \"args\": [false]\n            }\n        ]\n    } */\n]);\n"
  },
  {
    "path": "tests/operations/tests/IndexOfCoincidence.mjs",
    "content": "/**\n * Index of Coincidence tests.\n *\n * @author George O [georgeomnet+cyberchef@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Index of Coincidence\",\n        input: \"Hello world, this is a test to determine the correct IC value.\",\n        expectedMatch: /^Index of Coincidence: 0\\.07142857142857142\\nNormalized: 1\\.857142857142857/,\n        recipeConfig: [\n            {\n                \"op\": \"Index of Coincidence\",\n                \"args\": []\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/JA3Fingerprint.mjs",
    "content": "/**\n * JA3Fingerprint tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"JA3 Fingerprint: TLS 1.0\",\n        input: \"16030100a4010000a00301543dd2dd48f517ca9a93b1e599f019fdece704a23e86c1dcac588427abbaddf200005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101\",\n        expectedOutput: \"503053a0c5b2bd9b9334bf7f3d3b8852\",\n        recipeConfig: [\n            {\n                \"op\": \"JA3 Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n    {\n        name: \"JA3 Fingerprint: TLS 1.1\",\n        input: \"16030100a4010000a00302543dd2ed907e47d0086f34bee2c52dd6ccd8de63ba9387f5e810b09d9d49b38000005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101\",\n        expectedOutput: \"a314eb64cee6cb832aaaa372c8295bab\",\n        recipeConfig: [\n            {\n                \"op\": \"JA3 Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n    {\n        name: \"JA3 Fingerprint: TLS 1.2\",\n        input: \"1603010102010000fe0303543dd3283283692d85f9416b5ccc65d2aafca45c6530b3c6eafbf6d371b6a015000094c030c02cc028c024c014c00a00a3009f006b006a0039003800880087c032c02ec02ac026c00fc005009d003d00350084c012c00800160013c00dc003000ac02fc02bc027c023c013c00900a2009e0067004000330032009a009900450044c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff01000041000b000403000102000a000600040018001700230000000d002200200601060206030501050205030401040204030301030203030201020202030101000f000101\",\n        expectedOutput: \"c1a36e1a870786cc75edddc0009eaf3a\",\n        recipeConfig: [\n            {\n                \"op\": \"JA3 Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n    {\n        name: \"JA3 Fingerprint: TLS 1.3\",\n        input: \"1603010200010001fc03034355d402c132771a9386b6e9994ae37069e0621af504c26673b1343843c21d8d0000264a4a130113021303c02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010001addada0000ff01000100000000180016000013626c6f672e636c6f7564666c6172652e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b000201000028002b00295a5a000100001d0020cf78b9167af054b922a96752b43973107b2a57766357dd288b2b42ab5df30e08002d00020101002b000b0acaca7f12030303020301000a000a00085a5a001d001700180a0a000100001500e4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"4826a90ec2daf4f7b4b64cc1c8bd343b\",\n        recipeConfig: [\n            {\n                \"op\": \"JA3 Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/JA3SFingerprint.mjs",
    "content": "/**\n * JA3SFingerprint tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"JA3S Fingerprint: TLS 1.0\",\n        input: \"160301003d020000390301543dd2ddedbfe33895bd6bc676a3fa6b9fe5773a6e04d5476d1af3bcbc1dcbbb00c011000011ff01000100000b00040300010200230000\",\n        expectedOutput: \"bed95e1b525d2f41db3a6d68fac5b566\",\n        recipeConfig: [\n            {\n                \"op\": \"JA3S Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n    {\n        name: \"JA3S Fingerprint: TLS 1.1\",\n        input: \"160302003d020000390302543dd2ed88131999a0120d36c14a4139671d75aae3d7d7779081d3cf7dd7725a00c013000011ff01000100000b00040300010200230000\",\n        expectedOutput: \"130fac2dc19b142500acb0abc63b6379\",\n        recipeConfig: [\n            {\n                \"op\": \"JA3S Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n    {\n        name: \"JA3S Fingerprint: TLS 1.2\",\n        input: \"160303003d020000390303543dd328b38b445686739d58fab733fa23838f575e0e5ad9a1b9baace6cc3b4100c02f000011ff01000100000b00040300010200230000\",\n        expectedOutput: \"ccc514751b175866924439bdbb5bba34\",\n        recipeConfig: [\n            {\n                \"op\": \"JA3S Fingerprint\",\n                \"args\": [\"Hex\", \"Hash digest\"]\n            }\n        ],\n    },\n    // This Server Hello was based on draft 18 of the TLS1.3 spec which does not include a Session ID field, leading it to fail.\n    // The published version of TLS1.3 does require a legacy Session ID field (even if it is empty).\n    // {\n    //     name: \"JA3S Fingerprint: TLS 1.3\",\n    //     input: \"16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c\",\n    //     expectedOutput: \"986ae432c402479fe7a0c6fbe02164c1\",\n    //     recipeConfig: [\n    //         {\n    //             \"op\": \"JA3S Fingerprint\",\n    //             \"args\": [\"Hex\", \"Hash digest\"]\n    //         }\n    //     ],\n    // },\n]);\n"
  },
  {
    "path": "tests/operations/tests/JA4.mjs",
    "content": "/**\n * JA4 tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"JA4 Fingerprint: TLS 1.3\",\n        input: \"1603010200010001fc0303b2c03e7ba990ef540c316a665d4d925f8e9079ac4b15687e587dc99016e75a6c20d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f00205a5a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193fafa000000000024002200001f636f6e74656e742d6175746f66696c6c2e676f6f676c65617069732e636f6d0033002b00293a3a000100001d0020fb2cd8ef3d605b96ab03119ec4f30a6e2088cb1af86c41a81feace8706068c50000d001200100403080404010503080505010806060100230000000b00020100ff01000100000a000a00083a3a001d00170018001b000302000244690005000302683200120000002d000201010010000e000c02683208687474702f312e31000500050100000000002b0007060a0a03040303001700001a1a000100001500b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"t13d1516h2_8daaf6152771_e5627efa2ab1\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4 Fingerprint\",\n                \"args\": [\"Hex\", \"JA4\"]\n            }\n        ],\n    },\n    {\n        name: \"JA4 Fingerprint: TLS 1.3 Original Rendering\",\n        input: \"1603010200010001fc0303b2c03e7ba990ef540c316a665d4d925f8e9079ac4b15687e587dc99016e75a6c20d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f00205a5a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193fafa000000000024002200001f636f6e74656e742d6175746f66696c6c2e676f6f676c65617069732e636f6d0033002b00293a3a000100001d0020fb2cd8ef3d605b96ab03119ec4f30a6e2088cb1af86c41a81feace8706068c50000d001200100403080404010503080505010806060100230000000b00020100ff01000100000a000a00083a3a001d00170018001b000302000244690005000302683200120000002d000201010010000e000c02683208687474702f312e31000500050100000000002b0007060a0a03040303001700001a1a000100001500b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"t13d1516h2_acb858a92679_5276cb03a33b\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4 Fingerprint\",\n                \"args\": [\"Hex\", \"JA4 Original Rendering\"]\n            }\n        ],\n    },\n    {\n        name: \"JA4 Fingerprint: TLS 1.3 with whitespace-only ALPN\",\n        input: \"1603010200010001fc0303ed338a18e711d670cdc472ff570a5b59f1ace12e5365918bf68bf845019147b6207e4437bfb062d98a4aeb753be8f09022a9dc9413d7694dad4db57fcdcf076e820024130213031301c02cc030c02bc02fcca9cca8c024c028c023c027009f009e006b006700ff0100018f0000001800160000136465762e636f6e74656e74677261622e6e6574000b000403000102000a00160014001d0017001e00190018010001010102010301040023000000100004000201200016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b00050403040303002d00020101003300260024001d00207af053336d5e2c1675aa4c6ce78de5e5fdbd296538113f051ea17ccb64289f22001500d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"t13d181220_85036bcba153_d41ae481755e\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4 Fingerprint\",\n                \"args\": [\"Hex\", \"JA4\"]\n            }\n        ],\n    },\n    {\n        name: \"JA4 Fingerprint: TLS 1.3 with ALPN containing a whitespace\",\n        input: \"1603010200010001fc0303273682a603be3f64dd025df4ad0f4d2d13043c3a233405a68bb29b865808749a20f4dfc40242b2fce38fae26c516ef9bef20a1b9349eba3c003780168d72471f5c0024130213031301c02cc030c02bc02fcca9cca8c024c028c023c027009f009e006b006700ff0100018f0000001800160000136465762e636f6e74656e74677261622e6e6574000b000403000102000a00160014001d0017001e0019001801000101010201030104002300000010000500030261200016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b00050403040303002d00020101003300260024001d0020f4dd1567bd858d3a9f1d88db1fee6a10ab0ea1aa6afe96ffb6a7c4d79dea4075001500d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"t13d181260_85036bcba153_d41ae481755e\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4 Fingerprint\",\n                \"args\": [\"Hex\", \"JA4\"]\n            }\n        ],\n    },\n    {\n        name: \"JA4 Fingerprint: TLS 1.2\",\n        input: \"1603010200010001fc0303ecb2691addb2bf6c599c7aaae23de5f42561cc04eb41029acc6fc050a16ac1d22046f8617b580ac9358e2aa44e306d52466bcc989c87c8ca64309f5faf50ba7b4d0022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f00350100019100000021001f00001c636f6e74696c652e73657276696365732e6d6f7a696c6c612e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d00208909858fbeb6ed2f1248ba5b9e2978bead0e840110192c61daed0096798b184400170041044d183d91f5eed35791fa982464e3b0214aaa5f5d1b78616d9b9fbebc22d11f535b2f94c686143136aa795e6e5a875d6c08064ad5b76d44caad766e2483012748002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c000240010015007a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"t13d1715h2_5b57614c22b0_3d5424432f57\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4 Fingerprint\",\n                \"args\": [\"Hex\", \"JA4\"]\n            }\n        ],\n    },\n    {\n        name: \"JA4 Fingerprint: TLS 1.2 Original Rendering\",\n        input: \"1603010200010001fc0303ecb2691addb2bf6c599c7aaae23de5f42561cc04eb41029acc6fc050a16ac1d22046f8617b580ac9358e2aa44e306d52466bcc989c87c8ca64309f5faf50ba7b4d0022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f00350100019100000021001f00001c636f6e74696c652e73657276696365732e6d6f7a696c6c612e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d00208909858fbeb6ed2f1248ba5b9e2978bead0e840110192c61daed0096798b184400170041044d183d91f5eed35791fa982464e3b0214aaa5f5d1b78616d9b9fbebc22d11f535b2f94c686143136aa795e6e5a875d6c08064ad5b76d44caad766e2483012748002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c000240010015007a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"t13d1715h2_5b234860e130_014157ec0da2\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4 Fingerprint\",\n                \"args\": [\"Hex\", \"JA4 Original Rendering\"]\n            }\n        ],\n    },\n    {\n        name: \"JA4Server Fingerprint: TLS 1.2 h2 ALPN\",\n        input: \"16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832\",\n        expectedOutput: \"t1204h2_cca9_1428ce7b4018\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4Server Fingerprint\",\n                \"args\": [\"Hex\", \"JA4S\"]\n            }\n        ]\n    },\n    {\n        name: \"JA4Server Fingerprint: TLS 1.2 h2 ALPN Raw\",\n        input: \"16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832\",\n        expectedOutput: \"t1204h2_cca9_0000,ff01,000b,0010\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4Server Fingerprint\",\n                \"args\": [\"Hex\", \"JA4S Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"JA4Server Fingerprint: TLS 1.3\",\n        input: \"160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304\",\n        expectedOutput: \"t130200_1301_234ea6891581\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4Server Fingerprint\",\n                \"args\": [\"Hex\", \"JA4S\"]\n            }\n        ]\n    },\n    {\n        name: \"JA4Server Fingerprint: TLS 1.3 Raw\",\n        input: \"160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304\",\n        expectedOutput: \"t130200_1301_0033,002b\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4Server Fingerprint\",\n                \"args\": [\"Hex\", \"JA4S Raw\"]\n            }\n        ]\n    },\n    {\n        name: \"JA4Server Fingerprint: TLS 1.3 non-ascii ALPN\",\n        input: \"160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304\",\n        expectedOutput: \"t130200_1301_234ea6891581\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4Server Fingerprint\",\n                \"args\": [\"Hex\", \"JA4S\"]\n            }\n        ]\n    },\n    {\n        name: \"JA4Server Fingerprint: TLS 1.3 non-ascii ALPN Raw\",\n        input: \"160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304\",\n        expectedOutput: \"t130200_1301_0033,002b\",\n        recipeConfig: [\n            {\n                \"op\": \"JA4Server Fingerprint\",\n                \"args\": [\"Hex\", \"JA4S Raw\"]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/JSONBeautify.mjs",
    "content": "/**\n * JSONBeautify tests.\n *\n * @author Phillip Nordwall [Phillip.Nordwall@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"JSON Beautify: space, ''\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\" \", false, false],\n            },\n        ],\n    },\n    {\n        name: \"JSON Beautify: space, number\",\n        input: \"42\",\n        expectedOutput: \"42\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\" \", false, false],\n            },\n        ],\n    },\n    {\n        name: \"JSON Beautify: space, string\",\n        input: \"\\\"string\\\"\",\n        expectedOutput: \"\\\"string\\\"\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\" \", false, false],\n            },\n            {\n                op: \"HTML To Text\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JSON Beautify: space, boolean\",\n        input: \"false\",\n        expectedOutput: \"false\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\" \", false, false],\n            },\n        ],\n    },\n    {\n        name: \"JSON Beautify: space, emptyList\",\n        input: \"[]\",\n        expectedOutput: \"[]\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\" \", false, false],\n            },\n        ],\n    },\n    {\n        name: \"JSON Beautify: space, list\",\n        input: \"[2,1]\",\n        expectedOutput: \"[\\n 2,\\n 1\\n]\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\" \", false, false],\n            },\n        ],\n    },\n    {\n        name: \"JSON Beautify: tab, list\",\n        input: \"[2,1]\",\n        expectedOutput: \"[\\n\\t2,\\n\\t1\\n]\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\"\\t\", false, false],\n            },\n        ],\n    },\n    {\n        name: \"JSON Beautify: space, object\",\n        input: \"{\\\"second\\\":2,\\\"first\\\":3}\",\n        expectedOutput: \"{\\n \\\"second\\\": 2,\\n \\\"first\\\": 3\\n}\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\" \", false, false],\n            },\n            {\n                op: \"HTML To Text\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JSON Beautify: tab, nested\",\n        input: \"[2,{\\\"second\\\":2,\\\"first\\\":3,\\\"beginning\\\":{\\\"j\\\":\\\"3\\\",\\\"i\\\":[2,3,false]}},1,2,3]\",\n        expectedOutput: \"[\\n\\t2,\\n\\t{\\n\\t\\t\\\"second\\\": 2,\\n\\t\\t\\\"first\\\": 3,\\n\\t\\t\\\"beginning\\\": {\\n\\t\\t\\t\\\"j\\\": \\\"3\\\",\\n\\t\\t\\t\\\"i\\\": [\\n\\t\\t\\t\\t2,\\n\\t\\t\\t\\t3,\\n\\t\\t\\t\\tfalse\\n\\t\\t\\t]\\n\\t\\t}\\n\\t},\\n\\t1,\\n\\t2,\\n\\t3\\n]\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\"\\t\", false, false],\n            },\n            {\n                op: \"HTML To Text\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JSON Beautify: tab, nested, sorted\",\n        input: \"[2,{\\\"second\\\":2,\\\"first\\\":3,\\\"beginning\\\":{\\\"j\\\":\\\"3\\\",\\\"i\\\":[2,3,false]}},1,2,3]\",\n        expectedOutput: \"[\\n\\t2,\\n\\t{\\n\\t\\t\\\"beginning\\\": {\\n\\t\\t\\t\\\"i\\\": [\\n\\t\\t\\t\\t2,\\n\\t\\t\\t\\t3,\\n\\t\\t\\t\\tfalse\\n\\t\\t\\t],\\n\\t\\t\\t\\\"j\\\": \\\"3\\\"\\n\\t\\t},\\n\\t\\t\\\"first\\\": 3,\\n\\t\\t\\\"second\\\": 2\\n\\t},\\n\\t1,\\n\\t2,\\n\\t3\\n]\",\n        recipeConfig: [\n            {\n                op: \"JSON Beautify\",\n                args: [\"\\t\", true, false],\n            },\n            {\n                op: \"HTML To Text\",\n                args: []\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/JSONMinify.mjs",
    "content": "/**\n * JSONMinify tests.\n *\n * @author Phillip Nordwall [Phillip.Nordwall@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"JSON Minify: ''\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: number\",\n        input: \"42\",\n        expectedOutput: \"42\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: number\",\n        input: \"4.2\",\n        expectedOutput: \"4.2\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: string\",\n        input: \"\\\"string\\\"\",\n        expectedOutput: \"\\\"string\\\"\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: boolean\",\n        input: \"false\",\n        expectedOutput: \"false\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: emptyList\",\n        input: \"[\\n \\n  \\t]\",\n        expectedOutput: \"[]\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: list\",\n        input: \"[2,\\n  \\t1]\",\n        expectedOutput: \"[2,1]\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: object\",\n        input: \"{\\n \\\"second\\\": 2,\\n \\\"first\\\": 3\\n}\",\n        expectedOutput: \"{\\\"second\\\":2,\\\"first\\\":3}\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"JSON Minify: tab, nested\",\n        input: \"[\\n\\t2,\\n\\t{\\n\\t\\t\\\"second\\\": 2,\\n\\t\\t\\\"first\\\": 3,\\n\\t\\t\\\"beginning\\\": {\\n\\t\\t\\t\\\"j\\\": \\\"3\\\",\\n\\t\\t\\t\\\"i\\\": [\\n\\t\\t\\t\\t2,\\n\\t\\t\\t\\t3,\\n\\t\\t\\t\\tfalse\\n\\t\\t\\t]\\n\\t\\t}\\n\\t},\\n\\t1,\\n\\t2,\\n\\t3\\n]\",\n        expectedOutput: \"[2,{\\\"second\\\":2,\\\"first\\\":3,\\\"beginning\\\":{\\\"j\\\":\\\"3\\\",\\\"i\\\":[2,3,false]}},1,2,3]\",\n        recipeConfig: [\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/JSONtoCSV.mjs",
    "content": "/**\n * JSON to CSV tests.\n *\n * @author mshwed [m@ttshwed.com]\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst EXPECTED_CSV_SINGLE = \"a,b,c\\r\\n1,2,3\\r\\n\";\nconst EXPECTED_CSV_MULTIPLE = \"a,b,c\\r\\n1,2,3\\r\\n1,2,3\\r\\n\";\nconst EXPECTED_CSV_EMPTY = \"\\r\\n\\r\\n\";\n\nTestRegister.addTests([\n    {\n        name: \"JSON to CSV: strings as values\",\n        input: JSON.stringify({a: \"1\", b: \"2\", c: \"3\"}),\n        expectedOutput: EXPECTED_CSV_SINGLE,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: numbers as values\",\n        input: JSON.stringify({a: 1, b: 2, c: 3}),\n        expectedOutput: EXPECTED_CSV_SINGLE,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: numbers and strings as values\",\n        input: JSON.stringify({a: 1, b: \"2\", c: 3}),\n        expectedOutput: EXPECTED_CSV_SINGLE,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: boolean and null as values\",\n        input: JSON.stringify({a: false, b: null, c: 3}),\n        expectedOutput: \"a,b,c\\r\\nfalse,null,3\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: JSON as an array\",\n        input: JSON.stringify([{a: 1, b: \"2\", c: 3}]),\n        expectedOutput: EXPECTED_CSV_SINGLE,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: multiple JSON values in an array\",\n        input: JSON.stringify([{a: 1, b: \"2\", c: 3}, {a: 1, b: \"2\", c: 3}]),\n        expectedOutput: EXPECTED_CSV_MULTIPLE,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: empty JSON\",\n        input: JSON.stringify({}),\n        expectedOutput: EXPECTED_CSV_EMPTY,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: empty JSON in array\",\n        input: JSON.stringify([{}]),\n        expectedOutput: EXPECTED_CSV_EMPTY,\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: nested JSON\",\n        input: JSON.stringify({a: 1, b: {c: 2, d: 3}}),\n        expectedOutput: \"a,b.c,b.d\\r\\n1,2,3\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: nested array\",\n        input: JSON.stringify({a: 1, b: [2, 3]}),\n        expectedOutput: \"a,b.0,b.1\\r\\n1,2,3\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: nested JSON, nested array\",\n        input: JSON.stringify({a: 1, b: {c: [2, 3], d: 4}}),\n        expectedOutput: \"a,b.c.0,b.c.1,b.d\\r\\n1,2,3,4\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: nested array, nested JSON\",\n        input: JSON.stringify({a: 1, b: [{c: 3, d: 4}]}),\n        expectedOutput: \"a,b.0.c,b.0.d\\r\\n1,3,4\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: nested array, nested array\",\n        input: JSON.stringify({a: 1, b: [[2, 3]]}),\n        expectedOutput: \"a,b.0.0,b.0.1\\r\\n1,2,3\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    },\n    {\n        name: \"JSON to CSV: nested JSON, nested JSON\",\n        input: JSON.stringify({a: 1, b: { c: { d: 2, e: 3}}}),\n        expectedOutput: \"a,b.c.d,b.c.e\\r\\n1,2,3\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JSON to CSV\",\n                args: [\",\", \"\\\\r\\\\n\"]\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/JSONtoYAML.mjs",
    "content": "/**\n * YAML tests.\n *\n * @author ccarpo [ccarpo@gmx.net]\n *\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst EXAMPLE_YAML = `number: 3\\nplain: string\\nblock: |\\n  two\\n  lines`;\nconst EXAMPLE_JSON = `{ \"number\": 3, \"plain\": \"string\" }`;\n\nTestRegister.addTests([\n    {\n        name: \"YAML to JSON\",\n        input: EXAMPLE_YAML,\n        expectedOutput: JSON.stringify({\n            \"number\": 3,\n            \"plain\": \"string\",\n            \"block\": \"two\\nlines\\n\"\n        }, null, 4),\n        recipeConfig: [\n            {\n                op: \"YAML to JSON\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"JSON to YAML\",\n        input: EXAMPLE_JSON,\n        expectedOutput: `number: 3\\nplain: string\\n`,\n        recipeConfig: [\n            {\n                op: \"JSON to YAML\",\n                args: [],\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/JWK.mjs",
    "content": "/**\n * JWK conversion\n *\n * @author cplussharp\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n// test data for RSA key pair\nconst RSA_512 = {\n    private: {\n        pem1: `-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL\nNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF\nF7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL\nWQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p\n6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf\nRWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw\nNSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o=\n-----END RSA PRIVATE KEY-----`,\n        pem8: `-----BEGIN PRIVATE KEY-----\nMIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp\nruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx\n2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB\nvuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox\n/EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM\ndQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3\nJ0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe\nJzOq+X832g==\n-----END PRIVATE KEY-----`,\n        jwk: {\n            \"kty\": \"RSA\",\n            \"n\": \"8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w\",\n            \"e\": \"AQAB\",\n            \"d\": \"OJUpM0lv36MAQR3WAwsFF7DOy-LnigteCvaNWiNVxZ6jByB5Qb7sall_Qlu9sFI0ZwrlVcKS0kldee7JTYlLWQ\",\n            \"p\": \"_dQoR862lOBPW1iZ2FqqPGox_EGibRGL6u31qOXOzq8\",\n            \"q\": \"9L85B8_Gnb7p6Af7_wpmafL277OV4X4xBfzMR-TUzHU\",\n            \"dp\": \"Gr5UtCQidpMfqVcvdm0vDIh_1b0wmN9FZ65EuNPlsz8\",\n            \"dq\": \"ReysOpMeR8tXwLcnRAKQqAyGiI1icP5Au1kydAfo1FE\",\n            \"qi\": \"V6dwNSyj0feakeD890gmId-lvl_w_3oUXiczqvl_N9o\"\n        }\n    },\n    public: {\n        pem1: `-----BEGIN RSA PUBLIC KEY-----\nMEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB\n757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAE=\n-----END RSA PUBLIC KEY-----`,\n        pem8: `-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM\nYKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==\n-----END PUBLIC KEY-----`,\n        cert: `-----BEGIN CERTIFICATE-----\nMIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL\nBQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3\nMTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA\nMEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB\n757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p\npnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP\nBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx\ngmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z\nIA==\n-----END CERTIFICATE-----`,\n        jwk: {\n            \"kty\": \"RSA\",\n            \"n\": \"8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w\",\n            \"e\": \"AQAB\"\n        }\n    }\n};\n\n// test data for EC key pair\nconst EC_P256 = {\n    private: {\n        pem1: `-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49\nAwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC\na6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==\n-----END EC PRIVATE KEY-----`,\n        pem8: `-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw\nYBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC\n6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p\n-----END PRIVATE KEY-----`,\n        jwk: {\n            \"kty\": \"EC\",\n            \"crv\": \"P-256\",\n            \"x\": \"DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk\",\n            \"y\": \"CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk\",\n            \"d\": \"21OPBSSB8CJLCqBwYBdbITS54hbqfaTf3l2ZBne8avg\"\n        }\n    },\n    public: {\n        pem8: `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ\ngusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==\n-----END PUBLIC KEY-----`,\n        cert: `-----BEGIN CERTIFICATE-----\nMIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw\nFTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx\nNzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO\nPQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr\nqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM\ngcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD\nVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN\nac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80\nylxt\n-----END CERTIFICATE-----`,\n        jwk: {\n            \"kty\": \"EC\",\n            \"crv\": \"P-256\",\n            \"x\": \"DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk\",\n            \"y\": \"CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk\"\n        }\n    }\n};\n\nconst PEM_PRIV_DSA1024 = `-----BEGIN DSA PRIVATE KEY-----\nMIIBuwIBAAKBgQCkFEttBrPHEJRgcvaT8HbZs9h1pVQLHhn2F452izusRox1czMM\nIC8Z7YQiM1pt6bgEmf0h8ldx6UFT0YL9JWSbyBy1U5pHKfnz/xjeg7ZMReL4F0/T\nGwmu4ercqfM//TmEg9nL3nDxb4WmF2al/SmHN3qlzYmYaIDEFfEuu8vWbwIVAMOq\n7pqQiMGUu6uJY/nQTWW0c3IfAoGARWryStp2AElj538qN9tWRuyobRA93Q1ujrdM\nEqsqVpMZd1a8qtRyMaZVVdB7N3EweNUuFOoSAp10s/SQEH9qhVo6NwvzhB7lEtm4\n5FjWW9+9WCuuFOGZpTy8PSFAvQcfUqunP/DeaDliNmgKci+n0nfIBakuQn10Zmqk\nvGu8NZICgYBUsoQeXSJ19e6XZenk6G8wVI3yXFqnRAwb6s7sAVoPwfDCsOXTxC7W\nMlfz0HcYMiifFKEd28NnuAZ2e0ngyPHsb9s5phzTgRfO3GFzOjsjwgx3DmQI2Ck2\nyOWHSAtaNhH4DoBZEyNsb1akiB50vx9b09EHN4weqbgAu743NMDHRQIVAIG5uiiO\nOnWUYieHAiVIPkBCrYUd\n-----END DSA PRIVATE KEY-----`;\n\n// https://datatracker.ietf.org/doc/html/rfc8037#appendix-A.2\nconst JWK_PUB_ED25591 = {\n    \"kty\": \"OKP\",\n    \"crv\": \"Ed25519\",\n    \"x\": \"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"\n};\n\nTestRegister.addTests([\n    {\n        name: \"PEM to JWK: Missing footer\",\n        input: RSA_512.private.pem1.substring(0, RSA_512.private.pem1.length / 2),\n        expectedOutput: \"PEM footer '-----END RSA PRIVATE KEY-----' not found\",\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: DSA not supported\",\n        input: PEM_PRIV_DSA1024,\n        expectedOutput: \"DSA keys are not supported for JWK\",\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n\n    // test RSA key convertion\n    {\n        name: \"PEM to JWK: RSA Private Key PKCS1\",\n        input: RSA_512.private.pem1,\n        expectedOutput: JSON.stringify(RSA_512.private.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: RSA Private Key PKCS8\",\n        input: RSA_512.private.pem8,\n        expectedOutput: JSON.stringify(RSA_512.private.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: RSA Public Key PKCS1\",\n        input: RSA_512.public.pem1,\n        expectedOutput: \"Unsupported RSA public key format. Only PKCS#8 is supported.\",\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: RSA Public Key PKCS8\",\n        input: RSA_512.public.pem8,\n        expectedOutput: JSON.stringify(RSA_512.public.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: Certificate with RSA Public Key\",\n        input: RSA_512.public.cert,\n        expectedOutput: JSON.stringify(RSA_512.public.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n\n    // test EC key conversion\n    {\n        name: \"PEM to JWK: EC Private Key PKCS1\",\n        input: EC_P256.private.pem1,\n        expectedOutput: JSON.stringify(EC_P256.private.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: EC Private Key PKCS8\",\n        input: EC_P256.private.pem8,\n        expectedOutput: JSON.stringify(EC_P256.private.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: EC Public Key\",\n        input: EC_P256.public.pem8,\n        expectedOutput: JSON.stringify(EC_P256.public.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"PEM to JWK: Certificate with EC Public Key\",\n        input: EC_P256.public.cert,\n        expectedOutput: JSON.stringify(EC_P256.public.jwk),\n        recipeConfig: [\n            {\n                op: \"PEM to JWK\",\n                args: [],\n            }\n        ],\n    },\n\n\n    {\n        name: \"JWK to PEM: not a JWK\",\n        input: \"\\\"foobar\\\"\",\n        expectedOutput: \"Input is not a JSON Web Key\",\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"JWK to PEM: unsupported key type\",\n        input: JSON.stringify(JWK_PUB_ED25591),\n        expectedOutput: \"Unsupported JWK key type 'OKP'\",\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    },\n\n    // test RSA key conversion\n    {\n        name: \"JWK to PEM: RSA Private Key\",\n        input: JSON.stringify(RSA_512.private.jwk),\n        expectedOutput: RSA_512.private.pem8.replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\")+\"\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"JWK to PEM: RSA Public Key\",\n        input: JSON.stringify(RSA_512.public.jwk),\n        expectedOutput: RSA_512.public.pem8.replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\")+\"\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    },\n\n    // test EC key conversion\n    {\n        name: \"JWK to PEM: EC Private Key\",\n        input: JSON.stringify(EC_P256.private.jwk),\n        expectedOutput: EC_P256.private.pem8.replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\")+\"\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"JWK to PEM: EC Public Key\",\n        input: JSON.stringify(EC_P256.public.jwk),\n        expectedOutput: EC_P256.public.pem8.replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\")+\"\\r\\n\",\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    },\n\n    {\n        name: \"JWK to PEM: Array of keys\",\n        input: JSON.stringify([RSA_512.public.jwk, EC_P256.public.jwk]),\n        expectedOutput: (RSA_512.public.pem8 + \"\\n\" + EC_P256.public.pem8 + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"JWK to PEM: JSON Web Key Set\",\n        input: JSON.stringify({\"keys\": [RSA_512.public.jwk, EC_P256.public.jwk]}),\n        expectedOutput: (RSA_512.public.pem8 + \"\\n\" + EC_P256.public.pem8 + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"JWK to PEM\",\n                args: [],\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/JWTDecode.mjs",
    "content": "/**\n * JWT Decode tests\n *\n * @author gchq77703 []\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst outputObject = JSON.stringify({\n    String: \"SomeString\",\n    Number: 42,\n    iat: 1\n}, null, 4);\n\nTestRegister.addTests([\n    {\n        name: \"JWT Decode: HS\",\n        input: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk\",\n        expectedOutput: outputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Decode\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"JWT Decode: RS\",\n        input: \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y\",\n        expectedOutput: outputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Decode\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"JWT Decode: ES\",\n        input: \"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA\",\n        expectedOutput: outputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Decode\",\n                args: [],\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/JWTSign.mjs",
    "content": "/**\n * JWT Sign tests\n *\n * @author gchq77703 []\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst inputObject = JSON.stringify({\n    String: \"SomeString\",\n    Number: 42,\n    iat: 1\n}, null, 4);\n\nconst hsKey = \"secret_cat\";\nconst rsKey1024 = `-----BEGIN RSA PRIVATE KEY-----\nMIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw\n33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW\n+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB\nAoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS\n3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp\nuGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE\n2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0\nGAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K\nSu5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY\n6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5\nfSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523\nY0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP\nFaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==\n-----END RSA PRIVATE KEY-----`;\nconst rsKey2048 = `-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAk0VOoksAblwP82DALTG6xGC86Hfho3nChbcPGWyqn+ScfHBF\ncg3SeKyy6aWCyLcKfNwE5cPYzuYvVBsZyIrdfFOuV90D/aRYbuw6UkKR3cmmy9qE\nqvu05dogvc0BcmkwbC37Q8JnsZBRcosoLGgTFxcK+LXdsG7DukajpsGesxQjOLb2\n1jnx+ypzx74xvj7grqlXkxeDKr22q7QkO3A1ApoOuJRAU+SjEEZmqdXzRery2RWx\nhkWbCXuQw4PnW5Lh3Wwabnu7XKVIa6wJa1pqL2IAxmlZ0bvGTfjtO5ggNfgJk5V4\nbGSOXnsplpG71AWMrK2q6NqHjFIE1szEycUKrwIDAQABAoIBAAivyt6Zy/G2g8kC\n852hfvcRubLV92eRdAmNGFqTOqaUcS00i3QZyp4MRGqxtOV/88y/nEOtP1RHkZJw\nHXTjHq4JsDvwhnQR8JbCX6z1zkLQdS01u3jrwJTaPpooxdATfPlfO6CYjqM+SapB\no7dS1ZAZb4U8vPx+MWoDEVNxvO7/xyqho1Oc4H9MwqQUiyG2WfIoqxLSrBYcambv\nRmySwTIpgQZTr61EeWf/0eWpV0iEYbSnkB/VaKW+5tg4gCjPgy5v6/LQ0u/pzlYz\nayCL3xN2rp0tigXsiiWz3cM5gDsnatK4nVNRs9y3JSZpWpI236ZfZjs8Lts+WBUw\nhAEoE9kCgYEAyEIGD1A7R/t5EYk5HhHDH5tGdyxejAcQL5AIz0YnTZU8Iixyc7FR\nuDmAMiuKIcJY/nUlxZjSxNc3MkOfZNggQvf9ONrt+ftQ1yyTjv+019NfU4w4d0Ep\nLNaiAHgaPKimBUZjYXbLgiMXj/1pBaQmgUYTK/VlO3PVdowxxzxMYlMCgYEAvEOG\nGrhVaQV1nAYx86BgZ3wn90hBFXZWGaN+eXUmyrast93Ih3TCSgQDKPuN3pdv/TIe\ncpQv/BxEMpW+6d5Z1NP3GbrLpaZUiUNk8fqw1S3pmD5aWZrYIUaNukAyOxnZVgjv\nEWD9QTpI663gODaeZZTkDYiRNzTzGOg5HtzporUCgYBBOphEtqqImNXnq13qeHip\nO+eo+8/UJpzUEUN9WGmG8NxEeVvSaWin7DrgnKQCuQ5J3Biwk0XcDgoRmks6Ctf/\nWE2oDk/DxGOhowhxZMMgJd6AFUVzOstRqpvcMULCjWB+iV3nqk1Bl3KeWTmzN7O/\nGfc2s1kFE4btdV7lebObtwKBgE3rkLS8eLVYCh6Cvef9CAms7Im/wRhV+zrvXWh9\n4YljZEdRpy7RV5z03i33N/faLALa3JlF1jp9pIhfTD5Vxk59ULe4hZNRLYoGd+Bj\nhw8kyps1q4WMvkm/fueIrIGjqD2gwvopb4iwy/+n3rbFfHfE0UL8tEXqR3eWnhW1\nD4pFAoGAccR4eMJD43hJWaUQLtsj0RoW9lFKVXj7aqkIIeupXwt7Ic2z/FhCAJi+\nV0MWpd3K6+kPl+ifdt8U4kcYfubPMfJhd7IkMcgQS+yZK1+5xWdRISvI8GpNwIHE\nLUkVkCCadXNNZ7b1nmUKjse95u4IaE6hwAqjSTNb05gPmCfoEjg=\n-----END RSA PRIVATE KEY-----`;\nconst esKeyP256 = `-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2\nOF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r\n1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n-----END PRIVATE KEY-----`;\nconst esKeyP384 = `-----BEGIN PRIVATE KEY-----\nMIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDpgCvB2frnLKd7TuWe\nJM1ejXXmr9y/5gskxKuuylLvpQTiDdtLtuhJnvw1/zWKWO6hZANiAAQ5Crhsi5FD\nt55i53dCtdzG9OzCnbDFf/6136ZfEiakDTDeWCdUvNnB3WQEcVBr97BfSWLI9mO+\nT5yzm0RfhgvWIq/tBou+sIDeGp6NQfJwhDhf+JsdeF174gtfNMZGj/s=\n-----END PRIVATE KEY-----`;\nconst esKeyP521 = `-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIA0dBErrZ5ovKq4Xf/\niTlRkYxuOfgBZ6+tWIfG13YwthB1XrH06YmteZGNjHHLZEeycwUt0jM4kUb+tOsJ\n3ckhj1ihgYkDgYYABACYgsa8JWKH46CQagwNw14v/L+DIs1WAjJdMXZySjKlRkD9\nLtLMxkbX2H4H4Zl2KzCMJkwTSETzSKNlXvAUJqKbRwHezCp4y5XZN9MOBYdmyylZ\nNOVxwwTouimNkJ0K6A8+/Im5S3PWB8Ra1D6t+bT1WHHhEePZcltSLLFlbIIyot5m\n2w==\n-----END PRIVATE KEY-----`;\n\nTestRegister.addTests([\n    {\n        name: \"JWT Sign: HS256\",\n        input: inputObject,\n        expectedOutput: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk\",\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [hsKey, \"HS256\", \"{}\"],\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: HS256 with custom header\",\n        input: inputObject,\n        expectedOutput: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1c3RvbS5rZXkifQ.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.kXln8btJburfRlND8IDZAQ8NZGFFZhvHyooHa6N9za8\",\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [hsKey, \"HS256\", `{\"kid\":\"custom.key\"}`],\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: HS384\",\n        input: inputObject,\n        expectedOutput: \"eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ._bPK-Y3mIACConbJqkGFMQ_L3vbxgKXy9gSxtL9hA5XTganozTSXxD0vX0N1yT5s\",\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [hsKey, \"HS384\", \"{}\"],\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: HS512\",\n        input: inputObject,\n        expectedOutput: \"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.vZIJU4XYMFt3FLE1V_RZOxEetmV4RvxtPZQGzJthK_d47pjwlEb6pQE23YxHFmOj8H5RLEdqqLPw4jNsOyHRzA\",\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [hsKey, \"HS512\", \"{}\"],\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: ES256\",\n        input: inputObject,\n        expectedOutput: inputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [esKeyP256, \"ES256\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: ES384 - P256 key\",\n        input: inputObject,\n        expectedOutput: `Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.\n\nError: \"alg\" parameter \"ES384\" requires curve \"secp384r1\".`,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [esKeyP256, \"ES384\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: ES384\",\n        input: inputObject,\n        expectedOutput: inputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [esKeyP384, \"ES384\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: ES512\",\n        input: inputObject,\n        expectedOutput: inputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [esKeyP521, \"ES512\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: RS256, weak key\",\n        input: inputObject,\n        expectedOutput: `Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.\n\nError: secretOrPrivateKey has a minimum key size of 2048 bits for RS256`,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [rsKey1024, \"RS256\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: RS256\",\n        input: inputObject,\n        expectedOutput: inputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [rsKey2048, \"RS256\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: RS384\",\n        input: inputObject,\n        expectedOutput: inputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [rsKey2048, \"RS384\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    },\n    {\n        name: \"JWT Sign: RS512\",\n        input: inputObject,\n        expectedOutput: inputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Sign\",\n                args: [rsKey2048, \"RS512\", \"{}\"],\n            },\n            {\n                op: \"JWT Decode\",\n                args: []\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/JWTVerify.mjs",
    "content": "/**\n * JWT Verify tests\n *\n * @author gchq77703 []\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst outputObject = JSON.stringify({\n    String: \"SomeString\",\n    Number: 42,\n    iat: 1\n}, null, 4);\n\nconst hsKey = \"secret_cat\";\n/* Retaining private key as a comment\nconst rsPriv = `-----BEGIN RSA PRIVATE KEY-----\nMIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw\n33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW\n+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB\nAoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS\n3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp\nuGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE\n2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0\nGAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K\nSu5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY\n6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5\nfSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523\nY0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP\nFaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==\n-----END RSA PRIVATE KEY-----`;\n*/\nconst rsPub = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\nUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\nHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\no2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----`;\n/* Retaining private key as a comment\nconst esPriv = `-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2\nOF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r\n1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n-----END PRIVATE KEY-----`;\n*/\nconst esPub = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----`;\n\nTestRegister.addTests([\n    {\n        name: \"JWT Verify: HS\",\n        input: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk\",\n        expectedOutput: outputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Verify\",\n                args: [hsKey],\n            }\n        ],\n    },\n    {\n        name: \"JWT Verify: RS\",\n        input: \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y\",\n        expectedOutput: outputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Verify\",\n                args: [rsPub],\n            }\n        ],\n    },\n    {\n        name: \"JWT Verify: ES\",\n        input: \"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA\",\n        expectedOutput: outputObject,\n        recipeConfig: [\n            {\n                op: \"JWT Verify\",\n                args: [esPub],\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Jsonata.mjs",
    "content": "/**\n * Jsonata Query tests.\n *\n * @author Jon King [jon@ajarsoftware.com]\n *\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst INPUT_JSON_OBJECT_WITH_ARRAYS = `{\n  \"FirstName\": \"Fred\",\n  \"Surname\": \"Smith\",\n  \"Age\": 28,\n  \"Address\": {\n    \"Street\": \"Hursley Park\",\n    \"City\": \"Winchester\",\n    \"Postcode\": \"SO21 2JN\"\n  },\n  \"Phone\": [\n    {\n      \"type\": \"home\",\n      \"number\": \"0203 544 1234\"\n    },\n    {\n      \"type\": \"office\",\n      \"number\": \"01962 001234\"\n    },\n    {\n      \"type\": \"office\",\n      \"number\": \"01962 001235\"\n    },\n    {\n      \"type\": \"mobile\",\n      \"number\": \"077 7700 1234\"\n    }\n  ],\n  \"Email\": [\n    {\n      \"type\": \"work\",\n      \"address\": [\"fred.smith@my-work.com\", \"fsmith@my-work.com\"]\n    },\n    {\n      \"type\": \"home\",\n      \"address\": [\"freddy@my-social.com\", \"frederic.smith@very-serious.com\"]\n    }\n  ],\n  \"Other\": {\n    \"Over 18 ?\": true,\n    \"Misc\": null,\n    \"Alternative.Address\": {\n      \"Street\": \"Brick Lane\",\n      \"City\": \"London\",\n      \"Postcode\": \"E1 6RF\"\n    }\n  }\n}`;\n\nconst INPUT_ARRAY_OF_OBJECTS = `[\n  { \"ref\": [ 1,2 ] },\n  { \"ref\": [ 3,4 ] }\n]`;\n\nconst INPUT_NUMBER_ARRAY = `{\n  \"Numbers\": [1, 2.4, 3.5, 10, 20.9, 30]\n}`;\n\nTestRegister.addTests([\n    {\n        name: \"Jsonata: Returns a JSON string (double quoted)\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"Smith\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Surname\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Returns a JSON number\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: \"28\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Age\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Field references are separated by '.'\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"Winchester\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Address.City\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Matched the path and returns the null value\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: \"null\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Other.Misc\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Path not found. Returns nothing\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Other.DoesntExist\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Field references containing whitespace or reserved tokens can be enclosed in backticks\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Other.`Over 18 ?`\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Returns the first item (an object)\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '{\"type\":\"home\",\"number\":\"0203 544 1234\"}',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[0]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Returns the second item\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '{\"type\":\"office\",\"number\":\"01962 001234\"}',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[1]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Returns the last item\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '{\"type\":\"mobile\",\"number\":\"077 7700 1234\"}',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[-1]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Negative indexed count from the end\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '{\"type\":\"office\",\"number\":\"01962 001235\"}',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[-2]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Doesn't exist - returns nothing\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[8]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Selects the number field in the first item\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"0203 544 1234\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[0].number\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: No index is given to Phone so it selects all of them (the whole array), then it selects all the number fields for each of them\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput:\n            '[\"0203 544 1234\",\"01962 001234\",\"01962 001235\",\"077 7700 1234\"]',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone.number\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Might expect it to just return the first number, but it returns the first number of each of the items selected by Phone\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput:\n            '[\"0203 544 1234\",\"01962 001234\",\"01962 001235\",\"077 7700 1234\"]',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone.number[0]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Applies the index to the array returned by Phone.number.\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"0203 544 1234\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"(Phone.number)[0]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Returns a range of items by creating an array of indexes\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput:\n            '[{\"type\":\"home\",\"number\":\"0203 544 1234\"},{\"type\":\"office\",\"number\":\"01962 001234\"}]',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[[0..1]]\"],\n            },\n        ],\n    },\n    // Predicates\n    {\n        name: \"Jsonata: Select the Phone items that have a type field that equals 'mobile'\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '{\"type\":\"mobile\",\"number\":\"077 7700 1234\"}',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[type='mobile']\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Select the mobile phone number\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"077 7700 1234\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[type='mobile'].number\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Select the office phone numbers - there are two of them\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '[\"01962 001234\",\"01962 001235\"]',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Phone[type='office'].number\"],\n            },\n        ],\n    },\n    // Wildcards\n    {\n        name: \"Jsonata: Select the values of all the fields of 'Address'\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '[\"Hursley Park\",\"Winchester\",\"SO21 2JN\"]',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Address.*\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Select the 'Postcode' value of any child object\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"SO21 2JN\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"*.Postcode\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Select all Postcode values, regardless of how deeply nested they are in the structure\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '[\"SO21 2JN\",\"E1 6RF\"]',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"**.Postcode\"],\n            },\n        ],\n    },\n    // String Expressions\n    {\n        name: \"Jsonata: Concatenate 'FirstName' followed by space followed by 'Surname'\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"Fred Smith\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"FirstName & ' ' & Surname\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Concatenates the 'Street' and 'City' from the 'Address' object with a comma separator\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"Hursley Park, Winchester\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Address.(Street & ', ' & City)\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Casts the operands to strings, if necessary\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: '\"50true\"',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"5&0&true\"],\n            },\n        ],\n    },\n    // Numeric Expressions\n    {\n        name: \"Jsonata: Addition\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"3.4\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] + Numbers[1]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Subtraction\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"-19.9\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] - Numbers[4]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Multiplication\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"30\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] * Numbers[5]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Division\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"0.04784688995215311\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] / Numbers[4]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Modulus\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"3.5\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[2] % Numbers[5]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Equality\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"false\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] = Numbers[5]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Inequality\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] != Numbers[4]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Less than\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] < Numbers[4]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Less than or equal to\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] <= Numbers[4]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Greater than\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"false\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[0] > Numbers[4]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Greater than or equal to\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"false\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"Numbers[2] >= Numbers[4]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: Value is contained in\",\n        input: INPUT_JSON_OBJECT_WITH_ARRAYS,\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: ['\"01962 001234\" in Phone.number'],\n            },\n        ],\n    },\n    // Boolean Expressions\n    {\n        name: \"Jsonata: and\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"(Numbers[2] != 0) and (Numbers[5] != Numbers[1])\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: or\",\n        input: INPUT_NUMBER_ARRAY,\n        expectedOutput: \"true\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"(Numbers[2] != 0) or (Numbers[5] = Numbers[1])\"],\n            },\n        ],\n    },\n    // Array tests\n    {\n        name: \"Jsonata: $ at the start of an expression refers to the entire input document, subscripting it with 0 selects the first item\",\n        input: INPUT_ARRAY_OF_OBJECTS,\n        expectedOutput: '{\"ref\":[1,2]}',\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"$[0]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: .ref here returns the entire internal array\",\n        input: INPUT_ARRAY_OF_OBJECTS,\n        expectedOutput: \"[1,2]\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"$[0].ref\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: returns element on first position of the internal array\",\n        input: INPUT_ARRAY_OF_OBJECTS,\n        expectedOutput: \"1\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"$[0].ref[0]\"],\n            },\n        ],\n    },\n    {\n        name: \"Jsonata: $.field_reference flattens the result into a single array\",\n        input: INPUT_ARRAY_OF_OBJECTS,\n        expectedOutput: \"[1,2,3,4]\",\n        recipeConfig: [\n            {\n                op: \"Jsonata Query\",\n                args: [\"$.ref\"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Jump.mjs",
    "content": "/**\n * Jump tests\n *\n * @author tlwr [toby@toby.codes]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Jump: Empty Label\",\n        input: [\n            \"should be changed\",\n        ].join(\"\\n\"),\n        expectedOutput: [\n            \"c2hvdWxkIGJlIGNoYW5nZWQ=\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                op: \"Jump\",\n                args: [\"\", 10],\n            },\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n        ],\n    },\n    {\n        name: \"Jump: skips 1\",\n        input: [\n            \"shouldnt be changed\",\n        ].join(\"\\n\"),\n        expectedOutput: [\n            \"shouldnt be changed\",\n        ].join(\"\\n\"),\n        recipeConfig: [\n            {\n                op: \"Jump\",\n                args: [\"skipReplace\", 10],\n            },\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"],\n            },\n            {\n                op: \"Label\",\n                args: [\"skipReplace\"]\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/LS47.mjs",
    "content": "/**\n * LS47 tests.\n *\n * @author n1073645 [n1073645@gmail.com]\n *\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"LS47 Encrypt\",\n        input: \"thequickbrownfoxjumped\",\n        expectedOutput: \"(,t74ci78cp/8trx*yesu:alp1wqy\",\n        recipeConfig: [\n            {\n                op: \"LS47 Encrypt\",\n                args: [\"helloworld\", 0, \"test\"],\n            },\n        ],\n    },\n    {\n        name: \"LS47 Decrypt\",\n        input: \"(,t74ci78cp/8trx*yesu:alp1wqy\",\n        expectedOutput: \"thequickbrownfoxjumped---test\",\n        recipeConfig: [\n            {\n                op: \"LS47 Decrypt\",\n                args: [\"helloworld\", 0],\n            },\n        ],\n    },\n    {\n        name: \"LS47 Encrypt\",\n        input: \"thequickbrownfoxjumped\",\n        expectedOutput: \"Letter H is not included in LS47\",\n        recipeConfig: [\n            {\n                op: \"LS47 Encrypt\",\n                args: [\"Helloworld\", 0, \"test\"],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/LZNT1Decompress.mjs",
    "content": "/**\n * LZNT1 Decompress tests.\n *\n * @author 0xThiebaut [thiebaut.dev]\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"LZNT1 Decompress\",\n        input: \"\\x1a\\xb0\\x00compress\\x00edtestda\\x04ta\\x07\\x88alot\",\n        expectedOutput: \"compressedtestdatacompressedalot\",\n        recipeConfig: [\n            {\n                op: \"LZNT1 Decompress\",\n                args: []\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/LZString.mjs",
    "content": "/**\n * LZString tests.\n *\n * @author crespyl [peter@crespyl.net]\n * @copyright Peter Jacobs 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"LZString Compress To Base64\",\n        input: \"hello world\",\n        expectedOutput: \"BYUwNmD2AEDukCcwBMg=\",\n        recipeConfig: [\n            {\n                \"op\": \"LZString Compress\",\n                \"args\": [\"Base64\"]\n            }\n        ],\n    },\n    {\n        name: \"LZString Decompress From Base64\",\n        input: \"BYUwNmD2AEDukCcwBMg=\",\n        expectedOutput: \"hello world\",\n        recipeConfig: [\n            {\n                \"op\": \"LZString Decompress\",\n                \"args\": [\"Base64\"]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/LevenshteinDistance.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"Levenshtein Distance: Wikipedia example 1\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"3\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1, 1, 1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: Wikipedia example 2\",\n        \"input\": \"saturday\\nsunday\",\n        \"expectedOutput\": \"3\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1, 1, 1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: Wikipedia example 1 with substitution cost 2\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"5\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1, 1, 2,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: varied costs 1\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"230\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 10, 100, 1000,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: varied costs 2\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"1020\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1000, 100, 10,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: another delimiter\",\n        \"input\": \"kitten sitting\",\n        \"expectedOutput\": \"3\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \" \", 1, 1, 1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: too few samples\",\n        \"input\": \"kitten\",\n        \"expectedOutput\": \"Incorrect number of samples. Check your input and/or delimiter.\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1, 1, 1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: too many samples\",\n        \"input\": \"kitten\\nsitting\\nkitchen\",\n        \"expectedOutput\": \"Incorrect number of samples. Check your input and/or delimiter.\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1, 1, 1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: negative insertion cost\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"Negative costs are not allowed.\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", -1, 1, 1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: negative deletion cost\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"Negative costs are not allowed.\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1, -1, 1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: negative substitution cost\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"Negative costs are not allowed.\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 1, 1, -1,\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Levenshtein Distance: cost zero\",\n        \"input\": \"kitten\\nsitting\",\n        \"expectedOutput\": \"0\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Levenshtein Distance\",\n                \"args\": [\n                    \"\\\\n\", 0, 0, 0,\n                ],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Lorenz.mjs",
    "content": "/**\n * Lorenz SZ40/42a/42b machine tests.\n *\n * @author VirtualColossus\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        // Simple test first - plain text to ITA2\n        name: \"Lorenz SZ40: no pattern, plain text\",\n        input: \"HELLO WORLD, THIS IS A TEST MESSAGE.\",\n        expectedOutput: \"HELLO9WORLD55N889THIS9IS9A9TEST9MESSAGE55M\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ40\", \"No Pattern\", false, \"Send\", \"Plaintext\", \"Plaintext\", \"5/8/9\", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    },\n    {\n        // KH Pattern\n        name: \"Lorenz SZ40: KH pattern, plain text, all 1s\",\n        input: \"HELLO WORLD, THIS IS A TEST MESSAGE.\",\n        expectedOutput: \"VIC3TS/CUJA/3II9W9JWDI5DAFXT4SOIF3999IZD9T\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ40\", \"KH Pattern\", false, \"Send\", \"Plaintext\", \"Plaintext\", \"5/8/9\", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    },\n    {\n        // KH Pattern, Random Start\n        name: \"Lorenz SZ40: KH pattern, plain text, random start\",\n        input: \"HELLO WORLD, THIS IS A TEST MESSAGE.\",\n        expectedOutput: \"KGZP5ONYCHNNOXS9SN45MIE3SC3DJBZVJUOE5SLVGI\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ40\", \"KH Pattern\", false, \"Send\", \"Plaintext\", \"Plaintext\", \"5/8/9\", 20, 40, 3, 9, 27, 36, 4, 1, 9, 14, 21, 8, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    },\n    {\n        // ZMUG Pattern, Random Start\n        name: \"Lorenz SZ40: ZMUG pattern, plain text, random start\",\n        input: \"HELLO WORLD, THIS IS A TEST MESSAGE.\",\n        expectedOutput: \"IQVPAANDCA3CHDNO3V/CZQ/BTPZIKW8YAAQXQGLDMV\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ40\", \"ZMUG Pattern\", false, \"Send\", \"Plaintext\", \"Plaintext\", \"5/8/9\", 20, 40, 3, 9, 27, 36, 4, 1, 9, 14, 21, 8, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    },\n    {\n        // Bream Pattern, Random Start\n        name: \"Lorenz SZ40: Bream pattern, plain text, random start\",\n        input: \"HELLO WORLD, THIS IS A TEST MESSAGE.\",\n        expectedOutput: \"/89OALRPJEZQGOO84WOEQZ/I9NBRZOQPBTANC8E/GK\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ40\", \"BREAM Pattern\", false, \"Send\", \"Plaintext\", \"Plaintext\", \"5/8/9\", 20, 40, 3, 9, 27, 36, 4, 1, 9, 14, 21, 8, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    },\n    {\n        // KH Pattern, all 1s\n        name: \"Lorenz SZ42a: KH pattern, plain text, all 1s\",\n        input: \"HELLO WORLD, THIS IS A TEST MESSAGE.\",\n        expectedOutput: \"VIC3TS/ZOHUYXWLTUXPV9ZNOTW9IXJPFDLIBB5ZD9K\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ42a\", \"KH Pattern\", false, \"Send\", \"Plaintext\", \"Plaintext\", \"5/8/9\", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    },\n    {\n        // KH Pattern, Full Test on real message\n        name: \"Lorenz SZ42a: Receive, KH pattern, ITA2 output\",\n        input: \"J3KF+LXT/.+YTMLE/RFVC-SE///4GYX3Q.Z3GVWKWDVAPURPYL/.UYAI.EOW3ZBVVAQRTO/PACJ.NLVLZYYNTU.IDCPKTEZOSWCOBNWFJ+UAKE+WU-JMWYWLXRM+M/HV+TVTC-FOGN3QZG4J.VLM/KK+OVC/YIWTSZUDTSY+3LCCHZADQ-3VBXKEOCSO/+ZBFN34-F.+4UVFVLIP4KFGRBFIVFFJX/FKFSHJ.VUJVWXE+LFAICDYX3EZD33U+GSOGXPAXHTJSNUQI+PXS3JRG+-U+YZITF/SM4LIDPSYMVJM/BL/YHDGBG/UI+EM.JEMX.YQNUWTLAUCLUSDMZGXCQ3CPPCCYLTJC4KXB--G4VGQ4J.EYTEVSG33DVLVPDGNOGAJOUWGFY4KGO-4+IRKDPGDHGBQLFSS/YDP/FM-/BANLERZEMT.U3XZA43RGYD+-J4VYRTONRYF/OI4Y+I3LXUFAHGRT.RXCO3HKCQIML.VHVIGHBWBTU3RZFN.J.WGNLSGLYBJT+TPM-RHHMXTNDSVUO3W/4ORZ4UY.LA-XZYVLCZROMPM3RYSLUD-SNQQA+RK/UV4K/GOSSMJRGIZYPO+BEEB+OEWAWKYXW.-NKUQERUM-PA4WFNKU-Q-LNC.D-A3C.FB.RFOGZHUWTEAYB3HNHCEW.N33QEEVUEC3OOR4BRNLH/IF3+DJJ3S3J4XA+Z3SMT/K4L-IDLQWCLMQ3TO3PNYVCGZCXUMSSBH.BWMGGTIYSC/UBX/PE+3IZZ.Z/CCWLSL+4/4+IET/ELBCBDAM4ELKDAGB3O.3J+IEQQJ.U.VSAQSAYZAU+3YOZWPPPV/YOVOE/P/E4UOZYK4PE3A.ETZGFBCZE.4WA3PSDR3XMLJLH+S3UWOA.T-RID-MA.CVZGLNYK4U.HBPSA+3U+BLSGS/FNRXMOMEBBABIPOAGDWA-4T/B.L.HDSAJE.HE-4FZW+SDBAYCNBTEODDALCMIG-QNM+-PEFC+ABNQ-MVT3-GHKAN/ENZLPMJRVRB+4NBCRTFDVNLRTPIGIEZGSPUXMW3WJQTOEV+WA-HJZLX-JQID-X++FFGKCTO.3.F+JIISVTXKC+..YM/SOQUIAS-IAGJJPRYLLT.EJBJAMRP+MS-ZRUVLBBE-UNYQGBBHEB+QCHYYHVN.NHKS-YG3BNZKQJBO-FQ.SZB/JRFILGUZUZVCOVGULEKU4/HRBGYIZVLCM3/ONJ-OBIRSCT+IZCB-TTZMDQWQUCIVTGTTYNEOTTORM-FSKS3WJWL/ZXOCOCYGC.BRIRKXK.FLJUSP/-G.WP.MMVHBYREWQZZAN/BKSEYDBGXAUV+NUKUKIKIGS+VO-4EY/GWI+SGJOJCBYGGMY4+/EMGULCSC-Y+CXLIECYC-+-ZXHSPOTFGFDWIFT-4XXDDLMMKT433WH/BX-OILWDJC/FFE-ZYH3C4GI4T/3KUJQ4YNBQWXWB-RM.Q3GG/4Z-AIGW4GYYEBXRJHXQA..-.G/3W/-LVS+4GS-+FRYIOFYGUK-FEYA4J-ZB-MSPAM/WLLJ3GFMJP/GGF-C+O-KQ.K4PWVL+3O.LX4TUD+Y+QOO3GTJT+.MR-4JSRXD-X4SCIDIVLCDEGSOZOGWXQZOZ.3PPQ4ZYXKL+QETCM/3/--CHG4+W.BNHTB+Z-NZCO+QEB+-/FNJ+NSHTO+CW.CM/VHIM-S.3VAFDJ3MEH.G+NQFDCUSK+MCKDLEC-TFWSYBQSWE4UOQOXY-E.ESE4OLJQOBUQZUSLRWV-AVOYX3CKS3ZFUAQAWESYMXQV/4MOXORAVKOIELRXCSRAEU/KEFDQWQ-BWEXGALS/.JLQ/CEKT-4C+TWDNGST-UQ-ERBP.YZ-ZH/Q3ITMN-O3P/JBEVZUOY4CTNY4PKCB3YIW/+BOKDEZE.VCTROQDTAXI3VKGYQVOKSCXPDDAD4DLTELK.GDDLTRPXORSFTDOGB.-NQJHNM/4/JOTIVGOQF+FC.GDX4DMT.UBRVUIBCHGLDBZSFSICVVAF4TN.BMAP-IQR-LBCQ/TTHH\",\n        expectedOutput: \"XWOLLE9WI9R99AUCH9BLEIBEN955Z88KR99GWFRL5X89FUES959WPYP9QXT9QIPQ9V9AACQEM9959AA899QEE9959AA8999AN99OB5M89SUEDW/ST5V9AA8GEHEIME9KOMMANDOSACHE5AA89959AA89NUR9ZUR9PERSOENLICHENSINTERRICHTUNG5N889WEITERGABE9VERBOTEN5MA9AA8LAGEBERICHT9VOM99GSEPMM98NN99VOM9959EPMRM9QORT9AA889KATEN9NN9KAT9NN9KARTEN9959Q9C9QSVPP9PPP9A9MA98889ROEM959QM89WESTEN9959C899AOK999L9U9C88889ROEMS59QWM89A5M89K5MCMA989PANZERUNTERSTUEZ9NN9PANZSRUNTERSTUETZTESFEINDANGRIFFE99GEGEN9EIGENE9STUETZPU9NKTFRONT9NAHMEN9O4D9ELSTER5N89SOHL5N89RAU9NN9RAU9N9UND9BAD9BRAMBACH95K9WR8889NN9995KWT88SKM9BFO9HOF5LM89NEUE9STUETZPUNKTFRONT9B99DIESER9ORT5M89UEBRIGE9CORPSFRONT9BEIDERSEITIGE9AUFKLAERUNGSTAETIGKEI5M9DIESER9ORT5M89I/BRIGE9KORPSFRONT9BEIDERSEITIGE9AUFKLAERUNGSTAETIGKEI5MA99A8STELWV5M89ROEM959QEM89A5M8K5MMA989ZUNEHMENDER9FEINDDRUCK9IM9RAUXSN9UND99W99BAERNAU95K9T889NN9959KWT889KM9SW9MARIENBAD5LM889FEINDAFGRIF9F99VON99SO99GEGEN9PAULUSBRUNN5M899LAGE9DORT9UNGEKL4ERT5M89SCHWACHE99EIGENEN9SICHERUNGEN9DURCH9FEIND9AUS99SCHAENWAWDE9UND9WOSANT995K9Y89KM9SO99TACHAU5L89NACH9O99ZURUECKGEWOFNN9TURUEIKGEWORFEN5M89FEIND99MIT9INF9UND9PZ5M89IN9PEISSIGKAU9UND9WLAD9NS9DAHENTEN5M89EIGENE9SICHERANGEN9IN9MOLGAU959A89DRI9UND9WLAD9N993AHENTEN5M89EIGENE9SIC4ERUNGENSIGKAU9UND9WLAD9N99DAHENTEN5M89EUGENE9SICHERUNGEN9IN9MOLGAU959A89DRISSGLOBEN959A89WURKAU5M89IM9A9SNN9IM9RAUM99TAUS99PANZERUNTERSTUETZTE99FEINDANGRIFFE5MA99AA899A5X8O5M8K5M99QC9MA9989UEBER9ISAR9MIT9INF9UND9PZ5M89UEBERBESETZTEJ9FEIND9STIESZ99UEBER9S9NN9UEBER99VILSA9BSCHNITT9VOR9UND9NAHXS9AUNKIRCHEN959A89ALDERSBACH959A89EGGERSDORF995KWP889KM9W9P49SAU5LMA9A88ROEM9959IWM89A5MLK5MCMA989DWP889KM9W9PASSAU5LMASA88ROEM9959IWM89A5MLK5MCMA989DER9ZWISCHEN9PLATTLING9UND9LANDAI9FU9NN3/UF99BREITER9FRONT99UEBER9DIE9ISAR99UEBERB99NNN9UEBER5ESETZTE99FEIND9DRUECKTE9EIGENEN9LINIE5N89TROTZ9HIFTIGEN9WIDERST4F3ES5N89AUN9VISLABSCHNITT9ZUREUCK585M89FEINDPANZER9N9NN9IN9ARTOFJFN9IN9ARNTORF959KQT889KM9SO9LANDAU5B89UND99N9ROTTESDORF599IN9ARNZORZ959KQT889KM9SO9LANDAU5L89UND99N9ROTTESDORF59KI89KM9S9LANDAUGWMA59A89ROEM959QEM8939NN9959A899ROEM9959QK4OLE\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ42a\", \"KH Pattern\", false, \"Receive\", \"Plaintext\", \"ITA2\", \"5/8/9\", 12, 12, 41, 45, 17, 12, 3, 11, 31, 29, 12, 23, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    },\n    {\n        // ZMUG Pattern, Full Test, Receive\n        name: \"Lorenz SZ42b: Receive, ZMUG pattern, KT-Schalte, Plaintext output\",\n        input: \"YQPLQQX4OJGFOKBXROZVPBVIJOXSMQSMTMHX8VSBBMIHKJTYKR4ANTQRPCJXR3YE8KVLDN8SQN4VCJQUW3BB4HWEXRD4LUJNF99DV5FLQ4IRANWRCLYX//J8YSIWO44LCPTCBKX4TZGBPYHCE/FBJ4DSBFF5YLOVPBSRUDQULTTO9BYLLUCRGTM/VXQCS8XUDLY/5O3TPSYFQ89RQRGG/IEOKULB8MHQQBHIWRW38W5XSPT9YF8WJGN3HMEBY8XKQW/3BC888Q3JC/K8SOXJWONNQCM8Z4UQQQGE8RL3GXZ3YI4O/RBCF5JXJE5W5GJAT9/9XP9V8SFZLKUBUPD4HXABGLB8D3E4YTERP3GGISVNYHOL3BQVJUOFEBZMYHBKFGPWEOGGMFGF/EVGWT584T8BMURIBSIBELCANKT/VXHUHONHFPJ/OBCQGCQPFQSQW3GEFCQKY9Z4/KVIQJPJQ4ZQJZP/PEIHQG4H99B58HD5/XCLRBPXMR8P84CMHO5RYODKY94NINO4OGY8TOF9BD9EPD5U3P9KMJ/WBXX9CO9SHMK5KHQFA9BE35R4OSAPCAXZ9SALUKVPVVYB8MGGK3XSVLKGUT/8JVIF5DSDUQT3ZKZTICJTSGXVZAZ4WWGAMOFWUN94OZR4Y8AVLGZHOV/UHCURM5RQPKDCEMRBWLKYLTTGGE8ZIVP4HJMVW5HWKOHRZ4OIUL5OZJOVHL9AQUZA93F3H9QNS5VZYZXUM8AD3RZC9CQ5B//4J8/B35XVB9MHMYQSVQPFWSXWEDU5NHXBSO/W/NBW3V/KO5VHIZQRZOKIJSV4G9Y/H3FIYLBH5/XA3AYRPUREKAQL5WTXJ4EK3EAYQ8GIDXTBXMUFUGKZHZZZPYAQMQOXZAGKP4SPIEIKVUPVPRVRSXSDRJSS3XM5KOTUVK3O435VB93H4HB8IEXHDGCJ3VHPG48VZKB4GHRNFHDQ8LN4P9TI3VD9//LACSHQJZWK5/WLC5/PHN5JTVWAOXWX59/SIZ4ADMXO5GCMYSPNLR33CFLKGSN/ZYCFAR5I4/ZQRWCVGY9GCAA5GJCW55HXVGAKGZG/V9Q9AGB9WAVKJX9DM/FOCHWZXAVEPMKTSQJYPWCYFTH3WXZRVAFCLHFMFQUGUWDHQQSUF/QSPSY/8PWPU9B8WUXFE5IPYCSYX3FKEGXIY8DCNLOWZTU8N98ZNWL8QDP/JHTU5YNHN8OI8TMBBIUQEOVT4S/N9V//LZCQ5LN3X43LUG/KDVSOXUTKOP5VTI/OUV98ULIJW9EBVUMBYBLOIKJX/XUHHYPB/N3PEVBFJGXGO3H8VTIO9D8IV5FAL5DZDOC88WNITD/CEZOB3IS8W//ZIBFQOMG3HNUXV3YMM5QH8YYPV/EJMUCKSNRTGFHBZJFTX59CMRM/D5GVFWHF8QWPY44IKNLH8A9SMHYUNXGDJBX9PA4CJPRO4RZSPTG4SYBLHMSV/9UJPUJEPHPZWSDKMMMN/WEHOZDXBPWS3DHG9SBWFTB85HOPZYTVZ9KSBGZ8PAIGGIVXU/8YIFQGQDDDVDDV3TL8L5G9C/98CYDUGWWCGQCQLS4Y8BGSSMYLOODGA/YMRWI9LJI8TXT3ZYZ8NR/3RNDYHR9DINKB//HWPJETJ8/RFXSV/SKWEBKOMSJ9W393JH8E/G3UX/5CD35H4BZOU8GYELU4EUYNYP/UUBYQZKLIPFPOPZ8WNJ4WR5QWIZVMQ9BCVZM3MB8BES5APTTVTBSILLUQHY9X/A9FFQWR5DUMNHPUKVZDYB38ZSI3CNCZPG9C/UKYRX5YWXEIBIMCBNRPGGCZKHLNG4T/R5K4LSBZUVNGBCHPI/JUIZ/IMFXEE4KYSEMYBPWBZ/UCANCY4MPXFQB3NRZQOSLBOMD9TP3IY8RQWIEG9HG3CH8AI/GGPGLXNNV4Q/ZOMVCVS4UCQFFEMMWK/RSV8RXQQPSOT8GYAUXOUJQ9VQCHAS4FL3WAMQXARHYNEPYS5TR3/MHCUSPE33NWZ48X3J9HWEJGIKOTLKAJXXAQ5X3YVLGNSSM3XT/9E/PBTN5PHCJLWKICIHER/QQMVGAGCVTKBUL3B3GQUKLKZ38YMOOJ5DTAFZSFZLVO4UZXGNNR4SJ3JFMLKDP3MXSZXX3YVLGXF3O8H5JHBZFTOZ4JDTLQGHLD5XOBLVQI/MQXS4EJAIU/VLYIGGJWNOTTF9AEQSEEGT84GI355YLVTTDGVICR8ACXJFHPTHRG8UXCLIHMW4Z3CUV89YLL3W3HDC5HUUDJ/PUS54PIJ98IXL3SEEOWWPGFN9O9ICPG4K8558ONZOHSW/U4ZRSC4C3UOHHIEARXSCZDZM4IBDBLRI99DEKKIAYPJSOSC59XGUVKSPHV9FRE35A5SII3G3DRQDQKTSC9DNUPBXVD3ORPX9NM3BK3PB5PIWUZKNCL4I4IWVO4FE4GCZBHSZWN3GS88G884DUFDUFE9RJEEKM3AJHNSEANJWQMRP5DUXHEKDWAAQGZYUL3EXY4ALN4NR3SF5CFOTZ9FGNJDFQEZU4TTVORUVS5WIZBQLZQF5HD4TTYIRPAZDNPCIYS93ZY5QQEBTYIYCQ4AJGK83BFOIK9S5GAJCBYLKSDADHOS4/XVSHDYBFSO3FZIHHHWSJ5L4R33KCXOW8T9KFQ4C8UCOCOCKXU8JJ5R9JL88UAJQ4HXWDBQ45JGY/AYGP8PMGWWLFM/9SP5OHR3FQKDIYIAOLKWM8OT8JO4W9ET5LIZMMNJBNN/A999JNG3ALKLKEK/HWZJUC5PLAILQJ8MCOO8T3X98H4ZKFIKYUKDI4ODART/K4JZDHMBY4X/BDKOYRJ/CQKZCFFBY3494/DUFHLVYZL5VG35KFSVC9IXI4S3RB3FXBDCYYKVUUTBTEFNJVRB3H5BU9NGUGJ3FBD9OOXWRTUZ8GHAHKPNL5TTUHNGMARBKFQMKFN4ODB4Y8OKCJPFQNHIGK8WFYOHI/ZW/PMHF4JAFIAYNNPOYJY/AFTDSOPNGP8GK94X3QY4A/THAWWGH3LSRST4/AV8GY4QV53A5MRYF3K3MWK5YJAWLLYWV9U/CZN4WAV3OELTO45G5IEY4P845ZJJAYGTBHXBFE5XJ5C5FFUFXCOUSWTU/LSSQ/VWNP9BSOLUB5PLNWAZF4O8LKGTA9ZAAVRIW8CP3UFS5I5N/MNMVCRD/3AM/XUPGMJAM45AY4VDA5XY4F8ZV4NXZMPZROZ/QFPLOQ/3WSZ8/FXEJEFC9W3UJGOO4RL9FEVQL/DYDAK8JLMVPY9IPYXBX3WIYOLYB5VDUAJBKCINPZZ5843H/8ZIZCVQ4RTVCOTSATJC4ZVBXYROCDXRIPGH5/WS83Y4TAA45YBPNGFFSY3I/O3JIKWMAS9TYTY38JMIEREZWOYWT3ZYRA84IRDX93H3JJQSFL43JOLPPX3UDF59RL3ESVR8J83QKTJLSJAEQOBZ8FCLWIRBXOUXQOKQ/AE885X/VQIUFETMEUC48CJMKQIJQ9ZVPHOJMCIR93GTVYWMIYMPOWFWOL99NRTWQAQ3WKLF//F3VDQDS8HLLDRKESD89OU9PSPIXW5/TX35KZUONYTLQKMGRWTSV3/5/BJZV9MHPL5UZR4ZMJZEXUE8F8E3RVDEDGVQWENRFYBOEKQMBILPWUFXZQB9XGYOSZKS8IRL9VMH5DWE3WHY3FRR83I55XUXHBJ5BMAPOHIGHQIZ/RSSAK83AUEW4Q5DZ9WTDKRMLT9OYH/VZGXUMEOV/4PL3P/SSIHX3UQIVDVQKZWHYQGZO9NLMPRSJV8/G5FTKLJ8FSKRAOZ/FZCRJLBRIBTJPGUDLFGYBXBVMJQQA3MSHFDZE/HRUOUGYZJNNWARNXNYKAWYQQWMES8K4UCFEGHDPYD/NK/U/B84NI54QLJWW/8NTWPOS/ENS355GHATHAFKK4ONFQR/WHNY3IQTTCBWF35XYXLSOXV5AIZBIGDNFDHL5OO4H/3SJBJBPTQ/3WAAO9ZGW9GYP8/JKVZKK/O9VEMBDKIJYMMVCGODXZY3QNUB8EOWKXS3HT9OFV5JJCDC9FE3FUZJOHL4HH4PA9YFA5TI5ZHFPG/RXQ/5ZMGNSTHWZC4ZC9JA8JNHKT/HNE8CLU9QF385/HDJAEEGSKGPFRKDHEPAXDH9M8Q8KQKEGYWDI9ERQGWMDPZYS/WS9PRP3/3MUKLQIKR5V4GYHNQWTB/SZM9VPQSJZLLKIF5WC5EHTILCZ95RMOJZ89LPJMPRNZ4ERL8HXZIL5B9KEBFN4YJ4LNEQOSS5ILTYECXG/MY9MRQWHFM8QFLI8CAG3BUUG53XPH4CE8GUQKZBKLNAGWXORPFZR3V9XTLBBATUTFUPC/ULGGWLFMCSP8KUYIFXTZ8V4HUQLS9MKYMJMLF/94U3PNELHVWRMISI9QYL53O5XDSFP84VOOAYCKJE4HPG3GA8MGGZVMSBMT5KWQ9V88USEMD3T8IYMIYFENMRZURQLNCICODEG/ZW4HNTOKZMC//ZGQL/XBC8STG3RXKMCO9NSVQQ8QDFDZWNIAENP/J898ADWNHCDCIO5XT8PR4BCDDAQEJV4LGLRALWU5YUL5GBDH//4TQJHBP8JWWMVBV89E/VMRUCLXGKABKSXK5KXWF9NIZB/ADKX38LTZIUBBMWIMMNZXO/EKXEGZBQZD8CYXXRVVF3/NIDWVFYU99H5KJ9DE55N59LRTPLL9PAKPPLSRC38SEUGYSUG5GRTV/5WIDY9YBNDIS4QEBELA/4QVPMVWZG3R9HSE/ZXYK8GGHQ5WVDSYI4RZTBYMUL8QLMPB4P43Y9R3PAWWZRFNNIEXPL83DVJWL3YDG/OJ9UN5CS/L3LDTGMVHMFKZE/BD4MVKJTWFXWPR4KJXYUGIZBZZMTH/JM3MIWOV/ETI5NNOUSLUYL89G3X/TPHC3A/CTNT5HM4CX5PQDMKRSLAOFGPNVRFSE/HDZFE8LFIYM5GDMAYBVRCJCHJBDJ9YS9SLJ/LA54C5DPH8LX8Y3VLP4GROV/QIPZQDB5CS4EAKFULGLIB9XSOXKDE4OJVQXYEI8RBV9PLOT8QE5WLSXG8JW8C/5FGKUR9NHIKNLYFJNSIE8WPARBOUAMNDOKRZXNIWKF8EDD/4VIFV/4UIEVIG48/M4OLBDZKOXRO4//8XAOXY8FPVJZ/O4OWL4CIVNGMWACYCYUJJLPDS/D/KYLBBFCVRTQ/DJZ3WVQEZDVYIBOKDHCYGDP4FENMNOCYJVG/RJJVKIU4EZNQRAN9WWSGYSHZWQQWXKD5RLQDSMDW9R34CYUPW/5899KP3BJM/ZGIWMAPGYF/PYLHDXDXWUPMTNAFPZMQOQVBXRI3VL84TYQRVRVPIJTTUJ8EKOQWMRTR3QGYOBSQWWKO/V4I8S/E8XW9A3HYYVSIVFS8LTWURNZ8M5EGBCLFGC3LJLVZE8MEQNTXY3XL8YTQPA4KZCMO8OJ5RDCIELIKSANPLQAVEPE5IWXMA534XDAV9CZTCBEHVVCFAGIXS4F4KVB4K/AM/DCHYW4BTY4A954GJPQ3NAK8STGW4E/FE4L/WZZJGJPLH/MSDCMK/VQ5HLLONBWSKW3LPSPIB3BSZS8ZZVRAQPO5G5MAWGVFKJSYDBXB5KGUWQZ4FBPUQS9LBKWTQCS3RC8MVTQTTS9STDAXAQJHHZSGN/RXLODYJY8EUTA8H3IFIW5FI5UFTOPTOET8F5/MUQTONBCD3OTMS3QSV/UKAIINVWPQFNHYREGBG/XWEQN/HGMG9TRHLIIQIM5WZQDX5QEBCFRJVOO8/KS9NUJRLL8LJ/5/LH4RIJUIY4/MZT4VKPL5ITODTSAULBRRG8H945J84ABZA3JT9MFA/Z3OLMHMGLO8M8DVMAEHQJMEY3JGORHACL4BGQWCE99NIGNAOSMEI9FHIY5BVB3DR4FICF8VSI3Z/HWDAXUW8RIMJQUABTRXWOEDTIFBRFBE33RM/Y4INUYXHI9393YDIEIS9DUN/AMLN99F4LML3XOCSSCSWLK9UTIUVH4TWITVCY5XDV/59GCZHTE588MXX5EWURPG35BV8VIGNZKR5BYYRULFSVMACN3VH5P5U4SQ/KWFJPAMVFVRVPBOPB8IFKYKG5D8/E4LYAVOSNXRGIFOTAVPR/CG3YK8BBAOWO3M5V/OMIWMWVI98H5PVPDF8TZNNU4EVSYYY9YFLZPCGIOQM/IZ4HWGJ5RMT98Z/HMYXWS9JL5KNFHWO3VJXC/5IOKRAURSUTLQZGTOVGFTGZQ5HXSYF495/B9VSQBSKVSSSK49TKERKV9/YV9P/OF8MOHMT3DW4DNVN4H/M3RUDL/NCEY8PUNIJBSLL/ENHJ/QJAANL/IHZYGCR3548RIDAEJOVDE9OYJZWL5CB4VXH4ILMMBKSOZNWBCZEB3KB5VPUC/5MY3RVHB5IEJCNUNKDRLBAJRHPVCNDNMBYGJQRRRHNTZPNB3IFXN3DE8OB8WBN8U8BVY/CY5TALOLPKCQEN5NBRRXZEKLQQGYOCAM8DBXYSK8H9V5H8GBK5KR/V43YXMRAK/XCKCE99KGWIMZZCYOMJCMNFIE/ONFBORYY4DPM8CWLWR/V4F8ZMQEV/RBPXAKTEEYPD8QQ5HLCNUVMW/CC//YHPVRV5I4NQF5EMRHW4CWCJHDTPYUU3895ITSPBX8FIYHO3H/UX8Z8O/JRIGGYWLDYKEOLJJXWA8C/B4/DJIBRO5/JZD4XBGLVGSYSR8TN/U/WEM/3MMUVHGDFSSXXIYXE8Z9P59MDK5PQUQATLPNK9DKTXVYX4VZIBSKKSSQ4KN/FSYKHA8VE4YYHJMGTQWMBQPRDYK/UNNNLNCKH/UJ5CDTQ8ZWTGUV4FMKPJO/FI9S8N5PPZFVVZXPBYQUAOFBS4C5H4Z/DFTEF5K4P/VEZC3X9WJHPIZMSRZSCNUKGFSUQS3CSHSQL8AN3FZGERFOSY9SA4KURA4NWWNCJRINHI5A/H3TSJ9A/ZNLM5QFQICNRSKIKRQNA3A8WNPNLEBDB3QW9/4B5YYIIWDKG5C3/IOZXIDIFUE/NX8YTCMPGQ9YMN3AXXE85C5Y33ICVD3IXFP3AJ9UJYD/G54\",\n        expectedOutput: \"IF I SAID BLETCHLEY PARK, ENIGMA AND ALAN TURING, I WOULD THINK MANY OF YOU WOULD KNOW A LITTLE ABOUT THEM, THANKS TO THE WORK OF BOTH BLETCHLEY PARK AND RECENT FILMS SUCH AS THE IMITATION GAME. NOW, WHAT ABOUT IF I SAID COLOSSUS, TOMMY FLOWERS AND BILL TUTTE? I SUSPECT THAT VERY FEW PEOPLE, EVEN IN THE LOCAL AREA AROUND BLETCHLEY PARK AND MILTON KEYNES, WOULD BE FAMILIAR WITH THESE NAMES.  COLOSSUS IS, ARGUABLY, THE WORLDS FIRST ELECTRONIC COMPUTER EVER BUILT AND ITS STORY IS EVEN MORE AMAZING THAN ENIGMA, BUT IT HAS RECEIVED SIGNIFICANTLY LESS PUBLIC RECOGNITION.  MANY PEOPLE WHO HAVE HEARD OF COLOSSUS BELIEVE THAT IT IS SOMETHING TO DO WITH ENIGMA, BUT THIS IS NOT THE CASE IN FACT, IT WAS BUILT TO CRACK A HARDER AND MORE SECRETIVE DEVICE BUILT BY THE LORENZ COMPANY IN GERMANY. ADOLF HITLER, REALISING THE REQUIREMENT FOR FAST AND SECURE COMMUNICATION BETWEEN HIS HIGH COMMAND AND GENERALS IN THE FIELD, ORDERED A FORMIDABLE CIPHER ATTACHMENT WHICH COULD SEND AND RECEIVE ENCODED MESSAGES AT A MUCH HIGHER RATE THAN ENIGMA. THEY BUILT A MACHINE WHICH, RATHER THAN HAVING THREE OR FOUR WHEELS LIKE ENIGMA, HAD TWELVE WHEELS, EACH OF WHICH COULD HAVE ITS SETTINGS ALTERED TO MAKE DECIPHERING THE COMMUNICATIONS EXTREMELY DIFFICULT, IF NOT (THEY HOPED) IMPOSSIBLE.  IN 1940, BLETCHLEY PARK STARTED PICKING UP NEW TRANSMISSIONS, BUT NOBODY KNEW WHAT WAS ENCIPHERING THE CODES. THESE TRANSMISSIONS WERE RECORDED AND MANUALLY TRANSCRIBED ONTO PUNCHED TAPE AND SENT ON TO BLETCHLEY PARK. FINALLY, ON 30TH AUGUST 1941, A BREAKTHROUGH WAS MADE A GERMAN OPERATOR MANUALLY TYPED OUT AND TRANSMITTED A 4000 CHARACTER ENCODED MESSAGE. UNFORTUNATELY FOR HIM, THE RECEIVING END REPLIED SORRY, SEND IT AGAIN. IT WAS STRICTLY FORBIDDEN TO SEND TWO MESSAGES WITH THE SAME SETTINGS, BUT BEING ANNOYED AT HAVING TO TYPE THIS OUT AGAIN, HE DID JUST THAT AND STARTED TYPING AGAIN. TO SAVE TIME THOUGH, HE SHORTENED SOME OF THE WORDS (JUST LIKE WE WOULD TYPE NO RATHER THAN NUMBER) BUT DOING THIS MEANT THAT BLETCHLEY PARK HAD TWO MESSAGES, BOTH SENT WITH THE SAME KEY GENERATED BY THE MACHINE BUT WITH DIFFERENT ORIGINAL TEXTS.  BRIGADIER JOHN TILTMAN, A SENIOR CODE BREAKER, WORKED HARD FOR TEN DAYS AND MANAGED TO SUCCESSFULLY SPLIT THESE MESSAGES BACK INTO GERMAN. MORE IMPORTANTLY, HE ALSO WORKED OUT THE ORIGINAL KEY STRING WHICH WAS GENERATED BY THIS NEW UNKNOWN MACHINE.  VERY LITTLE PROGRESS WAS MADE TO CRACK THIS CODE, UNTIL THE KEY AND MESSAGES ENDED UP ON THE DESK OF A NEW MATHEMATICIAN BY THE NAME OF BILL TUTTE, WHO HAD JUST RECENTLY JOINED BLETCHLEY PARK.  AFTER WEEKS OF PAINSTAKING MANUAL WORK WITH PAPER AND PENCIL, DRAWING OUT GRIDS OF DOTS AND CROSSES, HE FINALLY DISCOVERED A REPEATING PATTERN USING ROWS OF 41. HE CORRECTLY REALISED THIS WAS HOW MANY POSITIONS THE FIRST OF THE ENCODING WHEELS MUST HAVE IN THE UNKNOWN MACHINE. FROM THIS BREAK, WITH THE ASSISTANCE OF OTHER CODEBREAKERS, HE MANAGED TO SUCCESSFULLY RECREATE THE WORKINGS OF THE WHOLE MACHINE. REMEMBER, THIS WAS WITHOUT EVER SEEING THE LORENZ WHICH WAS AN ASTOUNDING PIECE OF WORK.  BILL TUTTE, WHOSE CENTENARY WILL BE CELEBRATED IN MAY THIS YEAR, DID FURTHER WORK ON METHODS WHICH COULD POTENTIALLY BREAK INTO AN ENCODED MESSAGE, BUT IT INVOLVED A HUGE AMOUNT OF MANUAL EFFORT TO COUNT RESULTS FROM ALL SETTINGS UNTIL THE CORRECT ONE WAS FOUND.  MAX NEWMAN MANAGED THE CONSTRUCTION OF A MACHINE TO USE TUTTES CALCULATIONS, WHICH WAS NICKNAMED HEATH ROBINSON, BUT IT WAS, UNFORTUNATELY, RELATIVELY SLOW AND UNRELIABLE. THEY INVITED, TOMMY FLOWERS, FROM THE POST OFFICE RESEARCH STATION IN DOLLIS HILL TO SEE IF HE COULD IMPROVE THE MACHINE. TOMMY FLOWERS CAME BACK TO THEM WITH A PLAN TO BUILD A NEW MACHINE USING 1500 THERMIONIC VALVES. VALVES AT THIS TIME WERE BELIEVED TO BE UNRELIABLE AND REQUIRE REGULAR REPLACEMENT, BUT TOMMY FLOWERS KNEW FROM HIS RESEARCH THAT IF NOT SWITCHED OFF, THEY WORKED PERFECTLY.  WHILE BLETCHLEY PARK INITIALLY REFUSED, FLOWERS RETURNED TO DOLLIS HILL AND PERSUADED HIS SUPERIORS TO CONTINUE TO BUILD THIS MACHINE. IN JANUARY 1944, THE FIRST COLOSSUS WAS DELIVERED TO BLETCHLEY PARK, AND BY FEBRUARY WAS RUNNING SUCCESSFULLY AND RELIABLY FIRST TIME, MUCH TO EVERYONES ASTONISHMENT THEY QUICKLY PLACED ORDERS FOR AS MANY AS POSSIBLE, FLOWERS ALREADY HAVING STARTED WORKING ON A FASTER MARK 2.  A FURTHER NINE COLOSSUS COMPUTERS WERE DELIVERED TO BLETCHLEY PARK BY THE END OF THE WAR (APPROXIMATELY ONE PER MONTH) AND THEY HELPED BREAK AN AMAZING 63 MILLION CHARACTERS, SHORTENING THE CONFLICT AND SAVING MANY LIVES.  AFTER THE WAR, CHURCHILL ORDERED THE DISMANTLING OF ALL BUT TWO OF THE MACHINES AND THEIR EXISTENCE WAS KEPT SECRET FOR THIRTY YEARS.  TONY SALE, AN ELECTRONIC ENGINEER WORKING AS SENIOR CURATOR AT THE SCIENCE MUSEUM, ALONG WITH SEVERAL COLLEAGUES STARTED, IN 1991, THE CAMPAIGN TO SAVE BLETCHLEY PARK FROM PROPERTY DEVELOPERS. HE ALSO BEGAN GATHERING INFORMATION ABOUT COLOSSUS.  BY 1993, HE HAD RECOVERED EIGHT PHOTOGRAPHS FROM 1945, PLUS SOME FRAGMENTS OF CIRCUIT DIAGRAMS. HE STARTED TO BELIEVE THAT IT WOULD BE POSSIBLE TO REBUILD COLOSSUS, ALTHOUGH HE SAID THAT NOBODY BELIEVED THAT THIS WOULD BE POSSIBLE  JUST LIKE TOMMY FLOWERS BEFORE HIM  AFTER MONTHS OF WORK AND WITH HELP FROM THE ORIGINAL DESIGNER OF THE OPTICAL TAPE SYSTEM, DR ARNOLD LYNCH, HE MANAGED TO RE-ENGINEER THE BASIC SYSTEM. HE VISITED DR ALLEN COOMBS, WHO HELPED BUILD THE MK 2 COLOSSUS, ALONG WITH HARRY FENSON, ONE OF THE ORIGINAL COLOSSUS ENGINEERS. DR COOMBS GAVE TONY HIS WARTIME NOTES AND SOME CIRCUIT DIAGRAMS.  USING HIS, AND HIS WIFE MARGARETS OWN FUNDS, HE STARTED THE HUGE TASK REBUILDING THE COLOSSUS. HE PUT TOGETHER A TEAM OF EX-POST OFFICE AND RADIO ENGINEERS TO HELP THE REBUILD.  ON 6TH JUNE 1996, A BASIC WORKING COLOSSUS REBUILD WAS SWITCHED ON, AN OCCASION WHERE DR TOMMY FLOWERS ATTENDED AS WELL AS MANY PEOPLE WHO WORKED AT BLETCHLEY PARK DURING THE WAR.  THE NEWMANRY REPORT WAS DECLASSIFIED IN 2000, ALLOWING THEM TO BUILD A WORKING COLOSSUS MK 2 BY 1ST JUNE 2004, THE 60TH ANNIVERSARY OF THE FIRST RUNNING OF A COLOSSUS MK 2 IN 1944.  THE REBUILD CAN BE SEEN IN THE NATIONAL MUSEUM OF COMPUTING IN BLOCK H LOCATED WITHIN BLETCHLEY PARK. IT STANDS IN THE ORIGINAL ROOM WHERE COLOSSUS NO 9 STOOD IN WORLD WAR II. IT IS A MARVELLOUS WORKING TRIBUTE TO TOMMY FLOWERS AND THE ENGINEERS AT DOLLIS HILL, TO BILL TUTTE, JOHN TILTMAN, MAX NEWMAN, RALPH TESTER AND ALL THE CODE BREAKERS AT BLETCHLEY PARK, ALL THE WRNS WHO OPERATED COLOSSUS AND THE RADIO INTERCEPTORS AT KNOCKHOLT. BY MARTIN GILLOW, VIRTUALCOLOSSUS.CO.UK\",\n        recipeConfig: [\n            {\n                \"op\": \"Lorenz\",\n                \"args\": [\"SZ42b\", \"ZMUG Pattern\", true, \"Receive\", \"Plaintext\", \"Plaintext\", \"5/8/9\", 32, 28, 24, 11, 44, 6, 50, 34, 12, 18, 18, 9, \".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x\", \".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x\", \".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x\", \".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x.\", \"xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x.\", \"x.x.x.x.x.x...x.x.x...x.x.x...x.x....\", \".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx...\", \".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx..\", \"x..xxx...x.xxxx..xx..x..xx.xx..\", \"..xx..x.xxx...xx...xx..xx.xx.\", \"xx..x..xxxx..xx.xxx....x..\", \"xx..xx....xxxx.x..x.x..\"]\n            }\n        ]\n    }\n\n]);\n"
  },
  {
    "path": "tests/operations/tests/LuhnChecksum.mjs",
    "content": "/**\n * From Decimal tests\n *\n * @author n1073645 [n1073645@gmail.com]\n * @author k3ach [k3ach@proton.me]\n * @copyright Crown Copyright 2020\n * @licence Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst testCases = [\n    {\n        radix: 2,\n        input: \"01\",\n        checksum: \"1\",\n        checkdigit: \"1\",\n    }, {\n        radix: 2,\n        input: \"001111\",\n        checksum: \"0\",\n        checkdigit: \"0\",\n    }, {\n        radix: 2,\n        input: \"00011101\",\n        checksum: \"0\",\n        checkdigit: \"0\",\n    }, {\n        radix: 2,\n        input: \"0100101101\",\n        checksum: \"1\",\n        checkdigit: \"1\",\n    }, {\n        radix: 4,\n        input: \"0123\",\n        checksum: \"1\",\n        checkdigit: \"1\",\n    }, {\n        radix: 4,\n        input: \"130100\",\n        checksum: \"2\",\n        checkdigit: \"2\",\n    }, {\n        radix: 4,\n        input: \"32020313\",\n        checksum: \"3\",\n        checkdigit: \"0\",\n    }, {\n        radix: 4,\n        input: \"302233210112\",\n        checksum: \"3\",\n        checkdigit: \"0\",\n    }, {\n        radix: 6,\n        input: \"012345\",\n        checksum: \"4\",\n        checkdigit: \"4\",\n    }, {\n        radix: 6,\n        input: \"134255\",\n        checksum: \"2\",\n        checkdigit: \"4\",\n    }, {\n        radix: 6,\n        input: \"15021453\",\n        checksum: \"5\",\n        checkdigit: \"4\",\n    }, {\n        radix: 6,\n        input: \"211450230513\",\n        checksum: \"3\",\n        checkdigit: \"1\",\n    }, {\n        radix: 8,\n        input: \"01234567\",\n        checksum: \"2\",\n        checkdigit: \"2\",\n    }, {\n        radix: 8,\n        input: \"340624\",\n        checksum: \"0\",\n        checkdigit: \"4\",\n    }, {\n        radix: 8,\n        input: \"07260247\",\n        checksum: \"3\",\n        checkdigit: \"3\",\n    }, {\n        radix: 8,\n        input: \"026742114675\",\n        checksum: \"7\",\n        checkdigit: \"1\",\n    }, {\n        radix: 10,\n        input: \"0123456789\",\n        checksum: \"7\",\n        checkdigit: \"7\",\n    }, {\n        radix: 10,\n        input: \"468543\",\n        checksum: \"7\",\n        checkdigit: \"4\",\n    }, {\n        radix: 10,\n        input: \"59377601\",\n        checksum: \"5\",\n        checkdigit: \"6\",\n    }, {\n        radix: 10,\n        input: \"013909981254\",\n        checksum: \"1\",\n        checkdigit: \"3\",\n    }, {\n        radix: 12,\n        input: \"0123456789ab\",\n        checksum: \"3\",\n        checkdigit: \"3\",\n    }, {\n        radix: 12,\n        input: \"284685\",\n        checksum: \"0\",\n        checkdigit: \"6\",\n    }, {\n        radix: 12,\n        input: \"951a2661\",\n        checksum: \"0\",\n        checkdigit: \"8\",\n    }, {\n        radix: 12,\n        input: \"898202676387\",\n        checksum: \"b\",\n        checkdigit: \"9\",\n    }, {\n        radix: 14,\n        input: \"0123456789abcd\",\n        checksum: \"a\",\n        checkdigit: \"a\",\n    }, {\n        radix: 14,\n        input: \"33db25\",\n        checksum: \"0\",\n        checkdigit: \"d\",\n    }, {\n        radix: 14,\n        input: \"0b4ac128\",\n        checksum: \"b\",\n        checkdigit: \"3\",\n    }, {\n        radix: 14,\n        input: \"3d1c6d16160d\",\n        checksum: \"3\",\n        checkdigit: \"c\",\n    }, {\n        radix: 16,\n        input: \"0123456789abcdef\",\n        checksum: \"4\",\n        checkdigit: \"4\",\n    }, {\n        radix: 16,\n        input: \"e1fe64\",\n        checksum: \"b\",\n        checkdigit: \"6\",\n    }, {\n        radix: 16,\n        input: \"241a5dcd\",\n        checksum: \"1\",\n        checkdigit: \"9\",\n    }, {\n        radix: 16,\n        input: \"1fea740e0e1f\",\n        checksum: \"7\",\n        checkdigit: \"4\",\n    }, {\n        radix: 18,\n        input: \"0123456789abcdefgh\",\n        checksum: \"d\",\n        checkdigit: \"d\",\n    }, {\n        radix: 18,\n        input: \"995dgf\",\n        checksum: \"9\",\n        checkdigit: \"1\",\n    }, {\n        radix: 18,\n        input: \"9f80h32h\",\n        checksum: \"1\",\n        checkdigit: \"0\",\n    }, {\n        radix: 18,\n        input: \"5f9428e493g4\",\n        checksum: \"8\",\n        checkdigit: \"c\",\n    }, {\n        radix: 20,\n        input: \"0123456789abcdefghij\",\n        checksum: \"5\",\n        checkdigit: \"5\",\n    }, {\n        radix: 20,\n        input: \"918jci\",\n        checksum: \"h\",\n        checkdigit: \"d\",\n    }, {\n        radix: 20,\n        input: \"jab7j50d\",\n        checksum: \"g\",\n        checkdigit: \"j\",\n    }, {\n        radix: 20,\n        input: \"c56fe85eb6gg\",\n        checksum: \"g\",\n        checkdigit: \"5\",\n    }, {\n        radix: 22,\n        input: \"0123456789abcdefghijkl\",\n        checksum: \"g\",\n        checkdigit: \"g\",\n    }, {\n        radix: 22,\n        input: \"de57le\",\n        checksum: \"5\",\n        checkdigit: \"l\",\n    }, {\n        radix: 22,\n        input: \"e3fg6dfc\",\n        checksum: \"f\",\n        checkdigit: \"d\",\n    }, {\n        radix: 22,\n        input: \"1f8l80ai4kbg\",\n        checksum: \"l\",\n        checkdigit: \"f\",\n    }, {\n        radix: 24,\n        input: \"0123456789abcdefghijklmn\",\n        checksum: \"6\",\n        checkdigit: \"6\",\n    }, {\n        radix: 24,\n        input: \"agne7d\",\n        checksum: \"4\",\n        checkdigit: \"f\",\n    }, {\n        radix: 24,\n        input: \"1l4d9cf4\",\n        checksum: \"d\",\n        checkdigit: \"c\",\n    }, {\n        radix: 24,\n        input: \"blc1j09i3296\",\n        checksum: \"8\",\n        checkdigit: \"7\",\n    }, {\n        radix: 26,\n        input: \"0123456789abcdefghijklmnop\",\n        checksum: \"j\",\n        checkdigit: \"j\",\n    }, {\n        radix: 26,\n        input: \"82n9op\",\n        checksum: \"i\",\n        checkdigit: \"2\",\n    }, {\n        radix: 26,\n        input: \"e9cddn70\",\n        checksum: \"9\",\n        checkdigit: \"i\",\n    }, {\n        radix: 26,\n        input: \"ck0ep419knom\",\n        checksum: \"p\",\n        checkdigit: \"g\",\n    }, {\n        radix: 28,\n        input: \"0123456789abcdefghijklmnopqr\",\n        checksum: \"7\",\n        checkdigit: \"7\",\n    }, {\n        radix: 28,\n        input: \"a6hnoo\",\n        checksum: \"h\",\n        checkdigit: \"9\",\n    }, {\n        radix: 28,\n        input: \"lblc7kh0\",\n        checksum: \"a\",\n        checkdigit: \"f\",\n    }, {\n        radix: 28,\n        input: \"64k5piod3lmf\",\n        checksum: \"0\",\n        checkdigit: \"p\",\n    }, {\n        radix: 30,\n        input: \"0123456789abcdefghijklmnopqrst\",\n        checksum: \"m\",\n        checkdigit: \"m\",\n    }, {\n        radix: 30,\n        input: \"t69j7d\",\n        checksum: \"9\",\n        checkdigit: \"s\",\n    }, {\n        radix: 30,\n        input: \"p54o9ig3\",\n        checksum: \"a\",\n        checkdigit: \"o\",\n    }, {\n        radix: 30,\n        input: \"gc1njrt55030\",\n        checksum: \"6\",\n        checkdigit: \"1\",\n    }, {\n        radix: 32,\n        input: \"0123456789abcdefghijklmnopqrstuv\",\n        checksum: \"8\",\n        checkdigit: \"8\",\n    }, {\n        radix: 32,\n        input: \"rdou19\",\n        checksum: \"u\",\n        checkdigit: \"3\",\n    }, {\n        radix: 32,\n        input: \"ighj0pc7\",\n        checksum: \"3\",\n        checkdigit: \"8\",\n    }, {\n        radix: 32,\n        input: \"op4nn5fvjsrs\",\n        checksum: \"g\",\n        checkdigit: \"j\",\n    }, {\n        radix: 34,\n        input: \"0123456789abcdefghijklmnopqrstuvwx\",\n        checksum: \"p\",\n        checkdigit: \"p\",\n    }, {\n        radix: 34,\n        input: \"nvftj5\",\n        checksum: \"b\",\n        checkdigit: \"f\",\n    }, {\n        radix: 34,\n        input: \"u9v9g162\",\n        checksum: \"j\",\n        checkdigit: \"b\",\n    }, {\n        radix: 34,\n        input: \"o5gqg5d7gjh9\",\n        checksum: \"5\",\n        checkdigit: \"q\",\n    }, {\n        radix: 36,\n        input: \"0123456789abcdefghijklmnopqrstuvwxyz\",\n        checksum: \"9\",\n        checkdigit: \"9\",\n    }, {\n        radix: 36,\n        input: \"29zehu\",\n        checksum: \"i\",\n        checkdigit: \"j\",\n    }, {\n        radix: 36,\n        input: \"1snmikbu\",\n        checksum: \"s\",\n        checkdigit: \"v\",\n    }, {\n        radix: 36,\n        input: \"jpkar545q7gb\",\n        checksum: \"3\",\n        checkdigit: \"d\",\n    },\n];\n\ntestCases.forEach(element => {\n    TestRegister.addTests([\n        {\n            name: \"Luhn Checksum Mod \" + element.radix + \" on \" + element.input,\n            input: element.input,\n            expectedOutput: \"Checksum: \" + element.checksum + \"\\nCheckdigit: \" + element.checkdigit + \"\\nLuhn Validated String: \" + element.input + element.checkdigit,\n            recipeConfig: [\n                {\n                    op: \"Luhn Checksum\",\n                    args: [element.radix]\n                },\n            ],\n        },\n    ]);\n});\n\nTestRegister.addTests([\n    {\n        name: \"Luhn Checksum on standard data\",\n        input: \"35641709012469\",\n        expectedOutput: \"Checksum: 7\\nCheckdigit: 0\\nLuhn Validated String: 356417090124690\",\n        recipeConfig: [\n            {\n                op: \"Luhn Checksum\",\n                args: [10]\n            },\n        ],\n    },\n    {\n        name: \"Luhn Checksum on standard data 2\",\n        input: \"896101950123440000\",\n        expectedOutput: \"Checksum: 5\\nCheckdigit: 1\\nLuhn Validated String: 8961019501234400001\",\n        recipeConfig: [\n            {\n                op: \"Luhn Checksum\",\n                args: [10]\n            },\n        ],\n    },\n    {\n        name: \"Luhn Checksum on standard data 3\",\n        input: \"35726908971331\",\n        expectedOutput: \"Checksum: 6\\nCheckdigit: 7\\nLuhn Validated String: 357269089713317\",\n        recipeConfig: [\n            {\n                op: \"Luhn Checksum\",\n                args: [10]\n            },\n        ],\n    },\n    {\n        name: \"Luhn Checksum on empty data\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Luhn Checksum\",\n                args: [10]\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/MIMEDecoding.mjs",
    "content": "/**\n * MIME Header Decoding tests\n *\n * @author mshwed [m@ttshwed.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Encoded comments\",\n        input: \"(=?ISO-8859-1?Q?a?=)\",\n        expectedOutput: \"(a)\",\n        recipeConfig: [\n            {\n                \"op\": \"MIME Decoding\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"Encoded adjacent comments whitespace\",\n        input: \"(=?ISO-8859-1?Q?a?= b)\",\n        expectedOutput: \"(a b)\",\n        recipeConfig: [\n            {\n                \"op\": \"MIME Decoding\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"Encoded adjacent single whitespace ignored\",\n        input: \"(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)\",\n        expectedOutput: \"(ab)\",\n        recipeConfig: [\n            {\n                \"op\": \"MIME Decoding\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"Encoded adjacent double whitespace ignored\",\n        input: \"(=?ISO-8859-1?Q?a?=  =?ISO-8859-1?Q?b?=)\",\n        expectedOutput: \"(ab)\",\n        recipeConfig: [\n            {\n                \"op\": \"MIME Decoding\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"Encoded adjacent CRLF whitespace ignored\",\n        input: \"(=?ISO-8859-1?Q?a?=\\r\\n =?ISO-8859-1?Q?b?=)\",\n        expectedOutput: \"(ab)\",\n        recipeConfig: [\n            {\n                \"op\": \"MIME Decoding\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"UTF-8 Encodings Multiple Headers\",\n        input: \"=?utf-8?q?=C3=89ric?= <eric@example.org>, =?utf-8?q?Ana=C3=AFs?= <anais@example.org>\",\n        expectedOutput: \"Éric <eric@example.org>, Anaïs <anais@example.org>\",\n        recipeConfig: [\n            {\n                \"op\": \"MIME Decoding\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"ISO Decoding\",\n        input: \"From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>\\nTo: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>\\nCC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>\\nSubject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\\n=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=\",\n        expectedOutput: \"From: Keith Moore <moore@cs.utk.edu>\\nTo: Keld Jørn Simonsen <keld@dkuug.dk>\\nCC: André Pirard <PIRARD@vm1.ulg.ac.be>\\nSubject: If you can read this you understand the example.\",\n        recipeConfig: [\n            {\n                \"op\": \"MIME Decoding\",\n                \"args\": []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/MS.mjs",
    "content": "/**\n * MS tests.\n *\n * @author bwhitn [brian.m.whitney@outlook.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Microsoft Script Decoder\",\n        input: \"#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&@*eEI@#@&@#@&\\x7fjm.raY 214Wv:zms/obI0xEAAA==^#~@\",\n        expectedOutput: \"var my_msg = \\\"Testing <1><2><3>!\\\";\\r\\n\\r\\nWScript.Echo(my_msg);\",\n        recipeConfig: [\n            {\n                \"op\": \"Microsoft Script Decoder\",\n                \"args\": []\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Magic.mjs",
    "content": "/**\n * Magic tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport { JPG_RAW } from \"../../samples/Images.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Magic: nothing\",\n        input: \"\",\n        expectedOutput: \"Nothing of interest could be detected about the input data.\\nHave you tried modifying the operation arguments?\",\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic: hex, correct rank\",\n        input: \"41 42 43 44 45\",\n        expectedMatch: /Properties[^#]+?#recipe=From_Hex\\('Space'\\)\"/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic: jpeg render\",\n        input: JPG_RAW,\n        expectedMatch: /Render_Image\\('Raw'\\)/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic: mojibake\",\n        input: \"\\xd0\\x91\\xd1\\x8b\\xd1\\0\\xd1\\x82\\xd1\\x80\\xd0\\xb0\\xd1\\0\\x20\\xd0\\xba\\xd0\\xbe\\xd1\\x80\\xd0\\xb8\\xd1\\x87\\xd0\\xbd\\xd0\\xb5\\xd0\\xb2\\xd0\\xb0\\xd1\\0\\x20\\xd0\\xbb\\xd0\\xb8\\xd1\\0\\xd0\\xb0\\x20\\xd0\\xbf\\xd1\\x80\\xd1\\x8b\\xd0\\xb3\\xd0\\xb0\\xd0\\xb5\\xd1\\x82\\x20\\xd1\\x87\\xd0\\xb5\\xd1\\x80\\xd0\\xb5\\xd0\\xb7\\x20\\xd0\\xbb\\xd0\\xb5\\xd0\\xbd\\xd0\\xb8\\xd0\\xb2\\xd1\\x83\\xd1\\x8e\\x20\\xd1\\0\\xd0\\xbe\\xd0\\xb1\\xd0\\xb0\\xd0\\xba\\xd1\\x83\\x2e\",\n        expectedMatch: /Быртрар коричневар лира прыгает через ленивую робаку./,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [1, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic: extensive language support, Yiddish\",\n        input: \"די שנעל ברוין פאָקס דזשאַמפּס איבער די פויל הונט.\",\n        expectedMatch: /Yiddish/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [1, false, true]\n            }\n        ],\n    },\n    {\n        name: \"Magic Chain: Base64\",\n        input: \"WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09\",\n        expectedMatch: /From_Base64\\('A-Za-z0-9\\+\\/=',true,false\\)\\nFrom_Base64\\('A-Za-z0-9\\+\\/=',true,false\\)\\nFrom_Base64\\('A-Za-z0-9\\+\\/=',true,false\\)/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic Chain: Hex -> Hexdump -> Base64\",\n        input: \"MDAwMDAwMDAgIDM3IDM0IDIwIDM2IDM1IDIwIDM3IDMzIDIwIDM3IDM0IDIwIDMyIDMwIDIwIDM3ICB8NzQgNjUgNzMgNzQgMjAgN3wKMDAwMDAwMTAgIDMzIDIwIDM3IDM0IDIwIDM3IDMyIDIwIDM2IDM5IDIwIDM2IDY1IDIwIDM2IDM3ICB8MyA3NCA3MiA2OSA2ZSA2N3w=\",\n        expectedMatch: /From_Base64\\('A-Za-z0-9\\+\\/=',true,false\\)\\nFrom_Hexdump\\(\\)\\nFrom_Hex\\('Space'\\)/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic Chain: Charcode -> Octal -> Base32\",\n        input: \"GY3SANRUEA2DAIBWGYQDMNJAGQYCANRXEA3DGIBUGAQDMNZAGY2CANBQEA3DEIBWGAQDIMBAGY3SANRTEA2DAIBWG4QDMNBAGQYCANRXEA3DEIBUGAQDMNRAG4YSANBQEA3DMIBRGQ2SANBQEA3DMIBWG4======\",\n        expectedMatch: /From_Base32\\('A-Z2-7=',false\\)\\nFrom_Octal\\('Space'\\)\\nFrom_Hex\\('Space'\\)/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic Chain: Base64 output\",\n        input: \"WkVkV2VtUkRRbnBrU0Vwd1ltMWpQUT09\",\n        expectedMatch: /test string/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic Chain: Decimal -> Base32 -> Base32\",\n        input: \"I5CVSVCNJFBFER2BLFJUCTKKKJDVKUKEINGUUV2FIFNFIRKJIJJEORJSKNAU2SSSI5MVCRCDJVFFKRKBLFKECTSKIFDUKWKUIFEUEUSHIFNFCPJ5HU6Q====\",\n        expectedMatch: /test string/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [3, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Magic: Raw Inflate\",\n        input: \"\\x4d\\x52\\xb1\\x6e\\xdc\\x30\\x0c\\xdd\\xf3\\x15\\x44\\x80\\x6e\\xae\\x91\\x02\\x4d\\x80\\x8e\\x4d\\x9a\\x21\\x53\\x8b\\xa6\\x43\\x56\\x5a\\xe2\\x9d\\x84\\x93\\x25\\x43\\x94\\xed\\xf8\\xef\\xf3\\xe8\\x6b\\x0e\\xb7\\x1c\\xce\\xd4\\x7b\\x8f\\x8f\\x7c\\x7c\\xda\\x06\\xa9\\x4f\\x41\\x0e\\x14\\x95\\x98\\x34\\x8e\\x53\\x92\\x8e\\x62\\x6e\\x73\\x6c\\x71\\x11\\x5a\\x65\\x20\\x9e\\x26\\x3a\\x94\\x4a\\x8e\\x6b\\xdd\\x62\\x3e\\x52\\x99\\x1b\\x71\\x4a\\x34\\x72\\xce\\x52\\xa9\\x1c\\xe8\\xd6\\x99\\xd0\\x2d\\x95\\x49\\x2a\\xb7\\x58\\xb2\\xd2\\x1a\\x5b\\x88\\x19\\xa2\\x26\\x31\\xd4\\xb2\\xaa\\xd4\\x9e\\xfe\\x05\\x51\\xb9\\x86\\xc5\\xec\\xd2\\xec\\xe5\\x7f\\x6b\\x92\\xec\\x8a\\xb7\\x1e\\x29\\x9e\\x84\\xde\\x7e\\xff\\x25\\x34\\x7e\\x64\\x95\\x87\\xef\\x1d\\x8d\\xa5\\x0a\\xb9\\x62\\xc0\\x77\\x43\\xd6\\x6d\\x32\\x91\\x33\\xf6\\xe7\\xf3\\x6b\\x47\\xbf\\x9e\\x5f\\x89\\xb3\\xa7\\xc7\\x54\\xd6\\x43\\xd4\\xd0\\x91\\xab\\x82\\x4e\\x10\\x1c\\x62\\xe6\\xba\\xed\\xaf\\x41\\xde\\xfd\\x3c\\x4e\\x8a\\x57\\x88\\x55\\x51\\x35\\x15\\x7b\\xf1\\x72\\x5d\\xc1\\x60\\x9e\\x1b\\x03\\xc6\\xc9\\xcd\\xe9\\xac\\x13\\x58\\x31\\xc3\\x8e\\x76\\x41\\xdc\\x49\\xe7\\x11\\x42\\x2f\\x7f\\x96\\x87\\xbd\\xf6\\xd6\\xdf\\xdf\\xfd\\xa0\\x89\\xab\\x02\\x0c\\x66\\xe0\\x7c\\x34\\x1a\\xfe\\x54\\x76\\x0d\\xeb\\xfa\\x1c\\x11\\x2c\\x23\\x8c\\xb3\\x0b\\xfb\\x64\\xfd\\xcd\\x0d\\xb6\\x43\\xad\\x94\\x64\\x69\\x78\\xd1\\x78\\xcc\\xe2\\x51\\x00\\x85\\x07\\x2c\\x67\\x28\\x2d\\x50\\x13\\x17\\x72\\x84\\xa3\\x9d\\x9d\\x4b\\xfe\\x7a\\x5d\\xe1\\xb4\\x69\\x53\\xe3\\x20\\x9c\\x38\\x99\\x69\\xd9\\x87\\xc0\\xa2\\x2f\\xab\\x5b\\x79\\x3b\\xe7\\x63\\x41\\x06\\x5e\\xcc\\x1f\\x18\\x5e\\x20\\x61\\xe5\\x0b\\xd0\\xbc\\xa8\\x25\\xc0\\xe9\\x58\\x2a\\x5e\\x46\\xed\\xe9\\xa5\\x41\\x40\\x81\\xc9\\x4e\\x70\\x22\\xbe\\xbb\\x58\\xed\\x68\\x98\\x63\\xc2\\x6d\\xc0\\x18\\x72\\xad\\x32\\x4a\\x6e\\x38\\x94\\x8d\\x10\\x6e\\x2d\\xc0\\xd2\\x60\\x09\\x7c\\xfa\\x34\\x4f\\x2d\\x48\\xac\\xf4\\xed\\xee\\x0b\\x3e\\x72\\x59\\xf6\\xab\\xa0\\x16\\x47\\x1c\\xc9\\x82\\x65\\xa9\\xe0\\x17\\xb6\\x36\\xc1\\x46\\xfb\\x0f\",\n        expectedMatch: /#recipe=Raw_Inflate(.|\\n)+CyberChef is a simple, intuitive web app for carrying out all manner of /,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [1, false, false]\n            }\n        ]\n    },\n    {\n        name: \"Magic: Defang IP Address, valid\",\n        input: \"192.168.0.1\",\n        expectedMatch: /Properties[^#]+?#recipe=Defang_IP_Addresses\\(\\)\"/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [1, false, false]\n            }\n        ]\n    },\n    {\n        name: \"Magic: Defang IP Address, invalid\",\n        input: \"192.168.0.1.0\",\n        unexpectedMatch: /Defang_IP_Addresses/,\n        recipeConfig: [\n            {\n                op: \"Magic\",\n                args: [1, false, false]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Media.mjs",
    "content": "/**\n * Media operation tests.\n * @author anthony-arnold [anthony.arnold@uqconnect.edu.au]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Play Media: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            { op: \"Play Media\", args: [\"Raw\"] }\n        ]\n    },\n    {\n        name: \"Play Media: raw wav\",\n        input: \"52494646bcaf010057415645666d74201000000001000100401f0000401f0000010008006461746198af0100818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081808180818081818281807f82807f817e81808280827d8086817d80828184817f80807d847f7e7e8582837b81857e7e82867f7c7f857e7d838182837f83847b7c838082807c7d838481827e83827c7f848383807c7f7c80848085808180827e83827f7c82827c7f858183807c837b847f7f82827a85837a8287817f7882827f89837e7f877b7e808281817f7e807c827b7e7d8383838486838883837d7d7682797d847f7d868287867d83847c81807d7e818080828082828380807e7f827d8181807e828082858581808081807f7a7f81808183807d7c7f7f858180848484857f8082817e827b7e7f7f8385807f7f7d858183808081807f7f8183847f7e7d81828284857d7f7e8084868182837d7e7d7a7e7e8085837f80807b7f7b7a80827d7f817e8386838588868386868689868586838386868584807c7a78787875706e6c6a6c6d70757a7c7e7f80858b94a1a29a9996989a9e9ca09ea19caa8e554129243e4d475e7184a0ada2a4a0918179646167696f7b7a8390939ca4aab2af9c9691909aa2a3ae74321e162a4d5c506c88a5babca6a4a7a18c7a5e5c646f6a6b6d7f929a8f858182827e79849299a1abb2c7cc975b3826324447496788a5b8b6a9aaa9977b60494c586467707c949fa195938f8f847a717c88a0aab7b7d6a46045362f3c483d6089a1acb4a3a4a89570615652676f6d788f97a29c8e8992898381808393999da9bf8e515e504c4e5d4879929a90a1909e9c855c5b5f717679768b9b9f958c838c8d8c7f8782969ca797a963585f63425d60687e93818b99938c84786f7e7a7b7f86838a867f8486858899939697a7a6be6e465f6c4d465d678e948275958a867063688e8b828fa2a0988b7a7f7c737185898a8e90989c9b8c8c6e5f5a655159646f727f737382857c7f898a9aa4b1b6cac5d4957d695b403c4152\",\n        expectedOutput: \"<audio src='data:audio/x-wav;base64,UklGRryvAQBXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YZivAQCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGBgoGAf4KAf4F+gYCCgIJ9gIaBfYCCgYSBf4CAfYR/fn6FgoN7gYV+foKGf3x/hX59g4GCg3+DhHt8g4CCgHx9g4SBgn6Dgnx/hIODgHx/fICEgIWAgYCCfoOCf3yCgnx/hYGDgHyDe4R/f4KCeoWDeoKHgX94goJ/iYN+f4d7foCCgYF/foB8gnt+fYODg4SGg4iDg319doJ5fYR/fYaCh4Z9g4R8gYB9foGAgIKAgoKDgIB+f4J9gYGAfoKAgoWFgYCAgYB/en+BgIGDgH18f3+FgYCEhISFf4CCgX6Ce35/f4OFgH9/fYWBg4CAgYB/f4GDhH9+fYGCgoSFfX9+gISGgYKDfX59en5+gIWDf4CAe397eoCCfX+BfoOGg4WIhoOGhoaJhoWGg4OGhoWEgHx6eHh4dXBubGpsbXB1enx+f4CFi5ShopqZlpianpygnqGcqo5VQSkkPk1HXnGEoK2ipKCRgXlkYWdpb3t6g5CTnKSqsq+clpGQmqKjrnQyHhYqTVxQbIilurympKehjHpeXGRvamttf5Kaj4WBgoJ+eYSSmaGrssfMl1s4JjJER0lniKW4tqmqqZd7YElMWGRncHyUn6GVk4+PhHpxfIigqre31qRgRTYvPEg9YImhrLSjpKiVcGFWUmdvbXiPl6KcjomSiYOBgIOTmZ2pv45RXlBMTl1IeZKakKGQnpyFXFtfcXZ5doubn5WMg4yNjH+Hgpacp5epY1hfY0JdYGh+k4GLmZOMhHhvfnp7f4aDioZ/hIaFiJmTlpenpr5uRl9sTUZdZ46UgnWVioZwY2iOi4KPoqCYi3p/fHNxhYmKjpCYnJuMjG5fWmVRWWRvcn9zc4KFfH+JipqksbbKxdSVfWlbQDxBUg==' type='audio/x-wav' controls><p>Unsupported media type.</p></audio>\",\n        recipeConfig: [\n            { op: \"From Hex\", args: [\"Space\"] },\n            { op: \"Play Media\", args: [\"Raw\"] }\n        ]\n    },\n    {\n        name: \"Play Media: hex ogg\",\n        input: \"4f676753000200000000000000003129000000000000642493e3011e01766f72626973000000000244ac0000000000008138010000000000b8014f676753000000000000000000003129000001000000a3565ae9102dffffffffffffffffffffffffffff2403766f726269731d000000586970682e4f7267206c6962566f726269732049203230303230373137000000000105766f726269732242435601000001009c739a318799629452892194de3968196394526929a55a4aa9a183166babbdf7de7befbdf7de7bef1d739431469552524aa99d739631471563524a89a5945642682184d662abbdf7de6befb5f6de7bef99424c29a41442084a281d538c29a494424a4a0825640e3ac61c538c52093dd65e6bccbdb6d87beda163ce39e61c534c4a6821740e3ae69c534c4a68a984524206a153d05289adf7de62ebb9a5da7bef81d0905500000100c040101ab20a00500000108aa1188a028486ac020032000004e0288ee3388ee23892623916101ab20a00000200100000c0900c4bb114cdd1244dd22ccf134dd3377dd3366d55d7755dd7755dd77520346415000001004040a719a61a20c28c6416080d590500200000004420c3140342435601000001000052243949a2e4a494520e836431492ae5a494521ec5e4514d3206a594524a29a594524a29a594520a8364394a2ae5a4945212a364314aaad4a494521ee5e4\",\n        expectedOutput: \"<audio src='data:audio/ogg;base64,T2dnUwACAAAAAAAAAAAxKQAAAAAAAGQkk+MBHgF2b3JiaXMAAAAAAkSsAAAAAAAAgTgBAAAAAAC4AU9nZ1MAAAAAAAAAAAAAMSkAAAEAAACjVlrpEC3//////////////////yQDdm9yYmlzHQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMDIwNzE3AAAAAAEFdm9yYmlzIkJDVgEAAAEAnHOaMYeZYpRSiSGU3jloGWOUUmkppVpKqaGDFmurvffee++999577x1zlDFGlVJSSqmdc5YxRxVjUkqJpZRWQmghhNZiq7333mvvtfbee++ZQkwppBRCCEooHVOMKaSUQkpKCCVkDjrGHFOMUgk91l5rzL222HvtoWPOOeYcU0xKaCF0DjrmnFNMSmiphFJCBqFT0FKJrffeYuu5pdp774HQkFUAAAEAwEAQGrIKAFAAABCKoRiKAoSGrAIAMgAABOAojuM4juI4kmI5FhAasgoAAAIAEAAAwJAMS7EUzdEkTdIszxNN0zd90zZtVdd1Xdd1Xdd1IDRkFQAAAQBAQKcZphogwoxkFggNWQUAIAAAAEQgwxQDQkNWAQAAAQAAUiQ5SaLkpJRSDoNkMUkq5aSUUh7F5FFNMgallFJKKaWUUkoppZRSCoNkOUoq5aSUUhKjZDFKqtSklFIe5eQ=' type='audio/ogg' controls><p>Unsupported media type.</p></audio>\",\n        recipeConfig: [\n            { op: \"Play Media\", args: [\"Hex\"] }\n        ]\n    },\n    {\n        name: \"Play Media: base64 webm\",\n        input: \"GkXfo6NChoEBQveBAULygQRC84EIQoKEd2VibeyCAABCh4EBQoWBARhTgGcQIQmHEU2bdLtNu4tTq4QVSalmU6yBQE27i1OrhBZUrmtTrIGsTbuNU6uEEU2bdFOsgyEJc027jFOrhBxTu2tTrIINQRVJqWbnc6SQRsadRaGFqSlNPQovdQBWvSrXsYMPQkBEiYRG/cAARGGIBBu7mlIesABNgKVodHRwOi8vc291cmNlZm9yZ2UubmV0L3Byb2plY3RzL3lhbWthV0GQU29yZW5zb24gU3F1ZWV6ZRZUrmtMj66414EBc8WHiBmgyaYxwoOBASPjg4QCYloAIzFPhD+AAACGhVZfVlA4JYaIg1ZQOOCIsIICgLqCAWiuTFLXgQJzxYgBiP65XI76uoOBAiMxT4Q/gAAAhohBX1ZPUkJJU2OiTBkCHjoBdm9yYmlzAAAAAAFErAAA/////wD6AAD/////uAEDdm9yYmlzKgAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAwMzI1IChFdmVyeXdoZXJlKQAAAAABBXZvcmJpcx9CQ1YBAAABABhjVClGmVLSSokZc5QxRplikkqJpYQWQkidcxRTqTnXnGusubUghBAaU1ApBZlSjlJpGWOQKQWZUhBLSSV0EjonnWMQW0nB1phri0G2HIQNmlJMKcSUUopCCBlTjCnFlFJKQgcldA465hxTjkooQbicc6u1lpZji6l0kkrnJGRMQkgphZJKB6VTTkJINZbWUikdc1JSakHoIIQQQrYghA2C0JBVAAABAMBAEBqyCgBQAAAQiqEYigKEhqwCADIAAASgKI7iKI4jOZJjSRYQGrIKAAACABAAAMBwFEmRFMmxJEvSLEvTRFFVfdU2VVX2dV3XdV3XdSA0ZBUAAAEAQEinmaUaIMIMZBgIDVkFACAAAABGKMIQA0JDVgEAAAEAAGIoOYgmtOZ8c46DZjloKsXmdHAi1eZJbirm5pxzzjknm3PGOOecc4pyZjFoJrTmnHMSg2YpaCa05pxznsTmQWuqtOacc8Y5p4NxRhjnnHOatOZBajbW5pxzFrSmOWouxeaccyLl5kltLtXmnHPOOeecc84555xzqhenc3BOOOecc6L25lpuQhfnnHM+Gad7c0I455xzzjnnnHPOOeecc4LQkFUAABAAAEEYNoZxpyBIn6OBGEWIacikB92jwyRoDHIKqUejo5FS6iCUVMZJKZ0gNGQVAAAIAAAhhBRSSCGFFFJIIYUUUoghhhhiyCmnnIIKKqmkoooyyiyzzDLLLLPMMuuws8467DDEEEMMrbQSS0211VhjrbnnnGsO0lpprbXWSimllFJKKQgNWQUAgAAAEAgZZJBBRiGFFFKIIaaccsopqKACQkNWAQCAAAACAAAAPMlzREd0REd0REd0REd0RMdzPEeUREmUREm0TMvUTE8VVdWVXVvWZd32bWEXdt33dd/3dePXhWFZlmVZlmVZlmVZlmVZlmVZgtCQVQAACAAAgBBCCCGFFFJIIaUYY8wx56CTUEIgNGQVAAAIACAAAADAURzFcSRHciTJkixJkzRLszzN0zxN9ERRFE3TVEVXdEXdtEXZlE3XdE3ZdFVZtV1Ztm3Z1m1flm3f933f933f933f933f93UdCA1ZBQBIAADoSI6kSIqkSI7jOJIkAaEhqwAAGQAAAQAoiqM4juNIkiRJlqRJnuVZomZqpmd6qqgCoSGrAABAAAABAAAAAAAomuIppuIpouI5oiNKomVaoqZqriibsuu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6LhAasgoAkAAA0JEcyZEcSZEUSZEcyQFCQ1YBADIAAAIAcAzHkBTJsSxL0zzN0zxN9ERP9ExPFV3RBUJDVgEAgAAAAgAAAAAAMCTDUixHczRJlFRLtVRNtVRLFVVPVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVNU3TNE0gNGQlAAAEAMBijcHlICElJeXeEMIQk54xJiG1XiEEkZLeMQYVg54yogxy3kLjEIMeCA1ZEQBEAQAAxiDHEHPIOUepkxI556h0lBrnHKWOUmcpxZhizSiV2FKsjXOOUketo5RiLC12lFKNqcYCAAACHAAAAiyEQkNWBABRAACEMUgppBRijDmnnEOMKeeYc4Yx5hxzjjnnoHRSKuecdE5KxBhzjjmnnHNSOieVc05KJ6EAAIAABwCAAAuh0JAVAUCcAIBBkjxP8jRRlDRPFEVTdF1RNF3X8jzV9ExTVT3RVFVTVW3ZVFVZljzPND3TVFXPNFXVVFVZNlVVlkVV1W3TdXXbdFXdlm3b911bFnZRVW3dVF3bN1XX9l3Z9n1Z1nVj8jxV9UzTdT3TdGXVdW1bdV1d90xTlk3XlWXTdW3blWVdd2XZ9zXTdF3TVWXZdF3ZdmVXt11Z9n3TdYXflWVfV2VZGHZd94Vb15XldF3dV2VXN1ZZ9n1b14Xh1nVhmTxPVT3TdF3PNF1XdV1fV13X1jXTlGXTdW3ZVF1ZdmXZ911X1nXPNGXZdF3bNl1Xll1Z9n1XlnXddF1fV2VZ+FVX9nVZ15Xh1m3hN13X91VZ9oVXlnXh1nVhuXVdGD5V9X1TdoXhdGXf14XfWW5dOJbRdX1hlW3hWGVZOX7hWJbd95VldF1fWG3ZGFZZFoZf+J3l9n3jeHVdGW7d58y67wzH76T7ytPVbWOZfd1ZZl93juEYOr/w46mqr5uuKwynLAu/7evGs/u+soyu6/uqLAu/KtvCseu+8/y+sCyj7PrCasvCsNq2Mdy+biy/cBzLa+vKMeu+UbZ1fF94CsPzdHVdeWZdx/Z1dONHOH7KAACAAQcAgAATykChISsCgDgBAI8kiaJkWaIoWZYoiqbouqJouq6kaaapaZ5pWppnmqZpqrIpmq4saZppWp5mmpqnmaZomq5rmqasiqYpy6ZqyrJpmrLsurJtu65s26JpyrJpmrJsmqYsu7Kr267s6rqkWaapeZ5pap5nmqZqyrJpmq6reZ5qep5oqp4oqqpqqqqtqqosW55nmproqaYniqpqqqatmqoqy6aq2rJpqrZsqqptu6rs+rJt67ppqrJtqqYtm6pq267s6rIs27ovaZppap5nmprnmaZpmrJsmqorW56nmp4oqqrmiaZqqqosm6aqypbnmaoniqrqiZ5rmqoqy6Zq2qppmrZsqqotm6Yqy65t+77ryrJuqqpsm6pq66ZqyrJsy77vyqruiqYpy6aq2rJpqrIt27Lvy7Ks+6JpyrJpqrJtqqouy7JtG7Ns+7pomrJtqqYtm6oq27It+7os27rvyq5vq6qs67It+7ru+q5w67owvLJs+6qs+ror27pv6zLb9n1E05RlUzVt21RVWXZl2fZl2/Z90TRtW1VVWzZN1bZlWfZ9WbZtYTRN2TZVVdZN1bRtWZZtYbZl4XZl2bdlW/Z115V1X9d949dl3ea6su3Lsq37qqv6tu77wnDrrvAKAAAYcAAACDChDBQashIAiAIAAIxhjDEIjVLOOQehUco55yBkzkEIIZXMOQghlJI5B6GUlDLnIJSSUgihlJRaCyGUlFJrBQAAFDgAAATYoCmxOEChISsBgFQAAIPjWJbnmaJq2rJjSZ4niqqpqrbtSJbniaJpqqptW54niqapqq7r65rniaJpqqrr6rpomqapqq7ruroumqKpqqrrurKum6aqqq4ru7Ls66aqqqrryq4s+8Kquq4ry7Jt68Kwqq7ryrJs27Zv3Lqu677v+8KRreu6LvzCMQxHAQDgCQ4AQAU2rI5wUjQWWGjISgAgAwCAMAYhgxBCBiGEkFJKIaWUEgAAMOAAABBgQhkoNGQlABADAAAQASGDEEIIIYQQQgghhBBCCCGEEELnnHPOOeecc84JANiPcACQejAxMYWFhqwEAFIBAABjlFKKMecgRIw5xhh0EkqKGHOOMQelpFQ5ByGEVFrLrXIOQggptVRb5pyU1mKMOcbMOSkpxVZzzqGU1GKsueaaOymt1ZprzbmW1mrNNedccy6txZprzjXn3HLMNeecc845xpxzzjnnnHMBADgNDgCgBzasjnBSNBZYaMhKACAVAIBARinGnHMOOoQUY845ByGESCHGnHMOQggVY845Bx2EECrGHHMOQgghZM45ByGEEELInIMOOgghhNBBByGEEEIopXMQQgghhBJKCCGEEEIIIYQOQgghhBBCCCGEEEIIoZQSQgghhFBCKCUUAABY4AAAEGDD6ggnRWOBhYasBACAAAAghyWolDNhkGPQY0OQctRMgxBTTnSmmJPaTMUUZA5EJ51EhlpQtpfMAgAAIAgACDABBAYICr4QAmIMAEAQIjNEQmEVLDAogwaHeQDwABEhEQAkJijSLi6gywAXdHHXgRCCEIQgFgdQQAIOTrjhiTc84QYn6BSVOggAAAAAAAMAeAAAOCiAiIjmKiwuMDI0Njg6PAIAAAAAAAYAPgAAjg8gIqK5CosLjAyNDY4OjwAAAAAAAAAAACAgIAAAAAAAEAAAACAgJYaIhlZvcmJpc+GGtYRHLEQAHFO7a0IAu4yzgQC3h/eBAfGCD0e7kbOCAli3i/eBAfGCD0dTeIEqu5Gzgg3At4v3gQHxgg9HU3iB8buSs4Ib0LeM94EB8YIPR1N4ggHmu5KzgiN4t4z3gQHxgg9HU3iCAnW7krOCK8C3jPeBAfGCD0dTeIIDBruSs4IzkLeM94EB8YIPR1N4ggOXu5KzgjY4t4z3gQHxgg9HU3iCA8W7krOCOpi3jPeBAfGCD0dTeIIEIbuSs4I+gLeM94EB8YIPR1N4ggRlu5KzgkDYt4z3gQHxgg9HU3iCBI67krOCSNC3jPeBAfGCD0dTeIIFIbuSs4JJwLeM94EB8YIPR1N4ggU2u5Kzgk3Qt4z3gQHxgg9HU3iCBYO7krOCUZC3jPeBAfGCD0dTeIIFxLuSs4JR4LeM94EB8YIPR1N4ggXKu5KzglXwt4z3gQHxgg9HU3iCBhe7krOCWYi3jPeBAfGCD0dTeIIGXLuSs4JhWLeM94EB8YIPR1N4ggblu5KzgmWQt4z3gQHxgg9HU3iCBy67krOCaDi3jPeBAfGCD0dTeIIHXLuSs4JosLeM94EB8YIPR1N4ggdku5KzgnUIt4z3gQHxgg9HU3iCCEK7krOCddC3jPeBAfGCD0dTeIIIUruSs4J2ILeM94EB8YIPR1N4gghdu5Kzgn5At4z3gQHxgg9HU3iCCPgfQ7Z1ECD6JOeBAKeCD0ejQbOBAACAEjQAnQEqgAJoATkPAEEcIhYWIhYSIAYAABhYE9d0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpQ==\",\n        expectedOutput: \"<video src='data:video/webm;base64,GkXfo6NChoEBQveBAULygQRC84EIQoKEd2VibeyCAABCh4EBQoWBARhTgGcQIQmHEU2bdLtNu4tTq4QVSalmU6yBQE27i1OrhBZUrmtTrIGsTbuNU6uEEU2bdFOsgyEJc027jFOrhBxTu2tTrIINQRVJqWbnc6SQRsadRaGFqSlNPQovdQBWvSrXsYMPQkBEiYRG/cAARGGIBBu7mlIesABNgKVodHRwOi8vc291cmNlZm9yZ2UubmV0L3Byb2plY3RzL3lhbWthV0GQU29yZW5zb24gU3F1ZWV6ZRZUrmtMj66414EBc8WHiBmgyaYxwoOBASPjg4QCYloAIzFPhD+AAACGhVZfVlA4JYaIg1ZQOOCIsIICgLqCAWiuTFLXgQJzxYgBiP65XI76uoOBAiMxT4Q/gAAAhohBX1ZPUkJJU2OiTBkCHjoBdm9yYmlzAAAAAAFErAAA/////wD6AAD/////uAEDdm9yYmlzKgAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAwMzI1IChFdmVyeXdoZXJlKQAAAAABBXZvcmJpcx9CQ1YBAAABABhjVClGmVLSSokZc5QxRplikkqJpYQWQkidcxRTqTnXnGusubUghBAaU1ApBZlSjlJpGWOQKQWZUhBLSSV0EjonnWMQW0nB1phri0G2HIQNmlJMKcSUUopCCBlTjCnFlFJKQgcldA465hxTjkooQbicc6u1lpZji6l0kkrnJGRMQkgphZJKB6VTTkJINZbWUikdc1JSakHoIIQQQrYghA2C0JBVAAABAMBAEBqyCgBQAAAQiqEYigKEhqwCADIAAASgKI7iKI4jOZJjSRYQGrIKAAACABAAAMBwFEmRFMmxJEvSLEvTRFFVfdU2VVX2dV3XdV3XdSA0ZBUAAAEAQEinmaUaIMIMZBgIDVkFACAAAABGKMIQA0JDVgEAAAEAAGIoOYgmtOZ8c46DZjloKsXmdHAi1eZJbirm5pxzzjknm3PGOOecc4pyZjFoJrTmnHMSg2YpaCa05pxznsTmQWuqtOacc8Y5p4NxRhjnnHOatOZBajbW5pxzFrSmOWouxeaccyLl5kltLtXmnHPOOeecc84555xzqhenc3BOOOecc6L25lpuQhfnnHM+Gad7c0I455xzzjnnnHPOOeecc4LQkFUAABAAAEEYNoZxpyBIn6OBGEWIacikB92jwyRoDHIKqUejo5FS6iCUVMZJKZ0gNGQVAAAIAAAhhBRSSCGFFFJIIYUUUoghhhhiyCmnnIIKKqmkoooyyiyzzDLLLLPMMuuws8467DDEEEMMrbQSS0211VhjrbnnnGsO0lpprbXWSimllFJKKQgNWQUAgAAAEAgZZJBBRiGFFFKIIaaccsopqKACQkNWAQCAAAACAAAAPMlzREd0REd0REd0REd0RMdzPEeUREmUREm0TMvUTE8VVdWVXVvWZd32bWEXdt33dd/3dePXhWFZlmVZlmVZlmVZlmVZlmVZgtCQVQAACAAAgBBCCCGFFFJIIaUYY8wx56CTUEIgNGQVAAAIACAAAADAURzFcSRHciTJkixJkzRLszzN0zxN9ERRFE3TVEVXdEXdtEXZlE3XdE3ZdFVZtV1Ztm3Z1m1flm3f933f933f933f933f93UdCA1ZBQBIAADoSI6kSIqkSI7jOJIkAaEhqwAAGQAAAQAoiqM4juNIkiRJlqRJnuVZomZqpmd6qqgCoSGrAABAAAABAAAAAAAomuIppuIpouI5oiNKomVaoqZqriibsuu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6LhAasgoAkAAA0JEcyZEcSZEUSZEcyQFCQ1YBADIAAAIAcAzHkBTJsSxL0zzN0zxN9ERP9ExPFV3RBUJDVgEAgAAAAgAAAAAAMCTDUixHczRJlFRLtVRNtVRLFVVPVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVNU3TNE0gNGQlAAAEAMBijcHlICElJeXeEMIQk54xJiG1XiEEkZLeMQYVg54yogxy3kLjEIMeCA1ZEQBEAQAAxiDHEHPIOUepkxI556h0lBrnHKWOUmcpxZhizSiV2FKsjXOOUketo5RiLC12lFKNqcYCAAACHAAAAiyEQkNWBABRAACEMUgppBRijDmnnEOMKeeYc4Yx5hxzjjnnoHRSKuecdE5KxBhzjjmnnHNSOieVc05KJ6EAAIAABwCAAAuh0JAVAUCcAIBBkjxP8jRRlDRPFEVTdF1RNF3X8jzV9ExTVT3RVFVTVW3ZVFVZljzPND3TVFXPNFXVVFVZNlVVlkVV1W3TdXXbdFXdlm3b911bFnZRVW3dVF3bN1XX9l3Z9n1Z1nVj8jxV9UzTdT3TdGXVdW1bdV1d90xTlk3XlWXTdW3blWVdd2XZ9zXTdF3TVWXZdF3ZdmVXt11Z9n3TdYXflWVfV2VZGHZd94Vb15XldF3dV2VXN1ZZ9n1b14Xh1nVhmTxPVT3TdF3PNF1XdV1fV13X1jXTlGXTdW3ZVF1ZdmXZ911X1nXPNGXZdF3bNl1Xll1Z9n1XlnXddF1fV2VZ+FVX9nVZ15Xh1m3hN13X91VZ9oVXlnXh1nVhuXVdGD5V9X1TdoXhdGXf14XfWW5dOJbRdX1hlW3hWGVZOX7hWJbd95VldF1fWG3ZGFZZFoZf+J3l9n3jeHVdGW7d58y67wzH76T7ytPVbWOZfd1ZZl93juEYOr/w46mqr5uuKwynLAu/7evGs/u+soyu6/uqLAu/KtvCseu+8/y+sCyj7PrCasvCsNq2Mdy+biy/cBzLa+vKMeu+UbZ1fF94CsPzdHVdeWZdx/Z1dONHOH7KAACAAQcAgAATykChISsCgDgBAI8kiaJkWaIoWZYoiqbouqJouq6kaaapaZ5pWppnmqZpqrIpmq4saZppWp5mmpqnmaZomq5rmqasiqYpy6ZqyrJpmrLsurJtu65s26JpyrJpmrJsmqYsu7Kr267s6rqkWaapeZ5pap5nmqZqyrJpmq6reZ5qep5oqp4oqqpqqqqtqqosW55nmproqaYniqpqqqatmqoqy6aq2rJpqrZsqqptu6rs+rJt67ppqrJtqqYtm6pq267s6rIs27ovaZppap5nmprnmaZpmrJsmqorW56nmp4oqqrmiaZqqqosm6aqypbnmaoniqrqiZ5rmqoqy6Zq2qppmrZsqqotm6Yqy65t+77ryrJuqqpsm6pq66ZqyrJsy77vyqruiqYpy6aq2rJpqrIt27Lvy7Ks+6JpyrJpqrJtqqouy7JtG7Ns+7pomrJtqqYtm6oq27It+7os27rvyq5vq6qs67It+7ru+q5w67owvLJs+6qs+ror27pv6zLb9n1E05RlUzVt21RVWXZl2fZl2/Z90TRtW1VVWzZN1bZlWfZ9WbZtYTRN2TZVVdZN1bRtWZZtYbZl4XZl2bdlW/Z115V1X9d949dl3ea6su3Lsq37qqv6tu77wnDrrvAKAAAYcAAACDChDBQashIAiAIAAIxhjDEIjVLOOQehUco55yBkzkEIIZXMOQghlJI5B6GUlDLnIJSSUgihlJRaCyGUlFJrBQAAFDgAAATYoCmxOEChISsBgFQAAIPjWJbnmaJq2rJjSZ4niqqpqrbtSJbniaJpqqptW54niqapqq7r65rniaJpqqrr6rpomqapqq7ruroumqKpqqrrurKum6aqqq4ru7Ls66aqqqrryq4s+8Kquq4ry7Jt68Kwqq7ryrJs27Zv3Lqu677v+8KRreu6LvzCMQxHAQDgCQ4AQAU2rI5wUjQWWGjISgAgAwCAMAYhgxBCBiGEkFJKIaWUEgAAMOAAABBgQhkoNGQlABADAAAQASGDEEIIIYQQQgghhBBCCCGEEELnnHPOOeecc84JANiPcACQejAxMYWFhqwEAFIBAABjlFKKMecgRIw5xhh0EkqKGHOOMQelpFQ5ByGEVFrLrXIOQggptVRb5pyU1mKMOcbMOSkpxVZzzqGU1GKsueaaOymt1ZprzbmW1mrNNedccy6txZprzjXn3HLMNeecc845xpxzzjnnnHMBADgNDgCgBzasjnBSNBZYaMhKACAVAIBARinGnHMOOoQUY845ByGESCHGnHMOQggVY845Bx2EECrGHHMOQgghZM45ByGEEELInIMOOgghhNBBByGEEEIopXMQQgghhBJKCCGEEEIIIYQOQgghhBBCCCGEEEIIoZQSQgghhFBCKCUUAABY4AAAEGDD6ggnRWOBhYasBACAAAAghyWolDNhkGPQY0OQctRMgxBTTnSmmJPaTMUUZA5EJ51EhlpQtpfMAgAAIAgACDABBAYICr4QAmIMAEAQIjNEQmEVLDAogwaHeQDwABEhEQAkJijSLi6gywAXdHHXgRCCEIQgFgdQQAIOTrjhiTc84QYn6BSVOggAAAAAAAMAeAAAOCiAiIjmKiwuMDI0Njg6PAIAAAAAAAYAPgAAjg8gIqK5CosLjAyNDY4OjwAAAAAAAAAAACAgIAAAAAAAEAAAACAgJYaIhlZvcmJpc+GGtYRHLEQAHFO7a0IAu4yzgQC3h/eBAfGCD0e7kbOCAli3i/eBAfGCD0dTeIEqu5Gzgg3At4v3gQHxgg9HU3iB8buSs4Ib0LeM94EB8YIPR1N4ggHmu5KzgiN4t4z3gQHxgg9HU3iCAnW7krOCK8C3jPeBAfGCD0dTeIIDBruSs4IzkLeM94EB8YIPR1N4ggOXu5KzgjY4t4z3gQHxgg9HU3iCA8W7krOCOpi3jPeBAfGCD0dTeIIEIbuSs4I+gLeM94EB8YIPR1N4ggRlu5KzgkDYt4z3gQHxgg9HU3iCBI67krOCSNC3jPeBAfGCD0dTeIIFIbuSs4JJwLeM94EB8YIPR1N4ggU2u5Kzgk3Qt4z3gQHxgg9HU3iCBYO7krOCUZC3jPeBAfGCD0dTeIIFxLuSs4JR4LeM94EB8YIPR1N4ggXKu5KzglXwt4z3gQHxgg9HU3iCBhe7krOCWYi3jPeBAfGCD0dTeIIGXLuSs4JhWLeM94EB8YIPR1N4ggblu5KzgmWQt4z3gQHxgg9HU3iCBy67krOCaDi3jPeBAfGCD0dTeIIHXLuSs4JosLeM94EB8YIPR1N4ggdku5KzgnUIt4z3gQHxgg9HU3iCCEK7krOCddC3jPeBAfGCD0dTeIIIUruSs4J2ILeM94EB8YIPR1N4gghdu5Kzgn5At4z3gQHxgg9HU3iCCPgfQ7Z1ECD6JOeBAKeCD0ejQbOBAACAEjQAnQEqgAJoATkPAEEcIhYWIhYSIAYAABhYE9d0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpd0hkrLkkLy9LukMlZckheXpQ==' type='video/webm' controls><p>Unsupported media type.</p></video>\",\n        recipeConfig: [\n            { op: \"Play Media\", args: [\"Base64\"] }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Modhex.mjs",
    "content": "/**\n * Modhex operation tests.\n * @author linuxgemini [ilteris@asenkron.com.tr]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"ASCII to Modhex stream\",\n        input: \"aberystwyth\",\n        expectedOutput: \"hbhdhgidikieifiiikifhj\",\n        recipeConfig: [\n            {\n                \"op\": \"To Modhex\",\n                \"args\": [\n                    \"None\",\n                    0\n                ]\n            },\n        ]\n    },\n    {\n        name: \"ASCII to Modhex with colon deliminator\",\n        input: \"aberystwyth\",\n        expectedOutput: \"hb:hd:hg:id:ik:ie:if:ii:ik:if:hj\",\n        recipeConfig: [\n            {\n                \"op\": \"To Modhex\",\n                \"args\": [\n                    \"Colon\",\n                    0\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Modhex stream to UTF-8\",\n        input: \"uhkgkbuhkgkbugltlkugltkc\",\n        expectedOutput: \"救救孩子\",\n        recipeConfig: [\n            {\n                \"op\": \"From Modhex\",\n                \"args\": [\n                    \"Auto\"\n                ]\n            }\n        ]\n\n    },\n    {\n        name: \"Mixed case Modhex stream to UTF-8\",\n        input: \"uhKGkbUHkgkBUGltlkugltkc\",\n        expectedOutput: \"救救孩子\",\n        recipeConfig: [\n            {\n                \"op\": \"From Modhex\",\n                \"args\": [\n                    \"Auto\"\n                ]\n            }\n        ]\n\n    },\n    {\n        name: \"Mutiline Modhex with comma to ASCII (Auto Mode)\",\n        input: \"fk,dc,ie,hb,ii,dc,ht,ik,ie,hg,hr,hh,dc,ie,hk,\\n\\\nif,if,hk,hu,hi,dc,hk,hu,dc,if,hj,hg,dc,he,id,\\n\\\nhv,if,he,hj,dc,hv,hh,dc,if,hj,hg,dc,if,hj,hk,\\n\\\nie,dc,hh,hk,hi,dc,if,id,hg,hg,dr,dc,ie,if,hb,\\n\\\nid,ih,hk,hu,hi,dc,if,hv,dc,hf,hg,hb,if,hj,dr,\\n\\\ndc,hl,ig,ie,if,dc,hd,hg,he,hb,ig,ie,hg,dc,fk,\\n\\\ndc,he,hv,ig,hr,hf,hu,di,if,dc,ht,hb,hn,hg,dc,\\n\\\nig,ic,dc,ht,ik,dc,ht,hk,hu,hf,dc,ii,hj,hk,he,\\n\\\nhj,dc,hv,hh,dc,if,hj,hg,dc,hh,hk,hi,ie,dc,fk,\\n\\\ndc,ii,hv,ig,hr,hf,dc,he,hj,hv,hv,ie,hg,du\",\n        expectedOutput: \"I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Modhex\",\n                \"args\": [\n                    \"Auto\"\n                ]\n            }\n        ]\n\n    },\n    {\n        name: \"Mutiline Modhex with percent to ASCII (Percent Mode)\",\n        input: \"fk%dc%ie%hb%ii%dc%ht%ik%ie%hg%hr%hh%dc%ie%hk%\\n\\\nif%if%hk%hu%hi%dc%hk%hu%dc%if%hj%hg%dc%he%id%\\n\\\nhv%if%he%hj%dc%hv%hh%dc%if%hj%hg%dc%if%hj%hk%\\n\\\nie%dc%hh%hk%hi%dc%if%id%hg%hg%dr%dc%ie%if%hb%\\n\\\nid%ih%hk%hu%hi%dc%if%hv%dc%hf%hg%hb%if%hj%dr%\\n\\\ndc%hl%ig%ie%if%dc%hd%hg%he%hb%ig%ie%hg%dc%fk%\\n\\\ndc%he%hv%ig%hr%hf%hu%di%if%dc%ht%hb%hn%hg%dc%\\n\\\nig%ic%dc%ht%ik%dc%ht%hk%hu%hf%dc%ii%hj%hk%he%\\n\\\nhj%dc%hv%hh%dc%if%hj%hg%dc%hh%hk%hi%ie%dc%fk%\\n\\\ndc%ii%hv%ig%hr%hf%dc%he%hj%hv%hv%ie%hg%du\",\n        expectedOutput: \"I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Modhex\",\n                \"args\": [\n                    \"Percent\"\n                ]\n            }\n        ]\n\n    },\n    {\n        name: \"Mutiline Modhex with semicolon to ASCII (Semi-colon Mode)\",\n        input: \"fk;dc;ie;hb;ii;dc;ht;ik;ie;hg;hr;hh;dc;ie;hk;\\n\\\nif;if;hk;hu;hi;dc;hk;hu;dc;if;hj;hg;dc;he;id;\\n\\\nhv;if;he;hj;dc;hv;hh;dc;if;hj;hg;dc;if;hj;hk;\\n\\\nie;dc;hh;hk;hi;dc;if;id;hg;hg;dr;dc;ie;if;hb;\\n\\\nid;ih;hk;hu;hi;dc;if;hv;dc;hf;hg;hb;if;hj;dr;\\n\\\ndc;hl;ig;ie;if;dc;hd;hg;he;hb;ig;ie;hg;dc;fk;\\n\\\ndc;he;hv;ig;hr;hf;hu;di;if;dc;ht;hb;hn;hg;dc;\\n\\\nig;ic;dc;ht;ik;dc;ht;hk;hu;hf;dc;ii;hj;hk;he;\\n\\\nhj;dc;hv;hh;dc;if;hj;hg;dc;hh;hk;hi;ie;dc;fk;\\n\\\ndc;ii;hv;ig;hr;hf;dc;he;hj;hv;hv;ie;hg;du\",\n        expectedOutput: \"I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.\",\n        recipeConfig: [\n            {\n                \"op\": \"From Modhex\",\n                \"args\": [\n                    \"Semi-colon\"\n                ]\n            }\n        ]\n\n    },\n    {\n        name: \"ASCII to Modhex with comma and line breaks\",\n        input: \"aberystwyth\",\n        expectedOutput: \"hb,hd,hg,id,\\nik,ie,if,ii,\\nik,if,hj\",\n        recipeConfig: [\n            {\n                \"op\": \"To Modhex\",\n                \"args\": [\n                    \"Comma\",\n                    4\n                ]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/MorseCode.mjs",
    "content": "/**\n * Base58 tests.\n *\n * @author tlwr [toby@toby.codes]\n *\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"To Morse Code: 'SOS'\",\n        input: \"SOS\",\n        expectedOutput: \"... --- ...\",\n        recipeConfig: [\n            {\n                op: \"To Morse Code\",\n                args: [\"-/.\", \"Space\", \"Line feed\"],\n            },\n        ],\n    },\n    {\n        name: \"From Morse Code '... --- ...'\",\n        input: \"... --- ...\",\n        expectedOutput: \"SOS\",\n        recipeConfig: [\n            {\n                op: \"From Morse Code\",\n                args: [\"Space\", \"Line feed\"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/MultipleBombe.mjs",
    "content": "/**\n * Bombe machine tests.\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Multi-Bombe: 3 rotor\",\n        input: \"BBYFLTHHYIJQAYBBYS\",\n        expectedMatch: /<td>LGA<\\/td> {2}<td>SS<\\/td> {2}<td>VFISUSGTKSTMPSUNAK<\\/td>/,\n        recipeConfig: [\n            {\n                \"op\": \"Multiple Bombe\",\n                \"args\": [\n                    // I, II and III\n                    \"User defined\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\\nAJDKSIRUXBLHWTMCQGZNPYFVOE<F\\nBDFHJLCPRTXVZNYEIWGAKMUSQO<W\",\n                    \"\",\n                    \"AY BR CU DH EQ FS GL IP JX KN MO TZ VW\", // B\n                    \"THISISATESTMESSAGE\", 0, false\n                ]\n            }\n        ]\n    },\n    /*\n     * This is too slow to run regularly\n    {\n        name: \"Multi-Bombe: 4 rotor\",\n        input: \"LUOXGJSHGEDSRDOQQX\",\n        expectedMatch: /<td>LHSC<\\/td><td>SS<\\/td><td>HHHSSSGQUUQPKSEKWK<\\/td>/,\n        recipeConfig: [\n            {\n                \"op\": \"Multiple Bombe\",\n                \"args\": [\n                    // I, II and III\n                    \"User defined\",\n                    \"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R\\nAJDKSIRUXBLHWTMCQGZNPYFVOE<F\\nBDFHJLCPRTXVZNYEIWGAKMUSQO<W\",\n                    \"LEYJVCNIXWPBQMDRTAKZGFUHOS\", // Beta\n                    \"AE BN CK DQ FU GY HW IJ LO MP RX SZ TV\", // B thin\n                    \"THISISATESTMESSAGE\", 0, false\n                ]\n            }\n        ]\n    },\n    */\n]);\n"
  },
  {
    "path": "tests/operations/tests/MurmurHash3.mjs",
    "content": "/**\n * MurmurHash3 tests\n * @author AliceGrey [alice@grey.systems]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"To MurmurHash3: nothing\",\n        input: \"\",\n        expectedOutput: \"0\",\n        recipeConfig: [\n            {\n                op: \"MurmurHash3\",\n                args: [0],\n            },\n        ],\n    },\n    {\n        name: \"To MurmurHash3: 1\",\n        input: \"1\",\n        expectedOutput: \"2484513939\",\n        recipeConfig: [\n            {\n                op: \"MurmurHash3\",\n                args: [0],\n            },\n        ],\n    },\n    {\n        name: \"To MurmurHash3: Hello World!\",\n        input: \"Hello World!\",\n        expectedOutput: \"3691591037\",\n        recipeConfig: [\n            {\n                op: \"MurmurHash3\",\n                args: [0],\n            },\n        ],\n    },\n    {\n        name: \"To MurmurHash3: Hello World! with seed\",\n        input: \"Hello World!\",\n        expectedOutput: \"1148600031\",\n        recipeConfig: [\n            {\n                op: \"MurmurHash3\",\n                args: [1337],\n            },\n        ],\n    },\n    {\n        name: \"To MurmurHash3: foo\",\n        input: \"foo\",\n        expectedOutput: \"4138058784\",\n        recipeConfig: [\n            {\n                op: \"MurmurHash3\",\n                args: [0],\n            },\n        ],\n    },\n    {\n        name: \"To MurmurHash3: foo signed\",\n        input: \"foo\",\n        expectedOutput: \"-156908512\",\n        recipeConfig: [\n            {\n                op: \"MurmurHash3\",\n                args: [0, true],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/NTLM.mjs",
    "content": "/**\n * NTLM test.\n *\n * @author brun0ne [brunonblok@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"NT Hash\",\n        input: \"QWERTYUIOPASDFGHJKLZXCVBNM1234567890!@#$%^&*()_+.,?/\",\n        expectedOutput: \"C5FA1C40E55734A8E528DBFE21766D23\",\n        recipeConfig: [\n            {\n                op: \"NT Hash\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"LM Hash\",\n        input: \"QWERTYUIOPASDFGHJKLZXCVBNM1234567890!@#$%^&*()_+.,?/\",\n        expectedOutput: \"6D9DF16655336CA75A3C13DD18BA8156\",\n        recipeConfig: [\n            {\n                op: \"LM Hash\",\n                args: [],\n            },\n        ],\n    },\n\n]);\n"
  },
  {
    "path": "tests/operations/tests/NetBIOS.mjs",
    "content": "/**\n * NetBIOS tests.\n *\n * @author bwhitn [brian.m.whitney@outlook.com]\n *\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Encode NetBIOS name\",\n        input: \"The NetBIOS name\",\n        expectedOutput: \"FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF\",\n        recipeConfig: [\n            {\n                op: \"Encode NetBIOS Name\",\n                args: [65],\n            },\n        ],\n    },\n    {\n        name: \"Decode NetBIOS Name\",\n        input: \"FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF\",\n        expectedOutput: \"The NetBIOS name\",\n        recipeConfig: [\n            {\n                op: \"Decode NetBIOS Name\",\n                args: [65],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/NormaliseUnicode.mjs",
    "content": "/**\n * Text Encoding Brute Force tests.\n *\n * @author Matthieu [m@tthieux.xyz]\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Normalise Unicode - NFD\",\n        input: \"\\u00c7\\u0043\\u0327\\u2160\",\n        expectedMatch: /C\\u0327C\\u0327\\u2160/,\n        recipeConfig: [\n            {\n                op: \"Normalise Unicode\",\n                args: [\"NFD\"],\n            },\n        ],\n    }, {\n        name: \"Normalise Unicode - NFC\",\n        input: \"\\u00c7\\u0043\\u0327\\u2160\",\n        expectedMatch: /\\u00C7\\u00C7\\u2160/,\n        recipeConfig: [\n            {\n                op: \"Normalise Unicode\",\n                args: [\"NFC\"],\n            },\n        ],\n    }, {\n        name: \"Normalise Unicode - NFKD\",\n        input: \"\\u00c7\\u0043\\u0327\\u2160\",\n        expectedMatch: /C\\u0327C\\u0327I/,\n        recipeConfig: [\n            {\n                op: \"Normalise Unicode\",\n                args: [\"NFKD\"],\n            },\n        ],\n    }, {\n        name: \"Normalise Unicode - NFKC\",\n        input: \"\\u00c7\\u0043\\u0327\\u2160\",\n        expectedMatch: /\\u00C7\\u00C7I/,\n        recipeConfig: [\n            {\n                op: \"Normalise Unicode\",\n                args: [\"NFKC\"],\n            },\n        ],\n    },\n]);\n\n"
  },
  {
    "path": "tests/operations/tests/OTP.mjs",
    "content": "/**\n * OTP HOTP tests.\n *\n * @author bwhitn [brian.m.whitney@outlook.com]\n *\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Generate HOTP\",\n        input: \"JBSWY3DPEHPK3PXP\",\n        expectedOutput: `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0\\n\\nPassword: 282760`,\n        recipeConfig: [\n            {\n                op: \"Generate HOTP\",\n                args: [\"\", 6, 0], // [Name, Code length, Counter]\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/PEMtoHex.mjs",
    "content": "/**\n * Test PEMtoHex with different inputs\n *\n * @author cplussharp\n *\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n/** RSA 2048bit key pair as PKCS1 and PKCS8 and as certificate */\nconst PEMS_RSA_PRIVATE_KEY_PKCS1 = `-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA5WykLKHiBAhmZh5WhocgpQQqZjdrApuRxRT21SJZx6Ce+Oz2\nV17/heozu5LEz63jCxW1NrBckzl/Ys8p9LeqYTu6x/LbKloTjfEWxlzXnzUSqn9J\nHIxmmJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDhe2KnMGVDDg6kfCLokDdLo256LeQ4\nCEkViwY6d+at4xDlIHwvZZmG4Smk56eHhvQE3I8sSAzgoLMBamQ5m3MbiULAYtxs\nkCpCfjFxrL6Ziaaj7HZoneF40R30KCI9ygF8vkzxLwe3t5Y4XgHL9TYQm1+BDnin\nupIB/zTeO1ygBGA66m6zpmkmuG7d8HXIducz+wIDAQABAoIBACQu3jWr0lmQeZXh\ncwQEi8F6xrUYSGhA4NyUUdmLcV1ql6fqt29QLDySk1Yh76hRZF17LvlRF0ig6NZM\nlfFihhyPrwWZ57bmPe9E9rKSMe+KD0eUi5NVEVk/BmJpzxwZSfRC6NTDz8Zjp7po\nFUwGkYlEJdocHlc5N/fcCZG1Jti/Z1AsZIjO6r6S0O7neC7icECBKHUyWa8/yE1N\n++TVyMV+z53Ad1PC+SHMGDlbqJAM3o4wAzD/FAIzyVo6GSnnC+bFdgMtIwtwgYTH\nrbr8M8j5fqAHTqNJeblqt/5KHEj2VVsIsHIuQ6lv4llESEqmH+N5KE4O33U7/Wmj\ny/+VGAECgYEA9ysROHXOx1ofh3tI/r2C2FUan6/AAe4dc+8E4jMGI5T4xeqYHTRV\nl1yS+ZKIxqclIoAmd6SJ7Nx2GdQ55MmokZdZRqsFN1flFOZw2kFH/I0zQZXdOjF+\nsf5Lu0FfcTw3VJhJ/YU3CVdlgdP4ekHbaJVFW5i/pTUf5vNs6AGBGxsCgYEA7Z9D\n0qnF6GhxA8lJfwnyuktYnwIQoXE6Xp26/NZD7t7HzxHDf43LQxmTk+mDP/yIQHwb\nxIx2NE/ncNxlUMl/g1PkJbKVkB8tdIZrLyiT4lebeqgT72Q07IsxRl/GHOr7CfN0\n61OBRCe44IlOtaNAZk4zWwuAwAYx+G8ifuOJ+KECgYBP5NvsJChyx+7pHDC8JwXk\nZ53zgBvQg+eBUgGCHHwfhEflsa75wbDo/EOF6JfNnrmiLUpB4i2zIpAKSU9tZMHY\nTdPNw/orqX2jA9n2sqNSP1ISIR8hcF5Dqq9QGBGByLUZ4yAHksf3fQiSrrHi0ubZ\nJ2cD9Jv+Cu4E+Sp61AGngQKBgHmuTPTbq1TP5s+hi9laJsnvO3pxfEKv0MwSyWYf\n8rmnq3oGBq6S1buOpVvhAC0MDFm5NB76Lq2rHUFWGyu7g2ik1PfY823SCVzaWJjV\nlqUZZ6zv1QWJsvBOdvUqpjC4w8TcvsqjAFb+YFXa+ktZRekdsn607UFn6r7laizA\nKC8BAoGAZty7sIGMt1gDaoIjySgOox8x7AlY3QUyNQC5N8KW3MZ8KLC5UBtjhqLy\nwYOJr+/1R/7ibiHrKkIE/dmg5QN1iS5tZmFvyLwJ+nHQZObFrlcKtpr+1lekQY/m\nly6YJFk3yj2nhYzt8eVXBX2lCoLG1gsrbpXvUfIGJ53L9m1mVAo=\n-----END RSA PRIVATE KEY-----`;\n\nconst PEMS_RSA_PRIVATE_KEY_PKCS8 = `-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDlbKQsoeIECGZm\nHlaGhyClBCpmN2sCm5HFFPbVIlnHoJ747PZXXv+F6jO7ksTPreMLFbU2sFyTOX9i\nzyn0t6phO7rH8tsqWhON8RbGXNefNRKqf0kcjGaYlDONen1fUP31OP41alEYQiII\nUTsl8OF7YqcwZUMODqR8IuiQN0ujbnot5DgISRWLBjp35q3jEOUgfC9lmYbhKaTn\np4eG9ATcjyxIDOCgswFqZDmbcxuJQsBi3GyQKkJ+MXGsvpmJpqPsdmid4XjRHfQo\nIj3KAXy+TPEvB7e3ljheAcv1NhCbX4EOeKe6kgH/NN47XKAEYDrqbrOmaSa4bt3w\ndch25zP7AgMBAAECggEAJC7eNavSWZB5leFzBASLwXrGtRhIaEDg3JRR2YtxXWqX\np+q3b1AsPJKTViHvqFFkXXsu+VEXSKDo1kyV8WKGHI+vBZnntuY970T2spIx74oP\nR5SLk1URWT8GYmnPHBlJ9ELo1MPPxmOnumgVTAaRiUQl2hweVzk399wJkbUm2L9n\nUCxkiM7qvpLQ7ud4LuJwQIEodTJZrz/ITU375NXIxX7PncB3U8L5IcwYOVuokAze\njjADMP8UAjPJWjoZKecL5sV2Ay0jC3CBhMetuvwzyPl+oAdOo0l5uWq3/kocSPZV\nWwiwci5DqW/iWURISqYf43koTg7fdTv9aaPL/5UYAQKBgQD3KxE4dc7HWh+He0j+\nvYLYVRqfr8AB7h1z7wTiMwYjlPjF6pgdNFWXXJL5kojGpyUigCZ3pIns3HYZ1Dnk\nyaiRl1lGqwU3V+UU5nDaQUf8jTNBld06MX6x/ku7QV9xPDdUmEn9hTcJV2WB0/h6\nQdtolUVbmL+lNR/m82zoAYEbGwKBgQDtn0PSqcXoaHEDyUl/CfK6S1ifAhChcTpe\nnbr81kPu3sfPEcN/jctDGZOT6YM//IhAfBvEjHY0T+dw3GVQyX+DU+QlspWQHy10\nhmsvKJPiV5t6qBPvZDTsizFGX8Yc6vsJ83TrU4FEJ7jgiU61o0BmTjNbC4DABjH4\nbyJ+44n4oQKBgE/k2+wkKHLH7ukcMLwnBeRnnfOAG9CD54FSAYIcfB+ER+WxrvnB\nsOj8Q4Xol82euaItSkHiLbMikApJT21kwdhN083D+iupfaMD2fayo1I/UhIhHyFw\nXkOqr1AYEYHItRnjIAeSx/d9CJKuseLS5tknZwP0m/4K7gT5KnrUAaeBAoGAea5M\n9NurVM/mz6GL2Vomye87enF8Qq/QzBLJZh/yuaeregYGrpLVu46lW+EALQwMWbk0\nHvourasdQVYbK7uDaKTU99jzbdIJXNpYmNWWpRlnrO/VBYmy8E529SqmMLjDxNy+\nyqMAVv5gVdr6S1lF6R2yfrTtQWfqvuVqLMAoLwECgYBm3LuwgYy3WANqgiPJKA6j\nHzHsCVjdBTI1ALk3wpbcxnwosLlQG2OGovLBg4mv7/VH/uJuIesqQgT92aDlA3WJ\nLm1mYW/IvAn6cdBk5sWuVwq2mv7WV6RBj+aXLpgkWTfKPaeFjO3x5VcFfaUKgsbW\nCytule9R8gYnncv2bWZUCg==\n-----END PRIVATE KEY-----`;\n\nconst PEMS_RSA_PUBLIC_KEY_PKCS1 = `-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA5WykLKHiBAhmZh5WhocgpQQqZjdrApuRxRT21SJZx6Ce+Oz2V17/\nheozu5LEz63jCxW1NrBckzl/Ys8p9LeqYTu6x/LbKloTjfEWxlzXnzUSqn9JHIxm\nmJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDhe2KnMGVDDg6kfCLokDdLo256LeQ4CEkV\niwY6d+at4xDlIHwvZZmG4Smk56eHhvQE3I8sSAzgoLMBamQ5m3MbiULAYtxskCpC\nfjFxrL6Ziaaj7HZoneF40R30KCI9ygF8vkzxLwe3t5Y4XgHL9TYQm1+BDninupIB\n/zTeO1ygBGA66m6zpmkmuG7d8HXIducz+wIDAQAB\n-----END RSA PUBLIC KEY-----`;\n\nconst PEMS_RSA_PUBLIC_KEY_PKCS8 = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5WykLKHiBAhmZh5Whocg\npQQqZjdrApuRxRT21SJZx6Ce+Oz2V17/heozu5LEz63jCxW1NrBckzl/Ys8p9Leq\nYTu6x/LbKloTjfEWxlzXnzUSqn9JHIxmmJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDh\ne2KnMGVDDg6kfCLokDdLo256LeQ4CEkViwY6d+at4xDlIHwvZZmG4Smk56eHhvQE\n3I8sSAzgoLMBamQ5m3MbiULAYtxskCpCfjFxrL6Ziaaj7HZoneF40R30KCI9ygF8\nvkzxLwe3t5Y4XgHL9TYQm1+BDninupIB/zTeO1ygBGA66m6zpmkmuG7d8HXIducz\n+wIDAQAB\n-----END PUBLIC KEY-----`;\n\nconst PEMS_RSA_CERT = `-----BEGIN CERTIFICATE-----\nMIIDGzCCAgOgAwIBAgIUROs52CB3BsvEVLOCtALalnJG8tEwDQYJKoZIhvcNAQEL\nBQAwHTEbMBkGA1UEAwwSUlNBIDIwNDggUHVibGljS2V5MB4XDTIxMDQxMzIxMDE0\nOVoXDTMxMDQxMTIxMDE0OVowHTEbMBkGA1UEAwwSUlNBIDIwNDggUHVibGljS2V5\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5WykLKHiBAhmZh5Whocg\npQQqZjdrApuRxRT21SJZx6Ce+Oz2V17/heozu5LEz63jCxW1NrBckzl/Ys8p9Leq\nYTu6x/LbKloTjfEWxlzXnzUSqn9JHIxmmJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDh\ne2KnMGVDDg6kfCLokDdLo256LeQ4CEkViwY6d+at4xDlIHwvZZmG4Smk56eHhvQE\n3I8sSAzgoLMBamQ5m3MbiULAYtxskCpCfjFxrL6Ziaaj7HZoneF40R30KCI9ygF8\nvkzxLwe3t5Y4XgHL9TYQm1+BDninupIB/zTeO1ygBGA66m6zpmkmuG7d8HXIducz\n+wIDAQABo1MwUTAdBgNVHQ4EFgQUcRhRB6H5JqlDHbymwqydW2/EAt8wHwYDVR0j\nBBgwFoAUcRhRB6H5JqlDHbymwqydW2/EAt8wDwYDVR0TAQH/BAUwAwEB/zANBgkq\nhkiG9w0BAQsFAAOCAQEALXBmDizTp/Uz4M2A4nCl0AVclrXEk+YjAKqZnvtj44Gs\nCUcpxtcXu64ppsSYCwawvzIm6B2Mdmib422aInH0e0oNrn8cRzC144Hjnzxguamj\nLyZXnH/0wN9SAjqCKt++urH9wbRMIl0v+g4CWjGyY+eYkMmd1UMQvdCCCv6RVm56\n7dBCijJIHg23JbgPJD72JCluXtTYWllv3duSwuWeYHo5EftU456pDcztkgn9XwFk\nPFGnHLmbjpSzjE7u29qCjwHl3CiUsjfUlYFl/mf27oDXPqaWqPYv3fWH3H3ymiZQ\ncqptUF4hDtPkaNkKWFmlljChN92o8g/jrv4DVDgJzQ==\n-----END CERTIFICATE-----`;\n\n/** EC P-256 key pair and certificate */\nconst PEMS_EC_P256_PRIVATE_KEY = `-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIhdQxQIcMnCHD3X4WqNv+VgycWmFoEZpRl9X0+dT9uHoAoGCCqGSM49\nAwEHoUQDQgAEFLQcBbzDweo6af4k3k0gKWMNWOZVn8+9hH2rv4DKKYZ7E1z64LBt\nPnB1gMz++HDKySr2ozD3/46dIbQMXUZKpw==\n-----END EC PRIVATE KEY-----`;\n\nconst PEMS_EC_P256_PRIVATE_KEY_PKCS8 = `-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiF1DFAhwycIcPdfh\nao2/5WDJxaYWgRmlGX1fT51P24ehRANCAAQUtBwFvMPB6jpp/iTeTSApYw1Y5lWf\nz72Efau/gMophnsTXPrgsG0+cHWAzP74cMrJKvajMPf/jp0htAxdRkqn\n-----END PRIVATE KEY-----`;\n\nconst PEMS_EC_P256_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFLQcBbzDweo6af4k3k0gKWMNWOZV\nn8+9hH2rv4DKKYZ7E1z64LBtPnB1gMz++HDKySr2ozD3/46dIbQMXUZKpw==\n-----END PUBLIC KEY-----`;\n\nconst PEMS_FOO = `-----BEGIN FOO-----\nRk9P\n-----END FOO-----`;\nconst PEMS_BAR = `-----BEGIN BAR-----\nQkFS\n-----END BAR-----`;\n\nTestRegister.addTests([\n    {\n        name: \"PEMtoHex: Nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: No footer\",\n        input: PEMS_RSA_PRIVATE_KEY_PKCS1.substring(0, 200),\n        expectedOutput: \"PEM footer '-----END RSA PRIVATE KEY-----' not found\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: Multiple PEMs\",\n        input: PEMS_FOO + \"\\n\" + PEMS_BAR,\n        expectedOutput: \"FOOBAR\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            },\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: Single line PEM\",\n        input: PEMS_FOO.replace(/(\\n|\\r)/gm, \"\"),\n        expectedOutput: \"FOO\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            },\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: EC P-256 Private Key\",\n        input: PEMS_EC_P256_PRIVATE_KEY,\n        expectedOutput: \"30770201010420885d43140870c9c21c3dd7e16a8dbfe560c9c5a6168119a5197d5f4f9d4fdb87a00a06082a8648ce3d030107a1440342000414b41c05bcc3c1ea3a69fe24de4d2029630d58e6559fcfbd847dabbf80ca29867b135cfae0b06d3e707580ccfef870cac92af6a330f7ff8e9d21b40c5d464aa7\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: EC P-256 Private Key PKCS8\",\n        input: PEMS_EC_P256_PRIVATE_KEY_PKCS8,\n        expectedOutput: \"308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420885d43140870c9c21c3dd7e16a8dbfe560c9c5a6168119a5197d5f4f9d4fdb87a1440342000414b41c05bcc3c1ea3a69fe24de4d2029630d58e6559fcfbd847dabbf80ca29867b135cfae0b06d3e707580ccfef870cac92af6a330f7ff8e9d21b40c5d464aa7\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: EC P-256 Public Key\",\n        input: PEMS_EC_P256_PUBLIC_KEY,\n        expectedOutput: \"3059301306072a8648ce3d020106082a8648ce3d0301070342000414b41c05bcc3c1ea3a69fe24de4d2029630d58e6559fcfbd847dabbf80ca29867b135cfae0b06d3e707580ccfef870cac92af6a330f7ff8e9d21b40c5d464aa7\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: RSA Private Key PKCS1\",\n        input: PEMS_RSA_PRIVATE_KEY_PKCS1,\n        expectedOutput: \"fb49bd96ffc5d1351a35d773921fac03\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            },\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: RSA Private Key PKCS8\",\n        input: PEMS_RSA_PRIVATE_KEY_PKCS8,\n        expectedOutput: \"23086d03633689fee64680c3c24409eb\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            },\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: RSA Public Key PKCS1\",\n        input: PEMS_RSA_PUBLIC_KEY_PKCS1,\n        expectedOutput: \"5fc3f1f6c5d5806760b12eaad0c0292c\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            },\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: RSA Public Key PKCS8\",\n        input: PEMS_RSA_PUBLIC_KEY_PKCS8,\n        expectedOutput: \"30fbe8e9495d591232affebdd6206ea6\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            },\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    },\n    {\n        name: \"PEMtoHex: Certificate\",\n        input: PEMS_RSA_CERT,\n        expectedOutput: \"6694d8ca4a0ceb84c3951d25dc05ec6e\",\n        recipeConfig: [\n            {\n                \"op\": \"PEM to Hex\",\n                \"args\": []\n            },\n            {\n                \"op\": \"MD5\",\n                \"args\": []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/PGP.mjs",
    "content": "/**\n * PGP tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport {ASCII_TEXT, UTF8_TEXT, ALL_BYTES} from \"../../samples/Ciphers.mjs\";\n\n// RSA-1024\nconst ALICE_PRIVATE = `-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: Keybase OpenPGP v2.0.77\nComment: https://keybase.io/crypto\n\nxcEYBFsNaYsBBAC9rnmjTzLLBCey2gq9un+XXP089sP3AONhSivdJlJEEWjt999o\ng8vM18TcEk1sxyItp/GLlE/T70NPFAvdVXKI0KDQZ9fm77JDKitl587npRaspOOX\nL1yUVFGVr4YEPDLoAT4PJgwI3TsEBfLGeOZqBqd/stw/FKjrNZJLRYfjnQARAQAB\nAAP9G3EGgAM74fXJQy1wUwqMMvsXrUjgs6IZQ0Cryo7PZVxExfNlCtsmZ42VGWbn\nH071OY22eu8LWCn2nut+MUM6EnjDZ7e/u85eHd0r5fY/3Vl0lQCy53RDdEQ3w8vA\nXcUSabxwqpubmtyC3jxIXmVH6rLLmSpGGX8IqHRTfNDwHTECAPsgeVy0qkT0kJq1\nAw8gthHO6c3m1NOcAPyTqSRLVspxRDB0LuYHnVxAN+dUHFYAfwPj+h+E6ROolqe4\nIKtrls8CAMFcwisDUQXFQFmO2pkpgaQTkN9XjGqBhjYd1EGs+WcYZb7eD98Ue0TZ\nGUF4UtzHUW5hIiCgkTrwpdpRqE3xudMCAJbhnzE+Mj7yKAHAV8LjZfpJA9hh1G8c\nATDpoWD1yAcLO1mMVkSExpMHoiuQ5ujzWyCHYnXDdRo6jkowP4JxIX+axs0AwrQE\nEwEKAB4FAlsNaYsCGy8DCwkHAxUKCAIeAQIXgAMWAgECGQEACgkQhCRlSN+Y5IXj\n5QP8CZns1zlWk7S37Dhvxe3K3EYVgefc+EDWsj3xvlo+QUKQMAmANFNnqYzt++mv\ncVhvGzyn+wa244fJb3xGYAi+G4Ya5pWQbXSzAVhjteHyLcjS6VZ/ydxDGCZK37Gc\nMYs/8x4kwdU+A8/bQhJ+nRVEJjkg0OcoH9rJv0kB+ilcC7LHwRgEWw1piwEEAOXO\nJib0QIvuqKAZiN3Yol3xC9Wz5aXQyg7qCnnYHHrPIMgYvGrTjjvDFCwM5uxCv38Q\nd6rJnzrRXTC1EiAia/b7f76Z0r4W+j6KdVCGpQnVQE6b//WdY5ys1xLAkNr4xwNj\n42nrOIMGB1qV0XezJ4VBOMpMHlwE2WR27HOQakXDABEBAAEAA/9Svw4BzMVJHaBe\nNZOQviaIyPjH9ETmle2LvT4UbXqjxd057544oQCACFhFHEgyHj6x1A4i0wKgvS5f\nEXP7WimhUEybo8YktbYX691QGPHNNQw5dc6IzLZmSm9p1zpuOs1VBHs6lpR5Y0WT\n/2vDrbY2Loa+Dojuvuq3hY1Bu5fjcQIA/SdK6T8sEYwdZTfCKEWdvMQ8zhjioNNn\n5enUNT/WQXw6qvkczD2U48PlIXpwfn4Rjh3sGEiumng334LTslXtvwIA6GOl6eFC\n337clY0Yyog7cTsEZTdCQBIScZi7grMuL9KFWx4UbfHiDS976MRu1ciATCTSCdc5\nxgLEUF51WrWw/QIApR/pGgDg/Ow32jS38VonCH6TpFFMk9KciKCMm7sRrG3J6kFK\nUxuxWXPs+pWXjTn6ItfrX6M8dZZkC2BBR9UyrqB5wsCDBBgBCgAPBQJbDWmLBQkP\nCZwAAhsuAKgJEIQkZUjfmOSFnSAEGQEKAAYFAlsNaYsACgkQPtlTQFIjCzrjvQQA\nhyGjZ2zDMxyXA3oEoD3RfjPQtAYFPJ5i0/ir/FD2nX7//cyd8zJS24P6S9+ID8vB\n0n+JwF6KrvjqpMneXXbPmi5OebqMogLahWmhCtjriDKrfJJiL0HmTKGl89p2Z59e\nBoLbm4Jpk1rL7EmoibsmUZdBUutf0tw5IusXd/B5sNwhdgQArBzyHVIFyN+fegTE\n9cR677M92NjYhqY8c+fF/AV+7XQv0Vsi9B//HeTMCml6jytxdSIZBl7uLrasIOd0\nFJk+VP7UrOfDaz6oVq+tarStAelfqT9DRQXw+nEdes4bxDsPvi1OieTtexJRO181\nzdsmOukv2RhgrJzFCcpzAkUYGqjHwRgEWw1piwEEALDKG2L6NNhTXZ3MJJLVtEPD\n65c9KmT8DagJXCp0cl3bQbcs+zLmsfYwnIKNTOxnhxAER+5e6jmW4K7sbLY593rO\niqDXXX8OR28t88IGjlIrVd+2t6+ma5ecSgsyVqBDFFTpAzg+QFWdk2VEOlA5zNfC\ndX94pLUMjPDAHSsZfrufABEBAAEAA/wIFwePzPFUIOR8zxWxXnQkUbfbMOJawqoB\nUYRVMQT2xIzKTBWmq6XjJTBUTREDFG26zudXwiInxn67ongLErX/Zhohq8lBYjjx\nQkQRtU+QSDsksdJJL0Lj/6SAkljkhXmO+jYmRVirQfGBVl33Dk9YWnd/VePO5epn\nnYPxEGT+MQIA6n9nHmzbz5ohSi6Ovn4OW0704K3kLhOQ3WP3j+5+bWXv0Xd89w7v\nZMyv0IJOvhOw1670BcBxbI/CSFD4Nz+k+QIAwQAP/M8TdG1Twwx6yED0syNvPDfN\n5hzZM8031zTBbguSRJskD18aBtwcUun93+dcilRY94gXbl4xSq3YitTDVwIAvX+P\n06nJmFdgeoanpcYIBA4hwi+LPyfPcGo6tfnsxk7ul1mBK27TR+AjnJ+HNFCh54CM\n4cLH1djyBmwEt30Wm6FjwsCDBBgBCgAPBQJbDWmLBQkDwmcAAhsuAKgJEIQkZUjf\nmOSFnSAEGQEKAAYFAlsNaYsACgkQW/Z2teklb4u5/gP/fnZ7ZuV+l4c2EQY5Xnk8\nS/lY3Xr9zoucjQwQWeRKwAQYoiovzxA4XV8gGyrdAsrIPUFLp7PmUBG4YJV/7sVo\nzzRwVq+jS8Jo0xYbGJMv2DuAnXXrYCZWZRqRscr1Wlc+CUACmxYZjC1DVVrAXr0j\nTqYTk+jjhemTTAtUelgMhcX82wP9EEU66hCYFUayjn4bBlR1yEvMpJd8JSTHR/dZ\nH8t3Ri6R2AYRqBxro0JEXDhL9iDnuPQVxsbgq2YlhHrPJI8opKuxV7wrXrupzwFf\nKixJMNwsAPk/nSc4qIZvXTi2fmyAZDJYUgsm6CwkxumaVvIdVNGRmxqGSRTuEInt\nW03Cwfs=\n=Mb52\n-----END PGP PRIVATE KEY BLOCK-----`;\n\nconst ALICE_PUBLIC = `-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: Keybase OpenPGP v2.0.77\nComment: https://keybase.io/crypto\n\nxo0EWw1piwEEAL2ueaNPMssEJ7LaCr26f5dc/Tz2w/cA42FKK90mUkQRaO3332iD\ny8zXxNwSTWzHIi2n8YuUT9PvQ08UC91VcojQoNBn1+bvskMqK2XnzuelFqyk45cv\nXJRUUZWvhgQ8MugBPg8mDAjdOwQF8sZ45moGp3+y3D8UqOs1kktFh+OdABEBAAHN\nAMK0BBMBCgAeBQJbDWmLAhsvAwsJBwMVCggCHgECF4ADFgIBAhkBAAoJEIQkZUjf\nmOSF4+UD/AmZ7Nc5VpO0t+w4b8XtytxGFYHn3PhA1rI98b5aPkFCkDAJgDRTZ6mM\n7fvpr3FYbxs8p/sGtuOHyW98RmAIvhuGGuaVkG10swFYY7Xh8i3I0ulWf8ncQxgm\nSt+xnDGLP/MeJMHVPgPP20ISfp0VRCY5INDnKB/ayb9JAfopXAuyzo0EWw1piwEE\nAOXOJib0QIvuqKAZiN3Yol3xC9Wz5aXQyg7qCnnYHHrPIMgYvGrTjjvDFCwM5uxC\nv38Qd6rJnzrRXTC1EiAia/b7f76Z0r4W+j6KdVCGpQnVQE6b//WdY5ys1xLAkNr4\nxwNj42nrOIMGB1qV0XezJ4VBOMpMHlwE2WR27HOQakXDABEBAAHCwIMEGAEKAA8F\nAlsNaYsFCQ8JnAACGy4AqAkQhCRlSN+Y5IWdIAQZAQoABgUCWw1piwAKCRA+2VNA\nUiMLOuO9BACHIaNnbMMzHJcDegSgPdF+M9C0BgU8nmLT+Kv8UPadfv/9zJ3zMlLb\ng/pL34gPy8HSf4nAXoqu+Oqkyd5dds+aLk55uoyiAtqFaaEK2OuIMqt8kmIvQeZM\noaXz2nZnn14GgtubgmmTWsvsSaiJuyZRl0FS61/S3Dki6xd38Hmw3CF2BACsHPId\nUgXI3596BMT1xHrvsz3Y2NiGpjxz58X8BX7tdC/RWyL0H/8d5MwKaXqPK3F1IhkG\nXu4utqwg53QUmT5U/tSs58NrPqhWr61qtK0B6V+pP0NFBfD6cR16zhvEOw++LU6J\n5O17ElE7XzXN2yY66S/ZGGCsnMUJynMCRRgaqM6NBFsNaYsBBACwyhti+jTYU12d\nzCSS1bRDw+uXPSpk/A2oCVwqdHJd20G3LPsy5rH2MJyCjUzsZ4cQBEfuXuo5luCu\n7Gy2Ofd6zoqg111/DkdvLfPCBo5SK1XftrevpmuXnEoLMlagQxRU6QM4PkBVnZNl\nRDpQOczXwnV/eKS1DIzwwB0rGX67nwARAQABwsCDBBgBCgAPBQJbDWmLBQkDwmcA\nAhsuAKgJEIQkZUjfmOSFnSAEGQEKAAYFAlsNaYsACgkQW/Z2teklb4u5/gP/fnZ7\nZuV+l4c2EQY5Xnk8S/lY3Xr9zoucjQwQWeRKwAQYoiovzxA4XV8gGyrdAsrIPUFL\np7PmUBG4YJV/7sVozzRwVq+jS8Jo0xYbGJMv2DuAnXXrYCZWZRqRscr1Wlc+CUAC\nmxYZjC1DVVrAXr0jTqYTk+jjhemTTAtUelgMhcX82wP9EEU66hCYFUayjn4bBlR1\nyEvMpJd8JSTHR/dZH8t3Ri6R2AYRqBxro0JEXDhL9iDnuPQVxsbgq2YlhHrPJI8o\npKuxV7wrXrupzwFfKixJMNwsAPk/nSc4qIZvXTi2fmyAZDJYUgsm6CwkxumaVvId\nVNGRmxqGSRTuEIntW03Cwfs=\n=PuGL\n-----END PGP PUBLIC KEY BLOCK-----`;\n\n// ECC-384\nconst BOB_PRIVATE = `-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: Keybase OpenPGP v2.0.77\nComment: https://keybase.io/crypto\n\nxcAABFsNafYBAYDHiv+tCi4267xI6iTmBrhOKdNbKLWIYMG1OoE1f9qpT+nAVKFR\nzUAFXKqQjqMDESkAEQEAAQABewd7cLkIQHGKly8PE+P0h7fV7X5bJqwZiqDwC8DU\n38vCUO/KtkZO3jEQYA1U9DsNDQDA73KCr3K1tSX1afeWzb8vVBY4ZzXocKb9AMDV\nVk17t1N4nClMfqpGIDELtYBMiiCDyJ0AwLsnQb9cE+g1MZETtNDYXXxilkO/4CP9\n8j4HzQDCZAQTAQoAHgUCWw1p9gIbLwMLCQcDFQoIAh4BAheAAxYCAQIZAQAKCRCE\ncIHWt/IPg+sqAYC6goCyOCYD/DytOW3I2cb12iDyFOSDsOx6lrmIgLyP0dDnbJHb\nS9ar68yuHeDqP7jHiARbDWn2AQEAwSE4qpbLQzSIUfwmfWXmHneAuQIkEYawRxK/\nH1JkGxcAEQEAAQAA/0pvbnK5OdBGMABBSehs3LrW/hWWIL0y/MfS7h/6gSJ5AID3\nYOgHLqEgM1Bo2TzvIjwlAIDH3E+0ynQFdLH96FPp47eLAH9e/NZ74e2N8sTMBoYO\n1sbcLp7CkwQYAQoADwUCWw1p9gUJDwmcAAIbLgBICRCEcIHWt/IPgz0gBBkBCgAG\nBQJbDWn2AAoJELU8cYHhYcru2lwBAL4OUK2fkhzh2VU3meXgAMWjoP6ryRUCTmSQ\nxuULvvCyfZMBfiHzV5QLgXSUVUA7Og6mlH5pw2gtgsZhijwwywkzF3tQ+s++hOZR\n161wHxQKgwHIU8eIBFsNafYBAQCjOV/I3a0HkXVtLndCrWFcjmLzim9PX8EpYUV3\nyG2/AQARAQABAAD8DBWPVduzl7/ZJcAu7CzR7F376NxG8J42+ioX12n9cNEAgNj7\nqAcnQCtTDlb1waf4mdcAgMCTCuwur8AqIOSjoOzqwucAf1MfeKXhwNAEtoiD7S44\nf8UvxsKTBBgBCgAPBQJbDWn2BQkDwmcAAhsuAEgJEIRwgda38g+DPSAEGQEKAAYF\nAlsNafYACgkQNBtaoVz6VrvTVAD+LD063VrU7vlJ7xQwtMun4G3FW+RKgb7Rsww8\nB1mt68F5dQGAm8ctxECzEMmyO8jSkjOLkG6u8zLQWFm9MBZqcdmt6EUDf1dA/3xu\n/y59qEGb0j4w\n=I/Gz\n-----END PGP PRIVATE KEY BLOCK-----`;\n\nconst BOB_PUBLIC = `-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: Keybase OpenPGP v2.0.77\nComment: https://keybase.io/crypto\n\nxj0EWw1p9gEBgMeK/60KLjbrvEjqJOYGuE4p01sotYhgwbU6gTV/2qlP6cBUoVHN\nQAVcqpCOowMRKQARAQABzQDCZAQTAQoAHgUCWw1p9gIbLwMLCQcDFQoIAh4BAheA\nAxYCAQIZAQAKCRCEcIHWt/IPg+sqAYC6goCyOCYD/DytOW3I2cb12iDyFOSDsOx6\nlrmIgLyP0dDnbJHbS9ar68yuHeDqP7jOLQRbDWn2AQEAwSE4qpbLQzSIUfwmfWXm\nHneAuQIkEYawRxK/H1JkGxcAEQEAAcKTBBgBCgAPBQJbDWn2BQkPCZwAAhsuAEgJ\nEIRwgda38g+DPSAEGQEKAAYFAlsNafYACgkQtTxxgeFhyu7aXAEAvg5QrZ+SHOHZ\nVTeZ5eAAxaOg/qvJFQJOZJDG5Qu+8LJ9kwF+IfNXlAuBdJRVQDs6DqaUfmnDaC2C\nxmGKPDDLCTMXe1D6z76E5lHXrXAfFAqDAchTzi0EWw1p9gEBAKM5X8jdrQeRdW0u\nd0KtYVyOYvOKb09fwSlhRXfIbb8BABEBAAHCkwQYAQoADwUCWw1p9gUJA8JnAAIb\nLgBICRCEcIHWt/IPgz0gBBkBCgAGBQJbDWn2AAoJEDQbWqFc+la701QA/iw9Ot1a\n1O75Se8UMLTLp+BtxVvkSoG+0bMMPAdZrevBeXUBgJvHLcRAsxDJsjvI0pIzi5Bu\nrvMy0FhZvTAWanHZrehFA39XQP98bv8ufahBm9I+MA==\n=K9ht\n-----END PGP PUBLIC KEY BLOCK-----`;\n\nTestRegister.addTests([\n    {\n        name: \"PGP Encrypt/Decrypt: RSA, nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"PGP Encrypt\",\n                \"args\": [ALICE_PUBLIC]\n            },\n            {\n                \"op\": \"PGP Decrypt\",\n                \"args\": [ALICE_PRIVATE, \"\"]\n            }\n        ]\n    },\n    {\n        name: \"PGP Encrypt/Decrypt: RSA, All bytes\",\n        input: ALL_BYTES,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                \"op\": \"PGP Encrypt\",\n                \"args\": [ALICE_PUBLIC]\n            },\n            {\n                \"op\": \"PGP Decrypt\",\n                \"args\": [ALICE_PRIVATE, \"\"]\n            }\n        ]\n    },\n    {\n        name: \"PGP Encrypt/Decrypt: ECC, nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"PGP Encrypt\",\n                \"args\": [BOB_PUBLIC]\n            },\n            {\n                \"op\": \"PGP Decrypt\",\n                \"args\": [BOB_PRIVATE, \"\"]\n            }\n        ]\n    },\n    {\n        name: \"PGP Encrypt/Decrypt: ECC, All bytes\",\n        input: ALL_BYTES,\n        expectedOutput: ALL_BYTES,\n        recipeConfig: [\n            {\n                \"op\": \"PGP Encrypt\",\n                \"args\": [BOB_PUBLIC]\n            },\n            {\n                \"op\": \"PGP Decrypt\",\n                \"args\": [BOB_PRIVATE, \"\"]\n            }\n        ]\n    },\n    {\n        name: \"PGP Decrypt and Verify: UTF8, Alice -> Bob\",\n        input: `-----BEGIN PGP MESSAGE-----\nVersion: Keybase OpenPGP v2.0.77\nComment: https://keybase.io/crypto\n\nwTwDhHCB1rfyD4MBAX9ld8xGcf2v+X+pwINN0R0TvkWxNesKOQIKPV01AH8JG0J+\n+yFqLXqDHgYSLANNamfSwQoBOTWuh/5V6gpiXVm2oLHPv997AtoD/kVQrqylF5Xo\nHUsqPGtSgBA5WPX8tMoHKuqWxEy9FviLnIv73OZN0Ph70uo2E+QIv0Qx27znK0Jy\nKDSERvcldgShmVbDP3Pxtxkfr9xa2gar5f0OPovOmKGsTGciQJqPkclRwzIXg12L\nhyd2ElYOMf6vg/yOc06sX4Ih1Tn6JkYqMVJydykMv3g4Z8OXTfwrMLxwO1n3ZB/T\nOLdhBdsnREnyCqntBVjMKoRTQhfwq48n7b6caZ+aCPISdDIyDKBpxEzXaNBeEY2V\nGCqORM9WhsQ4A6pAx2SP694qH5vgOwrYrgeOU17oK++mzd1GyU2CXoFi73/PANJD\nTdC3hGr+S4XeuqZ368QG1cBWhNybsOu5sM2YbArb71ZMYuLDp+VolJbEkVf4c/dD\npVEOaX39NVKe6HcpOiw+CFO6GEkQqCXNprWK6ivBHzkAlF2pjjqlS6qhWxFPicSD\n+1ZKM1fmZu99bhTmdqE3MJx//QMu7mvlHaM85OQkWhWPBxGw/60GVBX9YtvUtfMS\nIOE1W/Zqmqzq+4frwnzWwYv9/U1RwIs/qlFVnzliREOzW+om8EncSSd7fQ==\n=fEAT\n-----END PGP MESSAGE-----\n`,\n        expectedOutput: `Signed by PGP key ID: DF98E485\nPGP fingerprint: e94e06dd0b3744a0e970de9d84246548df98e485\nSigned on Tue, 29 May 2018 15:44:52 GMT\n----------------------------------\n${UTF8_TEXT}`,\n        recipeConfig: [\n            {\n                \"op\": \"PGP Decrypt and Verify\",\n                \"args\": [ALICE_PUBLIC, BOB_PRIVATE, \"\"]\n            }\n        ]\n    },\n    {\n        name: \"PGP Decrypt: ASCII, Alice -> Bob\",\n        input: `-----BEGIN PGP MESSAGE-----\nVersion: Keybase OpenPGP v2.0.77\nComment: https://keybase.io/crypto\n\nwYwDPtlTQFIjCzoBBACSlbN7tmQVxR5ZD0rvCwXUkxO3RU8WgBkkmrTCUs9a+xrS\nF9HuKcpX/N6XrwTXyuX3BN2tGys4zd6nHV8jYqBoIyWJsWe3viTa1dh/x4183+GP\nfP61gizi3pj0gi2vfGnMhnThbdiO32PVKAeHLHBK+r3XlXZ0kzZCQKRgd55yr9Kk\nAa4SR+qpvtdobkDzbnbhcPLR6CQ8TMjTiNXEpgTc1i0JcP8jaMVFzBt8qgmDMdqU\nH2qMY1O7hezH3fp+EZzCAccJMtK7VPk13WAgMRH22HirG4aK1i75IVOtjBgObzDh\n8zKua7QLi6wJD/AtQ+D3/NgVpzoXwdoLvTjEcAyy+YWNWkJF/jvx3XV1Q/Fz7sHJ\n/bspORYvbi591S4U0m4pikwiOZk=\n=AVb/\n-----END PGP MESSAGE-----`,\n        expectedOutput: ASCII_TEXT,\n        recipeConfig: [\n            {\n                \"op\": \"PGP Decrypt\",\n                \"args\": [ALICE_PRIVATE, \"\"]\n            }\n        ]\n    },\n    {\n        name: \"PGP Verify: ASCII, Alice\",\n        input: `-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nA common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.\n-----BEGIN PGP SIGNATURE-----\n\niLMEAQEIAB0WIQRLbJy6MLpYOr9qojE+2VNAUiMLOgUCXRTsvwAKCRA+2VNAUiML\nOuaHBADMMNtsuN92Fb+UrDimsv6TDQpbJhDkwp9kZdKYP5HAmSYAhXBG7N+YCMw+\nv2FSpUu9jJiPBm1K1SEwLufQVexoRv6RsBNolRFB07sArau0s0DnIXUchCZWvyTP\n1KsjBnDr84U2b11H58g4DlTT4gQrz30rFuHz9AGmPAtDHbSXIA==\n=vnk/\n-----END PGP SIGNATURE-----`,\n        expectedOutput: `Signed by PGP key ID: DF98E485\nPGP fingerprint: e94e06dd0b3744a0e970de9d84246548df98e485\nSigned on Thu, 27 Jun 2019 16:20:15 GMT\n----------------------------------\nA common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.`,\n        recipeConfig: [\n            {\n                \"op\": \"PGP Verify\",\n                \"args\": [ALICE_PUBLIC]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/PHP.mjs",
    "content": "/**\n * PHP tests.\n *\n * @author Jarmo van Lenthe\n *\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"PHP Deserialize empty array\",\n        input: \"a:0:{}\",\n        expectedOutput: \"{}\",\n        recipeConfig: [\n            {\n                op: \"PHP Deserialize\",\n                args: [true],\n            },\n        ],\n    },\n    {\n        name: \"PHP Deserialize integer\",\n        input: \"i:10;\",\n        expectedOutput: \"10\",\n        recipeConfig: [\n            {\n                op: \"PHP Deserialize\",\n                args: [true],\n            },\n        ],\n    },\n    {\n        name: \"PHP Deserialize string\",\n        input: \"s:17:\\\"PHP Serialization\\\";\",\n        expectedOutput: \"\\\"PHP Serialization\\\"\",\n        recipeConfig: [\n            {\n                op: \"PHP Deserialize\",\n                args: [true],\n            },\n        ],\n    },\n    {\n        name: \"PHP Deserialize array (JSON)\",\n        input: \"a:2:{s:1:\\\"a\\\";i:10;i:0;a:1:{s:2:\\\"ab\\\";b:1;}}\",\n        expectedOutput: \"{\\\"a\\\": 10,\\\"0\\\": {\\\"ab\\\": true}}\",\n        recipeConfig: [\n            {\n                op: \"PHP Deserialize\",\n                args: [true],\n            },\n        ],\n    },\n    {\n        name: \"PHP Deserialize array (non-JSON)\",\n        input: \"a:2:{s:1:\\\"a\\\";i:10;i:0;a:1:{s:2:\\\"ab\\\";b:1;}}\",\n        expectedOutput: \"{\\\"a\\\": 10,0: {\\\"ab\\\": true}}\",\n        recipeConfig: [\n            {\n                op: \"PHP Deserialize\",\n                args: [false],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/PHPSerialize.mjs",
    "content": "/**\n * PHP Serialization tests.\n *\n * @author brun0ne [brunonblok@gmail.com]\n *\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"PHP Serialize empty array\",\n        input: \"[]\",\n        expectedOutput: \"a:0:{}\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize empty object\",\n        input: \"{}\",\n        expectedOutput: \"a:0:{}\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize null\",\n        input: \"null\",\n        expectedOutput: \"N;\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize integer\",\n        input: \"10\",\n        expectedOutput: \"i:10;\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize float\",\n        input: \"14.523\",\n        expectedOutput: \"d:14.523;\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize boolean\",\n        input: \"[true, false]\",\n        expectedOutput: \"a:2:{i:0;b:1;i:1;b:0;}\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize string\",\n        input: \"\\\"Test string to serialize\\\"\",\n        expectedOutput: \"s:24:\\\"Test string to serialize\\\";\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize object\",\n        input: \"{\\\"a\\\": 10,\\\"0\\\": {\\\"ab\\\": true}}\",\n        expectedOutput: \"a:2:{s:1:\\\"0\\\";a:1:{s:2:\\\"ab\\\";b:1;}s:1:\\\"a\\\";i:10;}\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"PHP Serialize array\",\n        input: \"[1,\\\"abc\\\",true,{\\\"x\\\":1,\\\"y\\\":2}]\",\n        expectedOutput: \"a:4:{i:0;i:1;i:1;s:3:\\\"abc\\\";i:2;b:1;i:3;a:2:{s:1:\\\"x\\\";i:1;s:1:\\\"y\\\";i:2;}}\",\n        recipeConfig: [\n            {\n                op: \"PHP Serialize\",\n                args: []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseCSR.mjs",
    "content": "/**\n * Parse CSR tests.\n *\n * @author jkataja\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n// openssl req -newkey rsa:1024 -keyout test-rsa-1024.key -out test-rsa-1024.csr \\\n//    -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\" \\\n//    -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\" \\\n//    -addext \"basicConstraints = critical,CA:FALSE\" \\\n//    -addext \"keyUsage = critical,digitalSignature,keyEncipherment\" \\\n//    -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_RSA_1024 = `-----BEGIN CERTIFICATE REQUEST-----\nMIICHzCCAYgCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEF\nAAOBjQAwgYkCgYEArrTrLI6FkzjX8FZfclt2ox1Dz7KRwt5f6ffZic7twLAKJ4ao\n/H3APjwoFVUXGjiNj/XF2RlId4UxB1b6CgWjujBb9W51rTdvfWLyAHsrLcptpVz+\nV9Y8X9kEFCRGGDyG5+X+Nu6COzTpUPDj4bIIX/uPk3fDYDEqLClVy8/VS48CAwEA\nAaBtMGsGCSqGSIb3DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3\ndy5leGFtcGxlLmNvbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNV\nHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOBgQB0mUlPgt6pt/kjD0pz\nOUNk5e9nBFQYQGuGIHGYbPX3mi4Wd9vUCdPixtPSTunHWs2cxX2nM8+MdcNTY+7Q\nNFgFNIvSXhbqMYoHAAApMHJOxiWpBFdYKp3tESnlgh2lUh7lQtmOjD4a1dzfU8PU\noViyp+UJGasN2WRd+4VtaPw64w==\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_RSA_1024 = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      RSA\n  Length:         1024 bits\n  Modulus:        00:ae:b4:eb:2c:8e:85:93:38:d7:f0:56:5f:72:5b:76:\n                  a3:1d:43:cf:b2:91:c2:de:5f:e9:f7:d9:89:ce:ed:c0:\n                  b0:0a:27:86:a8:fc:7d:c0:3e:3c:28:15:55:17:1a:38:\n                  8d:8f:f5:c5:d9:19:48:77:85:31:07:56:fa:0a:05:a3:\n                  ba:30:5b:f5:6e:75:ad:37:6f:7d:62:f2:00:7b:2b:2d:\n                  ca:6d:a5:5c:fe:57:d6:3c:5f:d9:04:14:24:46:18:3c:\n                  86:e7:e5:fe:36:ee:82:3b:34:e9:50:f0:e3:e1:b2:08:\n                  5f:fb:8f:93:77:c3:60:31:2a:2c:29:55:cb:cf:d5:4b:\n                  8f\n  Exponent:       65537 (0x10001)\nSignature\n  Algorithm:      SHA256withRSA\n  Signature:      74:99:49:4f:82:de:a9:b7:f9:23:0f:4a:73:39:43:64:\n                  e5:ef:67:04:54:18:40:6b:86:20:71:98:6c:f5:f7:9a:\n                  2e:16:77:db:d4:09:d3:e2:c6:d3:d2:4e:e9:c7:5a:cd:\n                  9c:c5:7d:a7:33:cf:8c:75:c3:53:63:ee:d0:34:58:05:\n                  34:8b:d2:5e:16:ea:31:8a:07:00:00:29:30:72:4e:c6:\n                  25:a9:04:57:58:2a:9d:ed:11:29:e5:82:1d:a5:52:1e:\n                  e5:42:d9:8e:8c:3e:1a:d5:dc:df:53:c3:d4:a1:58:b2:\n                  a7:e5:09:19:ab:0d:d9:64:5d:fb:85:6d:68:fc:3a:e3\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \\\n//    -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\" \\\n//    -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\" \\\n//    -addext \"basicConstraints = critical,CA:FALSE\" \\\n//    -addext \"keyUsage = critical,digitalSignature,keyEncipherment\" \\\n//    -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_RSA_2048 = `-----BEGIN CERTIFICATE REQUEST-----\nMIIDJDCCAgwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAKPogLmWPuK/IGdct2v/3MFKVaVeKp2Hl5at/zDFLCAe\n51bwh7BqNVJEci4ApwlXA1WVmQPBFBJlYwQZVjz5UAN2CmNHxud5nV03YmZ2/Iml\nRzpKcZMPqU+liJCC04L+XIbOdx+Vz52dF++Cc+FuSFq803yW+qefK8JsJNO9KuPx\nRLYKSAADa9MIJisru1PzcBAOcimOmNnFWuo+LKsd4lU30OExDdKHwtyt62Mj1c3o\nlO1JjvkjtWWjwHI+0EgTjvkeXlcUYZvvLlysdKERMRozvMTGqqoHWCgWl+Rq9Z6P\nTgNsRO4CKug1Zwmh8y6acZ7sYb/dar8HOeqJnc0pCv8CAwEAAaBtMGsGCSqGSIb3\nDQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNv\nbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF\nBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAG0cjfRBY1pBzu+jf7yMQrK5mQrh72air\nVuXHmochmyUxyt0G7ovnNhKEr+X9snShJLi5qlyvnb2roiwlCmuwGIZxErN1svQL\nZ3kQNZgH+Vyu5IRL2DlPs5AAxVmzPpbnbXNhMHyAK/ziLcU031O1PoCpxwfvPsjW\nHWOCjbZUVaJnxdp8AHqImoGAiVhJwc37feFvb2UQlLedUypQkPg/poNWduaRDoj8\nm9cpVxuxGLtONBnohzohnFECytSXWEXPIj8L9SpYK97G02nJYYCAcb5BF11Alfux\nsNxtsr6zgPaLRrvOBT11WxJVKerbhfezAJ3naem1eM3VLxCGWwMwxg==\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_RSA_2048 = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      RSA\n  Length:         2048 bits\n  Modulus:        00:a3:e8:80:b9:96:3e:e2:bf:20:67:5c:b7:6b:ff:dc:\n                  c1:4a:55:a5:5e:2a:9d:87:97:96:ad:ff:30:c5:2c:20:\n                  1e:e7:56:f0:87:b0:6a:35:52:44:72:2e:00:a7:09:57:\n                  03:55:95:99:03:c1:14:12:65:63:04:19:56:3c:f9:50:\n                  03:76:0a:63:47:c6:e7:79:9d:5d:37:62:66:76:fc:89:\n                  a5:47:3a:4a:71:93:0f:a9:4f:a5:88:90:82:d3:82:fe:\n                  5c:86:ce:77:1f:95:cf:9d:9d:17:ef:82:73:e1:6e:48:\n                  5a:bc:d3:7c:96:fa:a7:9f:2b:c2:6c:24:d3:bd:2a:e3:\n                  f1:44:b6:0a:48:00:03:6b:d3:08:26:2b:2b:bb:53:f3:\n                  70:10:0e:72:29:8e:98:d9:c5:5a:ea:3e:2c:ab:1d:e2:\n                  55:37:d0:e1:31:0d:d2:87:c2:dc:ad:eb:63:23:d5:cd:\n                  e8:94:ed:49:8e:f9:23:b5:65:a3:c0:72:3e:d0:48:13:\n                  8e:f9:1e:5e:57:14:61:9b:ef:2e:5c:ac:74:a1:11:31:\n                  1a:33:bc:c4:c6:aa:aa:07:58:28:16:97:e4:6a:f5:9e:\n                  8f:4e:03:6c:44:ee:02:2a:e8:35:67:09:a1:f3:2e:9a:\n                  71:9e:ec:61:bf:dd:6a:bf:07:39:ea:89:9d:cd:29:0a:\n                  ff\n  Exponent:       65537 (0x10001)\nSignature\n  Algorithm:      SHA256withRSA\n  Signature:      1b:47:23:7d:10:58:d6:90:73:bb:e8:df:ef:23:10:ac:\n                  ae:66:42:b8:7b:d9:a8:ab:56:e5:c7:9a:87:21:9b:25:\n                  31:ca:dd:06:ee:8b:e7:36:12:84:af:e5:fd:b2:74:a1:\n                  24:b8:b9:aa:5c:af:9d:bd:ab:a2:2c:25:0a:6b:b0:18:\n                  86:71:12:b3:75:b2:f4:0b:67:79:10:35:98:07:f9:5c:\n                  ae:e4:84:4b:d8:39:4f:b3:90:00:c5:59:b3:3e:96:e7:\n                  6d:73:61:30:7c:80:2b:fc:e2:2d:c5:34:df:53:b5:3e:\n                  80:a9:c7:07:ef:3e:c8:d6:1d:63:82:8d:b6:54:55:a2:\n                  67:c5:da:7c:00:7a:88:9a:81:80:89:58:49:c1:cd:fb:\n                  7d:e1:6f:6f:65:10:94:b7:9d:53:2a:50:90:f8:3f:a6:\n                  83:56:76:e6:91:0e:88:fc:9b:d7:29:57:1b:b1:18:bb:\n                  4e:34:19:e8:87:3a:21:9c:51:02:ca:d4:97:58:45:cf:\n                  22:3f:0b:f5:2a:58:2b:de:c6:d3:69:c9:61:80:80:71:\n                  be:41:17:5d:40:95:fb:b1:b0:dc:6d:b2:be:b3:80:f6:\n                  8b:46:bb:ce:05:3d:75:5b:12:55:29:ea:db:85:f7:b3:\n                  00:9d:e7:69:e9:b5:78:cd:d5:2f:10:86:5b:03:30:c6\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl genpkey -genparam -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out test-ec-param.pem\n// openssl req -newkey ec:test-ec-param.pem -keyout test-ec.key -out test-ec.csr \\\n//    -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\" \\\n//    -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\" \\\n//    -addext \"basicConstraints = critical,CA:FALSE\" \\\n//    -addext \"keyUsage = critical,digitalSignature,keyEncipherment\" \\\n//    -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_EC_P256 = `-----BEGIN CERTIFICATE REQUEST-----\nMIIBmzCCAUECAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqG\nSM49AwEHA0IABAmpYXNh+L9E0Q3sLhrO+MF1XgKCfqJntrOyIkrGwoiQftHbJWTA\n6duxQhU/3d9B+SN/ibeKY+xeiNBrs2eTYZ6gbTBrBgkqhkiG9w0BCQ4xXjBcMCcG\nA1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb20wDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCgYIKoZI\nzj0EAwIDSAAwRQIgQkum/qaLzE3QZ3WD00uLpalUn113FObd7rM5Mr3HQwQCIQCr\n7OjzYI9v7qIJp/E9N16XfJN87G2ZVIZ4FuPXVjokCQ==\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_EC_P256 = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      ECDSA\n  Length:         256 bits\n  Pub:            04:09:a9:61:73:61:f8:bf:44:d1:0d:ec:2e:1a:ce:f8:\n                  c1:75:5e:02:82:7e:a2:67:b6:b3:b2:22:4a:c6:c2:88:\n                  90:7e:d1:db:25:64:c0:e9:db:b1:42:15:3f:dd:df:41:\n                  f9:23:7f:89:b7:8a:63:ec:5e:88:d0:6b:b3:67:93:61:\n                  9e\n  ASN1 OID:       secp256r1\n  NIST CURVE:     P-256\nSignature\n  Algorithm:      SHA256withECDSA\n  Signature:      30:45:02:20:42:4b:a6:fe:a6:8b:cc:4d:d0:67:75:83:\n                  d3:4b:8b:a5:a9:54:9f:5d:77:14:e6:dd:ee:b3:39:32:\n                  bd:c7:43:04:02:21:00:ab:ec:e8:f3:60:8f:6f:ee:a2:\n                  09:a7:f1:3d:37:5e:97:7c:93:7c:ec:6d:99:54:86:78:\n                  16:e3:d7:56:3a:24:09\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl ecparam -name secp384r1 -genkey -noout -out test-ec-key.pem\n// openssl req -new -key test-ec-key.pem -out test-ec.csr\n// -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\"\n// -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\"\n// -addext \"basicConstraints = critical,CA:FALSE\"\n// -addext \"keyUsage = critical,digitalSignature,keyEncipherment\"\n// -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_EC_P384 = `-----BEGIN CERTIFICATE REQUEST-----\nMIIB2TCCAV4CAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTB2MBAGByqGSM49AgEGBSuB\nBAAiA2IABE3rpRO164NtXx2kYMP1zlN7YgHEincO4YgwoyAYyJm3LwcbR+XyKg6A\n/i+DUaGWa2FQ+f8w8VmEUFAgLozVxwnntPOCSODrXAQwJFPLCqs7m3o8OuzU3t07\nPOGhPtj7f6BtMGsGCSqGSIb3DQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5j\nb22CD3d3dy5leGFtcGxlLmNvbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF\noDATBgNVHSUEDDAKBggrBgEFBQcDATAKBggqhkjOPQQDAgNpADBmAjEAlq7RaEXU\naNHEC+qfuIitonWHOatm+qiiaNSh80QjLw5P1rszg9yQQigHd8cD7I4DAjEAzmo1\nDLpcESwZCBrh3sPflDA38TZjoedRNeWcVxdn1QmwDWMeprD/zgPAey8GOmyj\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_EC_P384 = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      ECDSA\n  Length:         384 bits\n  Pub:            04:4d:eb:a5:13:b5:eb:83:6d:5f:1d:a4:60:c3:f5:ce:\n                  53:7b:62:01:c4:8a:77:0e:e1:88:30:a3:20:18:c8:99:\n                  b7:2f:07:1b:47:e5:f2:2a:0e:80:fe:2f:83:51:a1:96:\n                  6b:61:50:f9:ff:30:f1:59:84:50:50:20:2e:8c:d5:c7:\n                  09:e7:b4:f3:82:48:e0:eb:5c:04:30:24:53:cb:0a:ab:\n                  3b:9b:7a:3c:3a:ec:d4:de:dd:3b:3c:e1:a1:3e:d8:fb:\n                  7f\n  ASN1 OID:       secp384r1\n  NIST CURVE:     P-384\nSignature\n  Algorithm:      SHA256withECDSA\n  Signature:      30:66:02:31:00:96:ae:d1:68:45:d4:68:d1:c4:0b:ea:\n                  9f:b8:88:ad:a2:75:87:39:ab:66:fa:a8:a2:68:d4:a1:\n                  f3:44:23:2f:0e:4f:d6:bb:33:83:dc:90:42:28:07:77:\n                  c7:03:ec:8e:03:02:31:00:ce:6a:35:0c:ba:5c:11:2c:\n                  19:08:1a:e1:de:c3:df:94:30:37:f1:36:63:a1:e7:51:\n                  35:e5:9c:57:17:67:d5:09:b0:0d:63:1e:a6:b0:ff:ce:\n                  03:c0:7b:2f:06:3a:6c:a3\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl ecparam -name secp521r1 -genkey -noout -out test-ec-key.pem\n// openssl req -new -key test-ec-key.pem -out test-ec.csr\n// -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\"\n// -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\"\n// -addext \"basicConstraints = critical,CA:FALSE\"\n// -addext \"keyUsage = critical,digitalSignature,keyEncipherment\"\n// -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_EC_P521 = `-----BEGIN CERTIFICATE REQUEST-----\nMIICIjCCAYQCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBmzAQBgcqhkjOPQIBBgUr\ngQQAIwOBhgAEAKf5BRB57svfglRz5dM0bnJAnieMFjNjOFca5/pJ2bOpORkp9Uol\nx//mHY5WOMYYC/xvM5lJRcmUnL791zQ6rf6pAD/CrEpDF2svae6e5nA/fN2XsB98\nxjmkTpYZVC5nFT83Ceo9J0kHbvliYlAMsEOO60qGghyWV7myiDgORfE+POU3oG0w\nawYJKoZIhvcNAQkOMV4wXDAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4\nYW1wbGUuY29tMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM\nMAoGCCsGAQUFBwMBMAoGCCqGSM49BAMCA4GLADCBhwJBDeIpSuvIT+kiE0ZnJwPS\nDVik93CLqjFm5Ieq02d81GwusSgAA82WlZZVZRsTEjkZXtk96zMBnh5/uxk+wN+j\n+PoCQgEDmXREwi0BPkHj6QlktE+7SLELVkrd75D9mfw/SV6ZJiLiLIT9yeoA0Zon\nuhcl2rK/DLQutuJF6JIBe5s7lieKfQ==\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_EC_P521 = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      ECDSA\n  Length:         521 bits\n  Pub:            04:00:a7:f9:05:10:79:ee:cb:df:82:54:73:e5:d3:34:\n                  6e:72:40:9e:27:8c:16:33:63:38:57:1a:e7:fa:49:d9:\n                  b3:a9:39:19:29:f5:4a:25:c7:ff:e6:1d:8e:56:38:c6:\n                  18:0b:fc:6f:33:99:49:45:c9:94:9c:be:fd:d7:34:3a:\n                  ad:fe:a9:00:3f:c2:ac:4a:43:17:6b:2f:69:ee:9e:e6:\n                  70:3f:7c:dd:97:b0:1f:7c:c6:39:a4:4e:96:19:54:2e:\n                  67:15:3f:37:09:ea:3d:27:49:07:6e:f9:62:62:50:0c:\n                  b0:43:8e:eb:4a:86:82:1c:96:57:b9:b2:88:38:0e:45:\n                  f1:3e:3c:e5:37\n  ASN1 OID:       secp521r1\n  NIST CURVE:     P-521\nSignature\n  Algorithm:      SHA256withECDSA\n  Signature:      30:81:87:02:41:0d:e2:29:4a:eb:c8:4f:e9:22:13:46:\n                  67:27:03:d2:0d:58:a4:f7:70:8b:aa:31:66:e4:87:aa:\n                  d3:67:7c:d4:6c:2e:b1:28:00:03:cd:96:95:96:55:65:\n                  1b:13:12:39:19:5e:d9:3d:eb:33:01:9e:1e:7f:bb:19:\n                  3e:c0:df:a3:f8:fa:02:42:01:03:99:74:44:c2:2d:01:\n                  3e:41:e3:e9:09:64:b4:4f:bb:48:b1:0b:56:4a:dd:ef:\n                  90:fd:99:fc:3f:49:5e:99:26:22:e2:2c:84:fd:c9:ea:\n                  00:d1:9a:27:ba:17:25:da:b2:bf:0c:b4:2e:b6:e2:45:\n                  e8:92:01:7b:9b:3b:96:27:8a:7d\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl dsaparam -out dsaparam.pem 1024\n// openssl gendsa -out dsakey.pem dsaparam.pem\n// openssl req -new -key dsakey.pem -out test-dsa.csr \\\n//    -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\" \\\n//    -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\" \\\n//    -addext \"basicConstraints = critical,CA:FALSE\" \\\n//    -addext \"keyUsage = critical,digitalSignature,keyEncipherment\" \\\n//    -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_DSA_1024 = `-----BEGIN CERTIFICATE REQUEST-----\nMIIC/jCCAqoCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAcAwggE0BgcqhkjOOAQB\nMIIBJwKBgQD8vvCmdM8wttdbq3kWigTEnnug4+2SLMl2RNXrlCQjmuZc7tGMyP1u\ngsSc9Pxd/tMrPKRawFP5SvUOkZ4cIrujdJVTb/hlfnGH4cWACe8EupwRzoqwZB1x\nawiHFzL9G6Go0HOy7bSbRdxBIYu46fnxNsDFf7lMlcBOKdq4Y12kvwIdAN4/vtK9\nKxhQfcrrzHsPXW+/xW0CMfr+NQir8PkCgYEAiNdM7IRZhXPaGRtGDpepSoRAf4uQ\nLWY9q+vFUx4fVRSSgwKBKLjW+BvzE2eJq0pXv7O09QHOghtcwzY3UrdN952sjUkJ\nLItt+5FxB7/JqCBPRrrVsyGEjR3+WbeI3wl6OvQFxm/OTNTTkemFdAfpT/YDSw+n\n1xLODTfegT/oyOoDgYUAAoGBAMz15lRPVAj8cje3ShbuACHPVE85d0Tk0Dw9qUcQ\nNCNS6A3STSbUiLGKeiRMGg2v/HM9ivV8tq1rywmgBAwtidcQ6P5yqYSZs6z3x9xZ\nOzeQ5jXftBQ1GXeU8zi1fC99inFGNixbPFVIz4/KiV0+So44n9ki2ylhbz0YQtpU\nwMF+oG0wawYJKoZIhvcNAQkOMV4wXDAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIP\nd3d3LmV4YW1wbGUuY29tMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMG\nA1UdJQQMMAoGCCsGAQUFBwMBMAsGCWCGSAFlAwQDAgNBADA+Ah0AkTogUUyKE5v9\nezKrOKpP07i2E9Zz0n/yjIvw4wIdAMB5yVMOEgI877vOFQ7zzf7oDR9eJMYlf4QV\n2sQ=\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_DSA_1024 = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      DSA\n  Length:         1024 bits\n  Pub:            00:cc:f5:e6:54:4f:54:08:fc:72:37:b7:4a:16:ee:00:\n                  21:cf:54:4f:39:77:44:e4:d0:3c:3d:a9:47:10:34:23:\n                  52:e8:0d:d2:4d:26:d4:88:b1:8a:7a:24:4c:1a:0d:af:\n                  fc:73:3d:8a:f5:7c:b6:ad:6b:cb:09:a0:04:0c:2d:89:\n                  d7:10:e8:fe:72:a9:84:99:b3:ac:f7:c7:dc:59:3b:37:\n                  90:e6:35:df:b4:14:35:19:77:94:f3:38:b5:7c:2f:7d:\n                  8a:71:46:36:2c:5b:3c:55:48:cf:8f:ca:89:5d:3e:4a:\n                  8e:38:9f:d9:22:db:29:61:6f:3d:18:42:da:54:c0:c1:\n                  7e\n  P:              00:fc:be:f0:a6:74:cf:30:b6:d7:5b:ab:79:16:8a:04:\n                  c4:9e:7b:a0:e3:ed:92:2c:c9:76:44:d5:eb:94:24:23:\n                  9a:e6:5c:ee:d1:8c:c8:fd:6e:82:c4:9c:f4:fc:5d:fe:\n                  d3:2b:3c:a4:5a:c0:53:f9:4a:f5:0e:91:9e:1c:22:bb:\n                  a3:74:95:53:6f:f8:65:7e:71:87:e1:c5:80:09:ef:04:\n                  ba:9c:11:ce:8a:b0:64:1d:71:6b:08:87:17:32:fd:1b:\n                  a1:a8:d0:73:b2:ed:b4:9b:45:dc:41:21:8b:b8:e9:f9:\n                  f1:36:c0:c5:7f:b9:4c:95:c0:4e:29:da:b8:63:5d:a4:\n                  bf\n  Q:              00:de:3f:be:d2:bd:2b:18:50:7d:ca:eb:cc:7b:0f:5d:\n                  6f:bf:c5:6d:02:31:fa:fe:35:08:ab:f0:f9\n  G:              00:88:d7:4c:ec:84:59:85:73:da:19:1b:46:0e:97:a9:\n                  4a:84:40:7f:8b:90:2d:66:3d:ab:eb:c5:53:1e:1f:55:\n                  14:92:83:02:81:28:b8:d6:f8:1b:f3:13:67:89:ab:4a:\n                  57:bf:b3:b4:f5:01:ce:82:1b:5c:c3:36:37:52:b7:4d:\n                  f7:9d:ac:8d:49:09:2c:8b:6d:fb:91:71:07:bf:c9:a8:\n                  20:4f:46:ba:d5:b3:21:84:8d:1d:fe:59:b7:88:df:09:\n                  7a:3a:f4:05:c6:6f:ce:4c:d4:d3:91:e9:85:74:07:e9:\n                  4f:f6:03:4b:0f:a7:d7:12:ce:0d:37:de:81:3f:e8:c8:\n                  ea\nSignature\n  Algorithm:      SHA256withDSA\n  Signature:\n      R:          00:91:3a:20:51:4c:8a:13:9b:fd:7b:32:ab:38:aa:4f:\n                  d3:b8:b6:13:d6:73:d2:7f:f2:8c:8b:f0:e3\n      S:          00:c0:79:c9:53:0e:12:02:3c:ef:bb:ce:15:0e:f3:cd:\n                  fe:e8:0d:1f:5e:24:c6:25:7f:84:15:da:c4\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl dsaparam -out dsaparam.pem 2048\n// openssl gendsa -out dsakey.pem dsaparam.pem\n// openssl req -new -key dsakey.pem -out test-dsa.csr \\\n//    -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\" \\\n//    -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\" \\\n//    -addext \"basicConstraints = critical,CA:FALSE\" \\\n//    -addext \"keyUsage = critical,digitalSignature,keyEncipherment\" \\\n//    -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_DSA_2048 = `-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCBCwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCA0IwggI1BgcqhkjOOAQB\nMIICKAKCAQEAsvoKmCHcR2y8qQ/kpBHOvlaGifq//F/0zhWSpfjvwqI3g2EjqXL7\nrCYyu9wxoogODo6Dnenxfw1xp3ZIJNCtfrSJyt0AudjOedtVWMSnTndoQVQtYSI0\nmmrBAqFL26i1bmEMxsd6pz2nU3p8yGY/wpYiWwyy+/TZv8a2t58owpw9Qkm4cX4E\nPo3ih/XbN6eooOx9ZaErcS9mg3UvwQDm0VYD3ZjSeqwP7YWGyhq7gPJsEiMrft12\n1SjyNz8rkhXzqZFRujjmfTT5dpCC/Z4d7/ZE30tbqHaNDM+YwBrb/aL7PnoWs847\nVpjCVxmVmgIPoMHlTbg29RsIUoFlFScaUQIdAMGwwpzilrReaEqcoX7PY5u4vtV0\n5zuiVIqkdBMCggEAQZhk5qdAYoMvZhPi5TOgysTzQE1FeAEtgypxZI65TpwO/JOr\nAX9vYZ/qCYX/ncj455qiPZenl59lo/iQPzhJUubuCevPWJ3dsKRbAyL/5NCwifnf\nYBMJGj0UFGL4ekVV0emLL9H5eqYz64w0eV2Sp40O8yCu0qr7QTi3zpqzJZ43E+26\nZ9bgR6c1lmgKW2QN72PHwMlTlq0O6mN+eikEWoGr09JWpXMThZemAO2mHLAiq6ju\n0+zduzWZyjZPZA1B4XUlTgCtzHveYpUzZ1NhZyM8jcGFOmmZWAFNwt03bq9/Ma0q\n3jB0Dyz7IDGm8D6Y770wJRP3jf7iCVYt8jB49gOCAQUAAoIBACnVv+1ROrUiHAwn\nxXGlsZdTEYZfWbE8Cter15JNNqh/Z1cdIp9m1t/rVF69nSWQvrvLeFo5p5mGxK8r\nIKHTZTaAn6uO6PcNJc6iB7fS15L4uiB7p73MdjE+3PcYMbhttDlexdm6QxsmCP1F\n3LYW3Uh879AURWZwPH3z4NZL2u1AFSyS1vQhtiCmztq94QwhjoDf9anFR8q05dAC\njuPlKYEIhMsoq+r/l/kOM1UghhXX6BmeF8R9hhW1p4Rv+gyAgbYjowJFtZnwE5p0\nOYLJzSQWjFMYEzHAoH8J4+D5okt4IXEd0BDxLBkm1WonIxYL/NL95p3qXpgUXqRX\nM9spEzWgbTBrBgkqhkiG9w0BCQ4xXjBcMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29t\ngg93d3cuZXhhbXBsZS5jb20wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw\nEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYJYIZIAWUDBAMCA0AAMD0CHQCyrstoqfvs\nMCfsZUeycKrKQmAJAHxuoGPCKl7yAhwhNH9RNxBm5roO2U901BeF2p0pT410ghH8\noA+F\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_DSA_2048 = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      DSA\n  Length:         2048 bits\n  Pub:            29:d5:bf:ed:51:3a:b5:22:1c:0c:27:c5:71:a5:b1:97:\n                  53:11:86:5f:59:b1:3c:0a:d7:ab:d7:92:4d:36:a8:7f:\n                  67:57:1d:22:9f:66:d6:df:eb:54:5e:bd:9d:25:90:be:\n                  bb:cb:78:5a:39:a7:99:86:c4:af:2b:20:a1:d3:65:36:\n                  80:9f:ab:8e:e8:f7:0d:25:ce:a2:07:b7:d2:d7:92:f8:\n                  ba:20:7b:a7:bd:cc:76:31:3e:dc:f7:18:31:b8:6d:b4:\n                  39:5e:c5:d9:ba:43:1b:26:08:fd:45:dc:b6:16:dd:48:\n                  7c:ef:d0:14:45:66:70:3c:7d:f3:e0:d6:4b:da:ed:40:\n                  15:2c:92:d6:f4:21:b6:20:a6:ce:da:bd:e1:0c:21:8e:\n                  80:df:f5:a9:c5:47:ca:b4:e5:d0:02:8e:e3:e5:29:81:\n                  08:84:cb:28:ab:ea:ff:97:f9:0e:33:55:20:86:15:d7:\n                  e8:19:9e:17:c4:7d:86:15:b5:a7:84:6f:fa:0c:80:81:\n                  b6:23:a3:02:45:b5:99:f0:13:9a:74:39:82:c9:cd:24:\n                  16:8c:53:18:13:31:c0:a0:7f:09:e3:e0:f9:a2:4b:78:\n                  21:71:1d:d0:10:f1:2c:19:26:d5:6a:27:23:16:0b:fc:\n                  d2:fd:e6:9d:ea:5e:98:14:5e:a4:57:33:db:29:13:35\n  P:              00:b2:fa:0a:98:21:dc:47:6c:bc:a9:0f:e4:a4:11:ce:\n                  be:56:86:89:fa:bf:fc:5f:f4:ce:15:92:a5:f8:ef:c2:\n                  a2:37:83:61:23:a9:72:fb:ac:26:32:bb:dc:31:a2:88:\n                  0e:0e:8e:83:9d:e9:f1:7f:0d:71:a7:76:48:24:d0:ad:\n                  7e:b4:89:ca:dd:00:b9:d8:ce:79:db:55:58:c4:a7:4e:\n                  77:68:41:54:2d:61:22:34:9a:6a:c1:02:a1:4b:db:a8:\n                  b5:6e:61:0c:c6:c7:7a:a7:3d:a7:53:7a:7c:c8:66:3f:\n                  c2:96:22:5b:0c:b2:fb:f4:d9:bf:c6:b6:b7:9f:28:c2:\n                  9c:3d:42:49:b8:71:7e:04:3e:8d:e2:87:f5:db:37:a7:\n                  a8:a0:ec:7d:65:a1:2b:71:2f:66:83:75:2f:c1:00:e6:\n                  d1:56:03:dd:98:d2:7a:ac:0f:ed:85:86:ca:1a:bb:80:\n                  f2:6c:12:23:2b:7e:dd:76:d5:28:f2:37:3f:2b:92:15:\n                  f3:a9:91:51:ba:38:e6:7d:34:f9:76:90:82:fd:9e:1d:\n                  ef:f6:44:df:4b:5b:a8:76:8d:0c:cf:98:c0:1a:db:fd:\n                  a2:fb:3e:7a:16:b3:ce:3b:56:98:c2:57:19:95:9a:02:\n                  0f:a0:c1:e5:4d:b8:36:f5:1b:08:52:81:65:15:27:1a:\n                  51\n  Q:              00:c1:b0:c2:9c:e2:96:b4:5e:68:4a:9c:a1:7e:cf:63:\n                  9b:b8:be:d5:74:e7:3b:a2:54:8a:a4:74:13\n  G:              41:98:64:e6:a7:40:62:83:2f:66:13:e2:e5:33:a0:ca:\n                  c4:f3:40:4d:45:78:01:2d:83:2a:71:64:8e:b9:4e:9c:\n                  0e:fc:93:ab:01:7f:6f:61:9f:ea:09:85:ff:9d:c8:f8:\n                  e7:9a:a2:3d:97:a7:97:9f:65:a3:f8:90:3f:38:49:52:\n                  e6:ee:09:eb:cf:58:9d:dd:b0:a4:5b:03:22:ff:e4:d0:\n                  b0:89:f9:df:60:13:09:1a:3d:14:14:62:f8:7a:45:55:\n                  d1:e9:8b:2f:d1:f9:7a:a6:33:eb:8c:34:79:5d:92:a7:\n                  8d:0e:f3:20:ae:d2:aa:fb:41:38:b7:ce:9a:b3:25:9e:\n                  37:13:ed:ba:67:d6:e0:47:a7:35:96:68:0a:5b:64:0d:\n                  ef:63:c7:c0:c9:53:96:ad:0e:ea:63:7e:7a:29:04:5a:\n                  81:ab:d3:d2:56:a5:73:13:85:97:a6:00:ed:a6:1c:b0:\n                  22:ab:a8:ee:d3:ec:dd:bb:35:99:ca:36:4f:64:0d:41:\n                  e1:75:25:4e:00:ad:cc:7b:de:62:95:33:67:53:61:67:\n                  23:3c:8d:c1:85:3a:69:99:58:01:4d:c2:dd:37:6e:af:\n                  7f:31:ad:2a:de:30:74:0f:2c:fb:20:31:a6:f0:3e:98:\n                  ef:bd:30:25:13:f7:8d:fe:e2:09:56:2d:f2:30:78:f6\nSignature\n  Algorithm:      SHA256withDSA\n  Signature:\n      R:          00:b2:ae:cb:68:a9:fb:ec:30:27:ec:65:47:b2:70:aa:\n                  ca:42:60:09:00:7c:6e:a0:63:c2:2a:5e:f2\n      S:          21:34:7f:51:37:10:66:e6:ba:0e:d9:4f:74:d4:17:85:\n                  da:9d:29:4f:8d:74:82:11:fc:a0:0f:85\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl req -newkey rsa:4096 -keyout test-rsa-4096.key -out test-rsa-4096.csr\n// -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\"\n// -addext \"subjectAltName = DNS:example.com,DNS:www.example.com,IP:127.0.0.1, \\\n// email:user@example.com,URI:http://example.com/api,otherName:1.2.3.4;UTF8:some value\"\n// -addext \"basicConstraints = critical,CA:FALSE\"\n// -addext \"keyUsage = critical,digitalSignature,keyEncipherment\"\n// -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_SAN = `-----BEGIN CERTIFICATE REQUEST-----\nMIIFbTCCA1UCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEB\nBQADggIPADCCAgoCggIBAJf8uQDFcQfj6qCuPa4hNyDWr3Lwzfc3qQZdOgNJ/kym\nGxxRHUXJyBtgkmAqDoSGmg1hUWgt9eZwd/Cf4Wd3qr+Q0ppg6dwZeWgYSunseoKl\nf0E5FvUfECNyDwCSbltN9TCsom2ePNOOJJHWo4Y3E3jGXz0n1Vwa6ePR0j62Rcey\n4lHLscQ3GoNvMLcXbY1HIhnbaI25MmFPB8p4PvpPsAYgbWHbw0jIR9dSxEK0HAU3\n2VkRkm8XaF4BOEfugqT3Bc7zAvwdFZRTTTZIICYW5T3zvtxBidJ8OSej16LV6ZeE\n/4VcTzXYTzIUXbNaev3XN1r5ZodkbZvxxk/EZmfes2OtedPulW4TW27HSl6XBos/\n8VQohelUXiyCLPrtbnjeHKSz47+ZAm23jMAFYWkTVdWvAa+G74UstuRRXfLAKCNv\n7VeA3l8IgEkfj48u+EenV6cJ3ZJJ5/qvZo7OUjhAtYJmNtlRYE4r3uWRmaNXYwrD\n7vJuMiZafaVC+74/UHLGGm7sHVJdo4KBO/LUbHJ/SKZIYMc14kJLOf6TPZXSGm9N\nTxbOV9Vzcjzivq1HxaYirLAM+nyVApVwwpVq/uiEFz579yrwySvBuwnewfdfZ6EZ\niNAKiBwQ8diFMnFfd/28hJ8TrIlq+5bkVo1ODuhyRIw9YB19IrmytaVvkR8624Ld\nAgMBAAGggbUwgbIGCSqGSIb3DQEJDjGBpDCBoTBsBgNVHREEZTBjggtleGFtcGxl\nLmNvbYIPd3d3LmV4YW1wbGUuY29thwR/AAABgRB1c2VyQGV4YW1wbGUuY29thhZo\ndHRwOi8vZXhhbXBsZS5jb20vYXBpoBMGAyoDBKAMDApzb21lIHZhbHVlMAwGA1Ud\nEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G\nCSqGSIb3DQEBCwUAA4ICAQAtOuh6MEralwgChJHBaGJavBxpCQ0p5K77RlAPIk5Q\nMv5086DxiZEFBKCRiZRtkOvo0aCHUn3awDrlEOgECiAYQqMIBUWeNwImtmpDopuI\nZMmVmzc2ojf9nUlPrPV+B6P2jTxTIQYpDQocbOgxDkcdZVSvLyMEFnHIMNQV7GS2\ngBmUnPp+4z2d8X9XaRspkuEt2nbA1NoXekWaG46jG56VoBycepOiNkwL4AsqunLa\nT0urcHq34g+HRQWwOA+q/72qP4oaj2ZO0fFJQl2ZsGRT/IuM1g2YsnVSpBOGY/J6\nQi2hDr6EEqphg501ny+FZE1BouQ/lSykafYyauwNq1puu/VyuF8grFmL0SoxWWfP\nh6viblGM/Vu69Bhl4gkWKtufWpOVpCA4vHzes8IVMFg7vhpwm33Xjo0lCPcIUin6\n0CqHZQCsWtj2yIAF66WHB0I1DHL5FNCWRPnQCo54qRZIYqtSP20QRr6GWC2d+ZgX\nwDxRpmzr8T8owBYWw3j+RK9CtZoWO4O586UR4J1Bn5PQfoR78Z/4mzv2sxVi9Fdf\nsJzlG6/nhmMaCqneIn97gkguvSgpOuKSeo/fjbpnthufgilrpDQoGrhZaXic0GVZ\n6JmbOh3tLMVf4ooyyaLfOCfV2FN12rDa3pdWhQ4MVN4gg9U3Cq0x7yRQKiSBlBnw\noA==\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_SAN = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      RSA\n  Length:         4096 bits\n  Modulus:        00:97:fc:b9:00:c5:71:07:e3:ea:a0:ae:3d:ae:21:37:\n                  20:d6:af:72:f0:cd:f7:37:a9:06:5d:3a:03:49:fe:4c:\n                  a6:1b:1c:51:1d:45:c9:c8:1b:60:92:60:2a:0e:84:86:\n                  9a:0d:61:51:68:2d:f5:e6:70:77:f0:9f:e1:67:77:aa:\n                  bf:90:d2:9a:60:e9:dc:19:79:68:18:4a:e9:ec:7a:82:\n                  a5:7f:41:39:16:f5:1f:10:23:72:0f:00:92:6e:5b:4d:\n                  f5:30:ac:a2:6d:9e:3c:d3:8e:24:91:d6:a3:86:37:13:\n                  78:c6:5f:3d:27:d5:5c:1a:e9:e3:d1:d2:3e:b6:45:c7:\n                  b2:e2:51:cb:b1:c4:37:1a:83:6f:30:b7:17:6d:8d:47:\n                  22:19:db:68:8d:b9:32:61:4f:07:ca:78:3e:fa:4f:b0:\n                  06:20:6d:61:db:c3:48:c8:47:d7:52:c4:42:b4:1c:05:\n                  37:d9:59:11:92:6f:17:68:5e:01:38:47:ee:82:a4:f7:\n                  05:ce:f3:02:fc:1d:15:94:53:4d:36:48:20:26:16:e5:\n                  3d:f3:be:dc:41:89:d2:7c:39:27:a3:d7:a2:d5:e9:97:\n                  84:ff:85:5c:4f:35:d8:4f:32:14:5d:b3:5a:7a:fd:d7:\n                  37:5a:f9:66:87:64:6d:9b:f1:c6:4f:c4:66:67:de:b3:\n                  63:ad:79:d3:ee:95:6e:13:5b:6e:c7:4a:5e:97:06:8b:\n                  3f:f1:54:28:85:e9:54:5e:2c:82:2c:fa:ed:6e:78:de:\n                  1c:a4:b3:e3:bf:99:02:6d:b7:8c:c0:05:61:69:13:55:\n                  d5:af:01:af:86:ef:85:2c:b6:e4:51:5d:f2:c0:28:23:\n                  6f:ed:57:80:de:5f:08:80:49:1f:8f:8f:2e:f8:47:a7:\n                  57:a7:09:dd:92:49:e7:fa:af:66:8e:ce:52:38:40:b5:\n                  82:66:36:d9:51:60:4e:2b:de:e5:91:99:a3:57:63:0a:\n                  c3:ee:f2:6e:32:26:5a:7d:a5:42:fb:be:3f:50:72:c6:\n                  1a:6e:ec:1d:52:5d:a3:82:81:3b:f2:d4:6c:72:7f:48:\n                  a6:48:60:c7:35:e2:42:4b:39:fe:93:3d:95:d2:1a:6f:\n                  4d:4f:16:ce:57:d5:73:72:3c:e2:be:ad:47:c5:a6:22:\n                  ac:b0:0c:fa:7c:95:02:95:70:c2:95:6a:fe:e8:84:17:\n                  3e:7b:f7:2a:f0:c9:2b:c1:bb:09:de:c1:f7:5f:67:a1:\n                  19:88:d0:0a:88:1c:10:f1:d8:85:32:71:5f:77:fd:bc:\n                  84:9f:13:ac:89:6a:fb:96:e4:56:8d:4e:0e:e8:72:44:\n                  8c:3d:60:1d:7d:22:b9:b2:b5:a5:6f:91:1f:3a:db:82:\n                  dd\n  Exponent:       65537 (0x10001)\nSignature\n  Algorithm:      SHA256withRSA\n  Signature:      2d:3a:e8:7a:30:4a:da:97:08:02:84:91:c1:68:62:5a:\n                  bc:1c:69:09:0d:29:e4:ae:fb:46:50:0f:22:4e:50:32:\n                  fe:74:f3:a0:f1:89:91:05:04:a0:91:89:94:6d:90:eb:\n                  e8:d1:a0:87:52:7d:da:c0:3a:e5:10:e8:04:0a:20:18:\n                  42:a3:08:05:45:9e:37:02:26:b6:6a:43:a2:9b:88:64:\n                  c9:95:9b:37:36:a2:37:fd:9d:49:4f:ac:f5:7e:07:a3:\n                  f6:8d:3c:53:21:06:29:0d:0a:1c:6c:e8:31:0e:47:1d:\n                  65:54:af:2f:23:04:16:71:c8:30:d4:15:ec:64:b6:80:\n                  19:94:9c:fa:7e:e3:3d:9d:f1:7f:57:69:1b:29:92:e1:\n                  2d:da:76:c0:d4:da:17:7a:45:9a:1b:8e:a3:1b:9e:95:\n                  a0:1c:9c:7a:93:a2:36:4c:0b:e0:0b:2a:ba:72:da:4f:\n                  4b:ab:70:7a:b7:e2:0f:87:45:05:b0:38:0f:aa:ff:bd:\n                  aa:3f:8a:1a:8f:66:4e:d1:f1:49:42:5d:99:b0:64:53:\n                  fc:8b:8c:d6:0d:98:b2:75:52:a4:13:86:63:f2:7a:42:\n                  2d:a1:0e:be:84:12:aa:61:83:9d:35:9f:2f:85:64:4d:\n                  41:a2:e4:3f:95:2c:a4:69:f6:32:6a:ec:0d:ab:5a:6e:\n                  bb:f5:72:b8:5f:20:ac:59:8b:d1:2a:31:59:67:cf:87:\n                  ab:e2:6e:51:8c:fd:5b:ba:f4:18:65:e2:09:16:2a:db:\n                  9f:5a:93:95:a4:20:38:bc:7c:de:b3:c2:15:30:58:3b:\n                  be:1a:70:9b:7d:d7:8e:8d:25:08:f7:08:52:29:fa:d0:\n                  2a:87:65:00:ac:5a:d8:f6:c8:80:05:eb:a5:87:07:42:\n                  35:0c:72:f9:14:d0:96:44:f9:d0:0a:8e:78:a9:16:48:\n                  62:ab:52:3f:6d:10:46:be:86:58:2d:9d:f9:98:17:c0:\n                  3c:51:a6:6c:eb:f1:3f:28:c0:16:16:c3:78:fe:44:af:\n                  42:b5:9a:16:3b:83:b9:f3:a5:11:e0:9d:41:9f:93:d0:\n                  7e:84:7b:f1:9f:f8:9b:3b:f6:b3:15:62:f4:57:5f:b0:\n                  9c:e5:1b:af:e7:86:63:1a:0a:a9:de:22:7f:7b:82:48:\n                  2e:bd:28:29:3a:e2:92:7a:8f:df:8d:ba:67:b6:1b:9f:\n                  82:29:6b:a4:34:28:1a:b8:59:69:78:9c:d0:65:59:e8:\n                  99:9b:3a:1d:ed:2c:c5:5f:e2:8a:32:c9:a2:df:38:27:\n                  d5:d8:53:75:da:b0:da:de:97:56:85:0e:0c:54:de:20:\n                  83:d5:37:0a:ad:31:ef:24:50:2a:24:81:94:19:f0:a0\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com\n    IP: 127.0.0.1\n    EMAIL: user@example.com\n    URI: http://example.com/api\n    Other: 1.2.3.4::some value`;\n\n// openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \\\n//    -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\" \\\n//    -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\" \\\n//    -addext \"basicConstraints = critical,CA:FALSE\" \\\n//    -addext \"keyUsage = critical,digitalSignature,keyEncipherment,\" \\\n//    -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_KEY_USAGE = `-----BEGIN CERTIFICATE REQUEST-----\nMIIDJDCCAgwCAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAKHQWxqtdJQ1l7ApTgwgsyrN/kRDrog/DsUlZQg3YodY\n4RRAgPr+AeQ1BhuWDVxaXein0XmXOESHgK9Z7X/hLgRy2ifK+n20Ij3+k6VSh6Lt\nlpjUPwK7PWBtZ969DukBIvq64XrJTNWIJPvXXQxkL4dk5NcDY4TjXWt0GgDVR+GH\nOU1JwfzviGVRdOmY8+Ckfxc+3QytTdP6KBQaiUk5sBEniovDpKfImtql72JsCRbA\n9Wue7X4EbXi2zvoAlJ5NXF3Ps1q2XsVJeIx/mMDcgRW7s5AVM9NQW0O1JLoA7dY+\nvSrKZj+ssuKCIWM7u9Big2I0miEl5AXrDlwZPBhM9FMCAwEAAaBtMGsGCSqGSIb3\nDQEJDjFeMFwwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNv\nbTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIB/jATBgNVHSUEDDAKBggrBgEF\nBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAPOr6jfq/mXilqXA11CTza69Ydd4fvp6q\nUG47PefzQqSmYtpUytwZRLGQ1IFRlYeXwbazVLkRmLNwpbB8C5fh9FPp55JCpM/O\ntgCW2uqLkCtkQMUCaSdRX/Y+9ypYhdBkSNv1Q+3QXi2jmi5QMqwerAwNmeXmH6AZ\nswMgAhuoLS9OrIqHjFoHGoXsgXMkbLr6m6hgyFt8ZbbwK4WpVcgCZfhtBiLilCJN\nXr9GUXL3FqUb7sIaYKAaghr2haqKhFsIH57XVK3DZYhOkLd9uC8TLdl2e+t9Hcy9\nymLwiIGMUfuBQMP8nVu3jGXAQ5N4VV+IZfF8UaBFW8tG+Ms2TeW68Q==\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_KEY_USAGE = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      RSA\n  Length:         2048 bits\n  Modulus:        00:a1:d0:5b:1a:ad:74:94:35:97:b0:29:4e:0c:20:b3:\n                  2a:cd:fe:44:43:ae:88:3f:0e:c5:25:65:08:37:62:87:\n                  58:e1:14:40:80:fa:fe:01:e4:35:06:1b:96:0d:5c:5a:\n                  5d:e8:a7:d1:79:97:38:44:87:80:af:59:ed:7f:e1:2e:\n                  04:72:da:27:ca:fa:7d:b4:22:3d:fe:93:a5:52:87:a2:\n                  ed:96:98:d4:3f:02:bb:3d:60:6d:67:de:bd:0e:e9:01:\n                  22:fa:ba:e1:7a:c9:4c:d5:88:24:fb:d7:5d:0c:64:2f:\n                  87:64:e4:d7:03:63:84:e3:5d:6b:74:1a:00:d5:47:e1:\n                  87:39:4d:49:c1:fc:ef:88:65:51:74:e9:98:f3:e0:a4:\n                  7f:17:3e:dd:0c:ad:4d:d3:fa:28:14:1a:89:49:39:b0:\n                  11:27:8a:8b:c3:a4:a7:c8:9a:da:a5:ef:62:6c:09:16:\n                  c0:f5:6b:9e:ed:7e:04:6d:78:b6:ce:fa:00:94:9e:4d:\n                  5c:5d:cf:b3:5a:b6:5e:c5:49:78:8c:7f:98:c0:dc:81:\n                  15:bb:b3:90:15:33:d3:50:5b:43:b5:24:ba:00:ed:d6:\n                  3e:bd:2a:ca:66:3f:ac:b2:e2:82:21:63:3b:bb:d0:62:\n                  83:62:34:9a:21:25:e4:05:eb:0e:5c:19:3c:18:4c:f4:\n                  53\n  Exponent:       65537 (0x10001)\nSignature\n  Algorithm:      SHA256withRSA\n  Signature:      3c:ea:fa:8d:fa:bf:99:78:a5:a9:70:35:d4:24:f3:6b:\n                  af:58:75:de:1f:be:9e:aa:50:6e:3b:3d:e7:f3:42:a4:\n                  a6:62:da:54:ca:dc:19:44:b1:90:d4:81:51:95:87:97:\n                  c1:b6:b3:54:b9:11:98:b3:70:a5:b0:7c:0b:97:e1:f4:\n                  53:e9:e7:92:42:a4:cf:ce:b6:00:96:da:ea:8b:90:2b:\n                  64:40:c5:02:69:27:51:5f:f6:3e:f7:2a:58:85:d0:64:\n                  48:db:f5:43:ed:d0:5e:2d:a3:9a:2e:50:32:ac:1e:ac:\n                  0c:0d:99:e5:e6:1f:a0:19:b3:03:20:02:1b:a8:2d:2f:\n                  4e:ac:8a:87:8c:5a:07:1a:85:ec:81:73:24:6c:ba:fa:\n                  9b:a8:60:c8:5b:7c:65:b6:f0:2b:85:a9:55:c8:02:65:\n                  f8:6d:06:22:e2:94:22:4d:5e:bf:46:51:72:f7:16:a5:\n                  1b:ee:c2:1a:60:a0:1a:82:1a:f6:85:aa:8a:84:5b:08:\n                  1f:9e:d7:54:ad:c3:65:88:4e:90:b7:7d:b8:2f:13:2d:\n                  d9:76:7b:eb:7d:1d:cc:bd:ca:62:f0:88:81:8c:51:fb:\n                  81:40:c3:fc:9d:5b:b7:8c:65:c0:43:93:78:55:5f:88:\n                  65:f1:7c:51:a0:45:5b:cb:46:f8:cb:36:4d:e5:ba:f1\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Non-repudiation\n    Key encipherment\n    Data encipherment\n    Key agreement\n    Key certificate signing\n    CRL signing\n  Extended Key Usage:\n    TLS Web Server Authentication\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\n// openssl req -newkey rsa:2048 -keyout test-rsa-2048.key -out test-rsa-2048.csr \\\n//    -subj \"/C=CH/ST=Zurich/L=Zurich/O=Example RE/OU=IT Department/CN=example.com\" \\\n//    -addext \"subjectAltName = DNS:example.com,DNS:www.example.com\" \\\n//    -addext \"basicConstraints = critical,CA:FALSE\" \\\n//    -addext \"keyUsage = critical,digitalSignature,keyEncipherment\" \\\n//    -addext \"extendedKeyUsage = serverAuth\"\nconst IN_EXAMPLE_COM_EXTENDED_KEY_USAGE = `-----BEGIN CERTIFICATE REQUEST-----\nMIIDpzCCAo8CAQAwcjELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0G\nA1UEBwwGWnVyaWNoMRMwEQYDVQQKDApFeGFtcGxlIFJFMRYwFAYDVQQLDA1JVCBE\nZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAMjQ/Bz+CzA/WaS+Nyp3ijWzYlKY7GmA/a2FuzNSPQlr\nWuGyZJcfb0CpLIpRF8qcDllAe+hFQnVGnk3svQIhfEOD7qwzBRMHVhe59jkv2kER\ns+u88KBCNfIAS6m5d45y4xH338aXq4lZexiEASWHS7SsWAR3kL3c9p14U9EHOaym\nZWPO/SCfCJyhxszDLM2eG5S2rviuu9nY+rk0Oo7z8x8PZF9Wl1NamLl1tWPqsznS\n3bfjdJYeUlm7XvTzC6EMAT6K/5ker0chl7Hg0mcEO9w4c2cSTAHvZ2b2sRYbxNQZ\n49byQsRAXW8TNnOaK9Phmvwy/irEXU9PEl3u7KvSnNcCAwEAAaCB7zCB7AYJKoZI\nhvcNAQkOMYHeMIHbMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBs\nZS5jb20wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwgZEGA1UdJQSBiTCB\nhgYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUF\nBwMIBgorBgEEAYI3AgEVBgorBgEEAYI3AgEWBgorBgEEAYI3CgMBBgorBgEEAYI3\nCgMDBgorBgEEAYI3CgMEBgorBgEEAYI3FAICBgorBgEEAYI3CgMDMA0GCSqGSIb3\nDQEBCwUAA4IBAQCcYWj1eIxj/FUEhhm2lZr06Pq4GEtIVsMWw5IrUn2FIFb/yY8x\nGHuB5v7XNA/8zhRWvIAXGaa8Bnajk4mR0rkxy1MXpd2YevdrF/XFa2Totv4E4/I6\npvrFefYTSGpmCu5zQTuoanM7JjE81vvbTLFdaHMdLOekpuK5v5kbuNdtDpEiAkd0\nvmV4BQ0BV3b3zhIRQqBB60pSBHYvMhHNn/80RhVUQxaPTS7/AMHRZGRc1lD9/bjA\npMBis9CL4AbXtTcztU5qy4VpB1/Ej3AbAjuJIbpbPH6XtxIEtqdM4Seqi44w9oX4\nrxQagXmvJPp+E4253EkeHwhfHh4SnJEtsibQ\n-----END CERTIFICATE REQUEST-----`;\n\nconst OUT_EXAMPLE_COM_EXTENDED_KEY_USAGE = `Subject\n  C  = CH\n  ST = Zurich\n  L  = Zurich\n  O  = Example RE\n  OU = IT Department\n  CN = example.com\nPublic Key\n  Algorithm:      RSA\n  Length:         2048 bits\n  Modulus:        00:c8:d0:fc:1c:fe:0b:30:3f:59:a4:be:37:2a:77:8a:\n                  35:b3:62:52:98:ec:69:80:fd:ad:85:bb:33:52:3d:09:\n                  6b:5a:e1:b2:64:97:1f:6f:40:a9:2c:8a:51:17:ca:9c:\n                  0e:59:40:7b:e8:45:42:75:46:9e:4d:ec:bd:02:21:7c:\n                  43:83:ee:ac:33:05:13:07:56:17:b9:f6:39:2f:da:41:\n                  11:b3:eb:bc:f0:a0:42:35:f2:00:4b:a9:b9:77:8e:72:\n                  e3:11:f7:df:c6:97:ab:89:59:7b:18:84:01:25:87:4b:\n                  b4:ac:58:04:77:90:bd:dc:f6:9d:78:53:d1:07:39:ac:\n                  a6:65:63:ce:fd:20:9f:08:9c:a1:c6:cc:c3:2c:cd:9e:\n                  1b:94:b6:ae:f8:ae:bb:d9:d8:fa:b9:34:3a:8e:f3:f3:\n                  1f:0f:64:5f:56:97:53:5a:98:b9:75:b5:63:ea:b3:39:\n                  d2:dd:b7:e3:74:96:1e:52:59:bb:5e:f4:f3:0b:a1:0c:\n                  01:3e:8a:ff:99:1e:af:47:21:97:b1:e0:d2:67:04:3b:\n                  dc:38:73:67:12:4c:01:ef:67:66:f6:b1:16:1b:c4:d4:\n                  19:e3:d6:f2:42:c4:40:5d:6f:13:36:73:9a:2b:d3:e1:\n                  9a:fc:32:fe:2a:c4:5d:4f:4f:12:5d:ee:ec:ab:d2:9c:\n                  d7\n  Exponent:       65537 (0x10001)\nSignature\n  Algorithm:      SHA256withRSA\n  Signature:      9c:61:68:f5:78:8c:63:fc:55:04:86:19:b6:95:9a:f4:\n                  e8:fa:b8:18:4b:48:56:c3:16:c3:92:2b:52:7d:85:20:\n                  56:ff:c9:8f:31:18:7b:81:e6:fe:d7:34:0f:fc:ce:14:\n                  56:bc:80:17:19:a6:bc:06:76:a3:93:89:91:d2:b9:31:\n                  cb:53:17:a5:dd:98:7a:f7:6b:17:f5:c5:6b:64:e8:b6:\n                  fe:04:e3:f2:3a:a6:fa:c5:79:f6:13:48:6a:66:0a:ee:\n                  73:41:3b:a8:6a:73:3b:26:31:3c:d6:fb:db:4c:b1:5d:\n                  68:73:1d:2c:e7:a4:a6:e2:b9:bf:99:1b:b8:d7:6d:0e:\n                  91:22:02:47:74:be:65:78:05:0d:01:57:76:f7:ce:12:\n                  11:42:a0:41:eb:4a:52:04:76:2f:32:11:cd:9f:ff:34:\n                  46:15:54:43:16:8f:4d:2e:ff:00:c1:d1:64:64:5c:d6:\n                  50:fd:fd:b8:c0:a4:c0:62:b3:d0:8b:e0:06:d7:b5:37:\n                  33:b5:4e:6a:cb:85:69:07:5f:c4:8f:70:1b:02:3b:89:\n                  21:ba:5b:3c:7e:97:b7:12:04:b6:a7:4c:e1:27:aa:8b:\n                  8e:30:f6:85:f8:af:14:1a:81:79:af:24:fa:7e:13:8d:\n                  b9:dc:49:1e:1f:08:5f:1e:1e:12:9c:91:2d:b2:26:d0\nRequested Extensions\n  Basic Constraints: critical\n    CA = false\n  Key Usage: critical\n    Digital Signature\n    Key encipherment\n  Extended Key Usage:\n    TLS Web Server Authentication\n    TLS Web Client Authentication\n    Code signing\n    E-mail Protection (S/MIME)\n    Trusted Timestamping\n    Microsoft Individual Code Signing\n    Microsoft Commercial Code Signing\n    Microsoft Trust List Signing\n    Microsoft Server Gated Crypto\n    Microsoft Encrypted File System\n    Microsoft Smartcard Login\n    Microsoft Server Gated Crypto\n  Subject Alternative Name:\n    DNS: example.com\n    DNS: www.example.com`;\n\nTestRegister.addTests([\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with RSA 1024\",\n        input: IN_EXAMPLE_COM_RSA_1024,\n        expectedOutput: OUT_EXAMPLE_COM_RSA_1024,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with RSA 2048\",\n        input: IN_EXAMPLE_COM_RSA_2048,\n        expectedOutput: OUT_EXAMPLE_COM_RSA_2048,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with EC 256\",\n        input: IN_EXAMPLE_COM_EC_P256,\n        expectedOutput: OUT_EXAMPLE_COM_EC_P256,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with EC 384\",\n        input: IN_EXAMPLE_COM_EC_P384,\n        expectedOutput: OUT_EXAMPLE_COM_EC_P384,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with EC 521\",\n        input: IN_EXAMPLE_COM_EC_P521,\n        expectedOutput: OUT_EXAMPLE_COM_EC_P521,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with DSA 1024\",\n        input: IN_EXAMPLE_COM_DSA_1024,\n        expectedOutput: OUT_EXAMPLE_COM_DSA_1024,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with DSA 2048\",\n        input: IN_EXAMPLE_COM_DSA_2048,\n        expectedOutput: OUT_EXAMPLE_COM_DSA_2048,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with DSA 2048\",\n        input: IN_EXAMPLE_COM_DSA_2048,\n        expectedOutput: OUT_EXAMPLE_COM_DSA_2048,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with various SAN types\",\n        input: IN_EXAMPLE_COM_SAN,\n        expectedOutput: OUT_EXAMPLE_COM_SAN,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with various Key Usages\",\n        input: IN_EXAMPLE_COM_KEY_USAGE,\n        expectedOutput: OUT_EXAMPLE_COM_KEY_USAGE,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse CSR: Example Certificate Signing Request (CSR) with various Extended Key Usages\",\n        input: IN_EXAMPLE_COM_EXTENDED_KEY_USAGE,\n        expectedOutput: OUT_EXAMPLE_COM_EXTENDED_KEY_USAGE,\n        recipeConfig: [\n            {\n                \"op\": \"Parse CSR\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseIPRange.mjs",
    "content": "/**\n * Parse IP Range tests.\n *\n * @author Klaxon [klaxon@veyr.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Parse IPv4 CIDR\",\n        input: \"10.0.0.0/30\",\n        expectedOutput: \"Network: 10.0.0.0\\nCIDR: 30\\nMask: 255.255.255.252\\nRange: 10.0.0.0 - 10.0.0.3\\nTotal addresses in range: 4\\n\\n10.0.0.0\\n10.0.0.1\\n10.0.0.2\\n10.0.0.3\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"Parse IPv4 hyphenated\",\n        input: \"10.0.0.0 - 10.0.0.3\",\n        expectedOutput: \"Minimum subnet required to hold this range:\\n\\tNetwork: 10.0.0.0\\n\\tCIDR: 30\\n\\tMask: 255.255.255.252\\n\\tSubnet range: 10.0.0.0 - 10.0.0.3\\n\\tTotal addresses in subnet: 4\\n\\nRange: 10.0.0.0 - 10.0.0.3\\nTotal addresses in range: 4\\n\\n10.0.0.0\\n10.0.0.1\\n10.0.0.2\\n10.0.0.3\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"Parse IPv4 list\",\n        input: \"10.0.0.8\\n10.0.0.5/30\\n10.0.0.1\\n10.0.0.3\",\n        expectedOutput: \"Minimum subnet required to hold this range:\\n\\tNetwork: 10.0.0.0\\n\\tCIDR: 28\\n\\tMask: 255.255.255.240\\n\\tSubnet range: 10.0.0.0 - 10.0.0.15\\n\\tTotal addresses in subnet: 16\\n\\nRange: 10.0.0.1 - 10.0.0.8\\nTotal addresses in range: 8\\n\\n10.0.0.1\\n10.0.0.2\\n10.0.0.3\\n10.0.0.4\\n10.0.0.5\\n10.0.0.6\\n10.0.0.7\\n10.0.0.8\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"Parse IPv6 CIDR - full\",\n        input: \"2404:6800:4001:0000:0000:0000:0000:0000/48\",\n        expectedOutput: \"Network: 2404:6800:4001:0000:0000:0000:0000:0000\\nShorthand: 2404:6800:4001::\\nCIDR: 48\\nMask: ffff:ffff:ffff:0000:0000:0000:0000:0000\\nRange: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\\nTotal addresses in range: 1.2089258196146292e+24\\n\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"Parse IPv6 CIDR - collapsed\",\n        input: \"2404:6800:4001::/48\",\n        expectedOutput: \"Network: 2404:6800:4001:0000:0000:0000:0000:0000\\nShorthand: 2404:6800:4001::\\nCIDR: 48\\nMask: ffff:ffff:ffff:0000:0000:0000:0000:0000\\nRange: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\\nTotal addresses in range: 1.2089258196146292e+24\\n\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"Parse IPv6 hyphenated\",\n        input: \"2404:6800:4001:: - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\",\n        expectedOutput: \"Range: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\\nShorthand range: 2404:6800:4001:: - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\\nTotal addresses in range: 1.2089258196146292e+24\\n\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"Parse IPv6 list\",\n        input: \"2404:6800:4001:ffff:ffff:ffff:ffff:ffff\\n2404:6800:4001::ffff\\n2404:6800:4001:ffff:ffff::1111\\n2404:6800:4001::/64\",\n        expectedOutput: \"Range: 2404:6800:4001:0000:0000:0000:0000:0000 - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\\nShorthand range: 2404:6800:4001:: - 2404:6800:4001:ffff:ffff:ffff:ffff:ffff\\nTotal addresses in range: 1.2089258196146292e+24\\n\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"IPv4 subnet out of range error\",\n        input: \"10.1.1.1/34\",\n        expectedOutput: \"IPv4 CIDR must be less than 32\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"invalid IPv4 address error\",\n        input: \"444.1.1.1/30\",\n        expectedOutput: \"Block out of range.\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"IPv6 subnet out of range error\",\n        input: \"2404:6800:4001::/129\",\n        expectedOutput: \"IPv6 CIDR must be less than 128\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n    {\n        name: \"invalid IPv6 address error\",\n        input: \"2404:6800:4001:/12\",\n        expectedOutput: \"Invalid input.\\n\\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported.\",\n        recipeConfig: [\n            {\n                \"op\": \"Parse IP range\",\n                \"args\": [true, true, false]\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseObjectIDTimestamp.mjs",
    "content": "/**\n * Parse ObjectID timestamp tests\n *\n * @author dmfj [dominic@dmfj.io]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n\nTestRegister.addTests([\n    {\n        name: \"Parse ISO timestamp from ObjectId\",\n        input: \"000000000000000000000000\",\n        expectedOutput: \"1970-01-01T00:00:00.000Z\",\n        recipeConfig: [\n            {\n                op: \"Parse ObjectID timestamp\",\n                args: [],\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseQRCode.mjs",
    "content": "/**\n * Parse QR Code tests\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Parse QR Code : JPEG\",\n        input: \"ffd8ffe000104a46494600010100004800480000ffe1008c4578696600004d4d002a000000080005011200030000000100010000011a0005000000010000004a011b0005000000010000005201280003000000010002000087690004000000010000005a00000000000000480000000100000048000000010003a00100030000000100010000a002000400000001000001e0a003000400000001000001e000000000ffed003850686f746f73686f7020332e30003842494d04040000000000003842494d0425000000000010d41d8cd98f00b204e9800998ecf8427effc000110801e001e003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffdb004300020202020202030202030403030304050404040405070505050505070807070707070708080808080808080a0a0a0a0a0a0b0b0b0b0b0d0d0d0d0d0d0d0d0d0dffdb004301020202030303060303060d0907090d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0dffdd0004001effda000c03010002110311003f00fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd0fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd1fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd2fdfca28a2800a28a2800a28a2803f3c7f6e8fdba2f3f636bcf06da5a78362f15ff00c25716a72b34ba99d3becdfd9c6d86062dae37effb47fb38dbdf3c7c07ff000fc4d5bfe88f5aff00e14adffcaea4ff0082e27fc85be0f7fd7af89bff0042d3abf03a803f7cbfe1f89ab7fd11eb5ffc295bff0095d47fc3f1356ffa23d6bff852b7ff002babf0505bc8c010539f59107e99a5fb349eb1ff00dfc4ff001a00fdeaff0087e26adff447ad7ff0a56ffe5751ff000fc4d5bfe88f5aff00e14adffcaeafc15fb349eb1ffdfc4ff1a3ecd27ac7ff007f13fc6803f7abfe1f89ab7fd11eb5ff00c295bff95d47fc3f1356ff00a23d6bff00852b7ff2babf057ecd27ac7ff7f13fc68fb349eb1ffdfc4ff1a00fdeaff87e26adff00447ad7ff000a56ff00e5751ff0fc4d5bfe88f5affe14adff00caeafc15fb349eb1ff00dfc4ff001a3ecd27ac7ff7f13fc6803f7abfe1f89ab7fd11eb5ffc295bff0095d5ec5f00bfe0adbaa7c6df8bfe13f85b27c30b6d223f12ea9169cd7cbaf35d35bf9aaedbc45f618c3e3674debd6bf9ad6528db4e3f0208fcc715f61fec0fff00276ff0b7fec67b4ffd153d007f66f4514500078afc2cf891ff000592d57e1f78efc41e0c5f8516b7aba26aba8e9ab727c42d0998585dcd6de614fb03eddfe56edbb8e338c9eb5fba47a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6ee803fa2afd8fbfe0a5da8fed51f17edbe175c7c3e83c371dc69f7f7bf6e8f596be606c9633b3ca36900f9bccebbf8c74afd60afe55ffe090bff002765a77fd80b5dff00d02dabfaa7cfd7f2a00fcaff00db27fe0a45a87eca1f1507c37b6f0041e268ce9561a97db24d61ac0e6f5ee5767962d271f2fd9f39ddceee9c73f39f813fe0b35ab78d7c61a37854fc26b5b41aaea36360671e22694c42f2e62b7de13ec0bbb6f99bb1919c6322be4fff0082c400ff00b51a4419439f0b686c159829204ba8824648ce0919fad7e797c0a4f27e2ff834c8d18dde21d11540914927fb46d8e00073d013401fdcd5140a2800a2933f5fca96803c9fe3afc4a93e0dfc1ef17fc54874f5d59fc2ba4dcea8b64d31b71706dd777966508e53774ddb5b1e86bf152ebfe0b7babdadccd6c7e0fda3186468c9ff008495c6769c7fd03abf56ff006e0ff9344f8b9ff629ea5ffa28d7f179ac7fc85af7febe65ff00d0cd007ef0ff00c3f1356ffa23d6bff852b7ff002baa7b7ff82deeaf70ec83e0fda8db1cb27fc8cae7fd5a33e3fe41ddf18afc0b485e4190547fbccabfcc8ad1d3a3f26791e578d57ecf723fd6275685c01d7a92702803fbd2d16fceaba4596a853cb3796d0ce501cedf3503e33c6719c56a5737e0eff914f45ffb075a7fe894ae92800afc9ffdb07fe0a5da8fecaff17ee7e175bfc3e83c491dbe9f617bf6e93596b1626f5643b3ca16938f97cbebbf9cf4afd5fcfd7f2afe567fe0af0bbff6b5d4630ca18e83a190198292025ce48c91d322803e9dff0087e26adff447ad7ff0a56ffe5751ff000fc4d5bfe88f5aff00e14adffcaeafc1436f2004929c7fd3443fd6a0a00febb3f61efdba2f7f6c4d4bc536177e0c87c283c39696174ad16a6750f3fedb2dc45b4e6dadf66cfb3e7f8b3bbb639fd10afe7cff00e0893ff21ef89bff00608d0fff004af51afe832803f15fe3effc15b754f825f17fc59f0b63f8616dabc7e1ad525d396f9b5e6b56b8f2951b798bec3204cefe9bdba578effc3f1356ff00a23d6bff00852b7ff2babf34bf6f8ff93b7f8a5ff633ddff00e8a82be3c552edb463f1200fccf1401fbe1ff0fc4d5bfe88f5affe14adff00caea3fe1f89ab7fd11eb5ffc295bff0095d5f82bf6693d63ff00bf89fe347d9a4f58ff00efe27f8d007ef57fc3f1356ffa23d6bff852b7ff002ba8ff0087e26adff447ad7ff0a56ffe5757e0afd9a4f58ffefe27f8d1f6693d63ff00bf89fe3401fbd5ff000fc4d5bfe88f5aff00e14adffcaea3fe1f89ab7fd11eb5ff00c295bff95d5f82bf6693d63ffbf89fe347d9a4f58ffefe27f8d007ef57fc3f1356ff00a23d6bff00852b7ff2ba8ff87e26adff00447ad7ff000a56ff00e5757e0afd9a4f58ff00efe27f8d21b7914124a71e9221fd33401fbd7ff0fc4d5bfe88f5affe14adff00caeafbf3f617fdba2f3f6c9bcf195a5df8362f0a7fc22916992ab45a99d47ed3fda26e460e6dadf66cfb3ffb59dddb1cff002235fbe3ff00043bff0090b7c61ffaf5f0cffe85a8d007f417451450014514500145145007ffd3fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf056dc06908233f2487f2535fbd5ff0005c4ff0090b7c1effaf5f137fe85a757e0b5b7fac3ff005ce4ff00d00d007f693f03be07fc17bdf82fe02bdbdf0178667b8b8f0ce912cb2c9a45a3c9248f6913333318892cc49249e49af53ff850bf03bfe89e785bff0004d67ffc6a93e027fc90df879ff62b68dffa47155ff8b7f16fc0bf03fc0b7df123e245f49a7681a7496f15c5c456f35d3abdd4c9044045023c8dba4751f2a9c67278a00a3ff0a17e077fd13cf0b7fe09acff00f8d51ff0a17e077fd13cf0b7fe09acff00f8d57c99ff000f4afd8b3fe872bfff00c27f55ff00e45a3fe1e95fb167fd0e57ff00f84feabffc8b401f59ff00c285f81dff0044f3c2dff826b3ff00e3547fc285f81dff0044f3c2dff826b3ff00e355f267fc3d2bf62cff00a1caff00ff0009fd57ff009168ff0087a57ec59ff4395fff00e13faaff00f22d007d67ff000a17e077fd13cf0b7fe09acfff008d521f80bf03b07fe2de785bff0004d67ffc6abcefe06fed83f013f68dd7751f0e7c24d76e356bfd2ed16faea39b4dbcb109033f961835cc31ab65b8c2926be9c3d0d007f1c5ff000511d0b44f0e7ed71f11349f0fe9f6ba658dbea96e90db59c296f0c6a74db1721238c2aa8dcc4e00ea49ef58dfb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd145140087a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6eebfb8935fcbe7c5ff00f8263fed71e37f8a3e2df14e93e17b06b0d535fd62f6cddf59b48d9edeeefee27899909254b2480e09c8ef8390003f2bbc27e34f177817545d6fc19adea5a0ea0a8f10bad2ef26b19c24980ea258191c2b606e19c1c0cf4af4eff869cfda1ffe8a778d7ff0a4d4bff922bdafe317fc13e7f693f81be04bbf88bf10342b3b2d1ace7b5b69658754b6b97125e4ab0c40469f310646009edd4d7c3f401fd5bffc13a3c29e18f8bdfb345878cfe2c69367e36f103eb3abd9b6ade2381357d41adadae5d6189ae6ec4b3347102422962172718c9afbc22f815f04e09a2b883e1ff85e396191258dd747b30c9246c1d19488b219580208e41008af8bff00e0949ff2687a77fd8c3af7fe95b57e92d0015f92ff00f0574f1c78d3c0bf07fc17a87827c41ab787aea7f11dc4734fa45fdc69f2c91a69b7520477b778d9977a2b6d271902bf5a2bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2803f0574efda5bf684b8d42da097e2778d4a4b3468c3fe124d4fa33007fe5e2bfae3fd8db57d5fc41fb2c7c2dd6f5ebeb9d4b51bef0c69f3dcddddcaf3dc4f2bc796792490b3bb31e4b3124d7f159a3ffc85acbfebe62ffd0c57f687fb0fff00c9a27c23ff00b14f4dff00d142800fdb83fe4d13e2e7fd8a7a97fe8a35fc5e6b1ff216bdff00af997ff4335fdb7fed49e08f12fc4afd9dbe21f803c1d6cb79adebfe1fbdb0b0b769121124f326d452f2154504f762057f37179ff04a2fdb22eaee6ba3e16d394cd23c981ae5a1037126803efdff008242fc35f877e31f837e34bcf16f85b45d6ae21f11db4714ba8e9f6f752221d32d1caab4a8c402cc588071924f5afd6b93e00fc0b951a297e1df851d1d4ab2b68b66432b0c10418b90475afca7fd92bc59a0ff00c1387c09ab7813f6b4b83e16d63c59aaff006b6910d8c336b493d95a5a5ada4923496114a232255c159029e463239afaa3fe1e95fb167fd0e57fff0084feabff00c8b401fa0d0c315bc490408b1c71a8444501555546000070001d057e7e7fc14e7c51e26f087ecaba86b3e12d6350d0f505d7745885e699773595c88e5b90aea2581d240194e0807914cff87a57ec59ff004395ff00fe13faafff0022d782fed29f1dbe1a7ededf09efbe027ecc3a94be25f1acb7763ad2595e59dd69109b2d32e236b87fb45ec314594f3106d04b1dc30a6803f9fdff00869bfda1f3ff00253bc6bff8526a5ffc915fd2affc13dfc1be12f8affb30e85e33f8a7a358f8cfc433ea5ad5bcdabf886dd355d46586d7509e386392eaec4b33ac4802a0672157818afc66ff00874dfed8ff00f42be9dff83cb4afd4ff00d9cff687f855fb0b7c25d2bf67dfda5f549bc37e39d36e350d4eeac2d2caef56852db55bc9ee2d985cd9432c2dbd0f4c86041c81401f767c4df819f052dbe1bf8aee6dbc01e188a68744d45e391347b456475b7908652220410790457f13d7aaab3285000f2613c7a98d49fccd7f573e3dff00829bfec6fadf81bc45a2e9fe30be7babfd26fad6053a0ea8a1a59a0744059ad40196206490077afe512ee449250c872047129faaa2a9fd45007ef3ff00c1127fe43df137fec11a1ffe95ea35fd0657f3e7ff000449ff0090f7c4dffb04687ffa57a8d7f419401fc647edf1ff00276ff14bfec67bbffd15056cff00c13bb42d13c47fb5c7c3bd27c41a7daea76371aa5c24d6d790a5c43228d36f9c078e40cac37283823a807b5637edf1ff00276ff14bfec67bbffd150575ff00f04d5ff93c8f86bff616b9ff00d35ea1401fd578f80bf03b03fe2de785bff04d67ff00c6a97fe142fc0eff00a279e16ffc1359ff00f1aaf571d057cc7f1cbf6c1f809fb396bba77873e2debb71a4dfea968d7d6b1c3a6de5f07815fcb2c5ada191570dc61883401e8bff000a17e077fd13cf0b7fe09acfff008d51ff000a17e077fd13cf0b7fe09acfff008d57c99ff0f4afd8b3fe872bff00fc27f55ffe45a3fe1e95fb167fd0e57fff0084feabff00c8b401f59ffc285f81dff44f3c2dff00826b3ffe3547fc285f81dff44f3c2dff00826b3ffe355f267fc3d2bf62cffa1cafff00f09fd57ff9168ff87a57ec59ff004395ff00fe13faafff0022d007d67ff0a17e077fd13cf0b7fe09acff00f8d57967c71f81ff0005ecbe0bf8f6f6cbc05e1982e2dfc33abcb14b1e91689247225a4acacac220432900823906bd87e127c5bf02fc70f02d8fc48f86f7d26a3a06a325c456f712dbcd6aecf6b33c128314e8922ed91187cca338c8e2a87c7bff00921bf10ffec56d67ff0048e5a00fe18ee005900031f2467f3515fbd5ff00043bff0090b7c61ffaf5f0cffe85a8d7e0b5cffac1ff005ce3ff00d0057ef4ff00c10eff00e42df187febd7c33ff00a16a3401fd05d14514005145140051451401ffd4fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054aff00932cf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe44dddf7b7cc7a9ef4df31ffbc7f3a1fefb7d4d32801fe63ff78fe74798ff00de3f9d328a00fdbcff008228127e2ef8e8939ff8a5a0ff00d2e35fd1f9e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd1457e55ff00c1467f6cef8b5fb296b1e0bb3f86b06892c1af58ea77578756b296ed835a4d6b14623f2ae6df6822762d9dd9c0e9401faa9457f2f51ffc163bf6a598b2c763e0edc2391c6747ba00ec52d8cff689eb8afe973c0fabddf883c17a06bfa86c175a9e976779388c6d4f327851df682490bb98e064e05007c35ff0547ff933df117fd863c3dffa73b7afe448f5afee6fe3a7c12f07fed09f0eaf3e1878ee4bd8b47beb8b4b995b4f985bdc6fb399678f6c855f68de833819c7420f35f0b7fc3a17f64eff009ede2bff00c1bfff006aa00e93fe0949ff002687a77fd8c3af7fe95b57e92d78c7c06f815e0bfd9d7e1f43f0d3c0325f49a44177757a87519c5c4fe65dbf9926640a991bba6467d49af67a002bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2bf646bf1bbfe0b3dff00244bc0ff00f6335d7fe9aaf2803f9a5d1ffe42d65ff5f317fe862bfb43fd87ff00e4d13e11ff00d8a7a6ff00e8a15fc58dbcef6b7115cc78df13abae791953919afd32f85fff000552fda1fe147c3cf0efc35f0d597859b4af0d69f069b686ef4bb99a730c0bb54c922dfc6acd8ea42283e82803fac4a2bf977ff87c9fed45ff003e3e0eff00c135d7ff002ca8ff0087c9fed45ff3e3e0effc135d7ff2ca803dbbfe0b6848f197c3920e3fe243aaff00e96d9d7e10798ffde3f9d7f47ff00bc1da37fc1527c2179f10ff00696f32df53f056a0da2e98be1677d2a06b4bdb6b5bd733248f74cf2798c0021c2e147cb9e6bdc3fe1d0bfb270c7ef7c57d7fe82fff00da6803f950f31ffbc7f3afd6cff823ab16fda81b7127fe298d77affd75d36bf347e29f8734ef097c41f10f87349f33ec9a6eada959c3e6b6f7f2ad6f2781371c0c9d918c9ee726bf4b7fe08e9ff2740fff0062c6bbff00a374da00fea3abf957ff0082bc92bfb59ea3b4e3fe245a174ff72e6bfaa8af86fe3c7fc13ebe047ed17f10a7f897f10a6d786ad71696d64cba7df8b680456818261046c777ce72493ed8a00fe3a77bff0078fe74dafeacff00e1d0bfb277fcf6f15ffe0dff00fb551ff0e85fd93bfe7b78afff0006ff00fdaa803e3cff008224ff00c87be26ffd82343ffd2bd46bfa0caf937f66efd8d3e117ecb3a86bba97c329758793c436f6b6f76ba9de0bb50966f2bc7e5feed0a9cccd9e483e80e73f595007f191fb7c7fc9dbfc52ff00b19eefff0045415d7ffc1357fe4f23e1affd85ae7ff4d7a85721fb7c7fc9dbfc52ff00b19eefff0045415d7ffc1357fe4f23e1affd85ae7ff4d7a85007f60e3a0afe703fe0b5e48f8bbe0520e3fe2969ff00f4b857f47e3a0afe6fff00e0b61ff2577c0bff0062bcff00fa5c2803f11fcc7fef1fce8f31ff00bc7f3a651400ff0031ff00bc7f3a723bef5f98f51dea2a7a7df5fa8a00febbbfe096bff2659e0dff00affd7fff004eb775f597c7bff921bf10ff00ec56d67ff48e5af937fe0969ff002657e0dffaff00d7ff00f4eb775f597c7bff00921bf10ffec56d67ff0048e5a00fe192e7fd60ff00ae71ff00e802bf7a7fe0877ff216f8c3ff005ebe19ff00d0b51afc16b9ff00583feb9c7ffa00afde9ff821dffc85be30ff00d7af867ff42d46803fa0ba28a2800a28a2800a28a2803fffd5fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe445fefb7d4d329eff7dbea69940051451401fb77ff00044fff0092bbe3affb15e0ff00d2e35fd201e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd7e04ffc167f41d6f5df11fc328747d3eeef58693ae2b7d96da5b80a4dd5811bbca47db90a719f4afdf6a69556ea01a00fe11edfe1c78eadd9e59340d57688661ff20ebbead1b01d6103a9ef5fdbf7c2d0cbf0cfc228e0ab2e85a68208c1045b47c107a5773e5a7a0fc853e800a28a280336e759d26ce5f22f2f2de09300ec925446c1e870c41a80788741660aba8da1248000b88f249e83ef57f31dff0005859043fb5224ab1c6cff00f08b686a19e3572019751240dc0e33819fa0afcf3f819706e7e2ef839658e1f97c43a23295891483fda36c382003d091f4a00fee5abf1bbfe0b3dff244bc0fff006335d7fe9aaf2bf64057e37ffc167bfe489781ff00ec66baff00d355e5007f32945145005ed3b4dbed5af23b0d3a096e6e263b638a18da576382701103313804e002715d77fc2b2f1eff00d00355ff00c175dfff0019afabff00e09bff00f2781f0d7fec36dffa6ebfafec3446981f28e9e82803f1d3fe08faa7c33f06fc6f67e220da54ede25b7291df2b5a3baae996a85952608c5772919c60e38afd75ff00848b403c7f69d9ff00e0447ffc557f3fff00f05b02b178d7e1bc8123629a16ac577a2b004de5a0c80475c122bf0e6c2e9ae2592296280a9b7b93c431820ac2e41042e4104645007b87c6af01f8c755f8ade2ebcb1d13529a17d7f592922585cba3abea172eacae9132b2956041048e6bef9ff824ae83ac7853f69b6b8f12595d69907fc233ad0f3af6de5b588b3cb61b543cc91a963b18e01ce057f4a3e0f443e13d17e51ff20eb4edff004c52bf3f7fe0ab91c67f642d49591581f10e84082a0820dd2e4104720d007e877fc247a07fd04acfff000223ff00e2ab4ad6eed6f62f3ed258e78c9203c6e1d723af2b91c57f035fda137fcf383fefc47ffc4d7f5bbff04b6444fd8efc3891aaa28d5fc4202a80000353b8c0007000a00fd10a28a2800a28a2803f8c8fdbe3fe4edfe297fd8cf77ffa2a0aebff00e09abff2791f0d7fec2d73ff00a6bd42b90fdbe3fe4edfe297fd8cf77ffa2a0aebff00e09abff2791f0d7fec2d73ff00a6bd42803fb071d057f37fff0005b0ff0092bbe05ffb15e7ff00d2e15fd200e82bf9bfff0082d87fc95df02ffd8af3ff00e970a00fc44a28a2800a7a7df5fa8a653d3efafd45007f5ddff04b4ff932bf06ff00d7febfff00a75bbafacbe3dffc90df887ff62b6b3ffa472d7c9bff0004b4ff00932bf06ffd7febff00fa75bbafacbe3dff00c90df887ff0062b6b3ff00a472d007f0c973feb07fd738ff00f4015fbd3ff043bff90b7c61ff00af5f0cff00e85a8d7e0b5cff00ac1ff5ce3ffd0057ef4ffc10effe42df187febd7c33ffa16a3401fd05d14514005145140051451401fffd6fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa471579dfed83f0375dfda37e026bbf093c39a8da6957fab5c69b347757caed020b1bc86e58308fe63b9622063b9eb5e87f0108ff00851bf0f3fec55d1bff0048e2af5ac8a00fe6fcff00c1143e2e9249f1d785b9ff00a617dfe347fc393fe2effd0f5e16ff00bf17dfe35fd20645191401fcdfff00c393fe2eff00d0f5e16ffbf17dfe349ff0e4ff008bbff43d785bfefc5f7f8d7f483914645007e58fec15fb05f8f3f647f1e788bc53e29f1168facdaeb3a3c7a745169d1dc2491c897026dcde7020a9191c1f4e2bf534f434b914d2460d007f1f3ff052aff93c8f895ff616b6ff00d35e9f5c87ec0fff00276ff0b7fec67b4ffd153d75dff052aff93c8f895ff616b6ff00d35e9f5c8fec0fff00276ff0b7fec67b4ffd153d007f66f5f16fed59fb6e7807f64ad47c3da778d340d67596f11db5e5cc0fa59b50b1259490c6fe61b99e1e4b4ebb76e7bf4afb4abf9f3ff82db7fc87be197fd8235cff00d2bd3a803e81ff0087d07c04ff00a12bc59ff7de95ff00c9f4bff0fa0f809ff42578b3fefbd2bff93abf98dab42caf5806582520f20843cfe9401fd71fecdfff00051cf85dfb4cfc4cb7f85fe11f0cf8834cbeb9b2bbbd5b9d40d91b7096610ba9fb3dcccfb8ef5c7cb8f7afd0fafe56ff00e09150cb07ed67a68991a32741d771b815fe0b6f5afea8f23d6803f21bf6ddff0082757c44fdaa3e327fc2c5f0e789b43d234f1a369fa77d9f508ee5e732d9bdcb17cc3850a44f81c93c76eff2c785bfe090df163e1c788b4df1ddcf8d3c35716fe1fbcb5d5a782186f165962d3a78ee9a342f950ce22da0918c9e7d6bfa21eb5cdf8c7fe453d6bfec1d77ff00a25e803f268ffc1677e032852de08f172ef557019b4b076b80cbc1bec8c820e0f35c3fc46f1b695ff0565f0d45f0dfe0f4371e0dbbf03de47aededc789d62920b9b7bd82eac56384584f3b6f0c589de54003be715fcebeadff001f31ff00d7b5affe884afdceff0082257fc8e3f11bfec03a57fe96de5007327fe08a1f1740ff0091ebc2dff7e2fbfc6bf2afe3f7c1bd57e02fc55f107c2dd66fadb51bbf0fde0b396e6d15d61918c10cf94127cc06d980e79c83ed5fdca1e86bf8efff00828fff00c9e07c4aff00b0e2ff00e9bac2800ff82707fc9e07c35ffb0e37fe9bafebfb101d057f1dff00f04e0ff93c0f86bff61c6ffd375fd7f6203a0a00fe797fe0b6bff238fc39ff00b00eabff00a5b675f855633a5bcecefd1a29a3fa19236407e809e6bf757fe0b6bff238fc39ff00b00eabff00a5b675f83d401fd2ee85ff000591f80fa5e8ba7e9b27833c58cf6b6b040cc1f4bc168e3552466f81c64771f957cc3fb6a7fc14a3e137ed1df02eefe19785bc35e20d33509f54d36f96e2fdac0c012ce612ba9f22ea5932c0617e5c67a902bf12858de91916f2907fd86ff0a6496b7312ef9629117a659481fa8a008475afebb7fe0971ff00267be1dffb0c7887ff004e7715fc890eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2803f43a8a2932280168a4c834b401fc647edf1ff00276ff14bfec67bbffd150575ff00f04d5ff93c8f86bff616b9ff00d35ea15c87edf1ff00276ff14bfec67bbffd150575dff04d5ff93c8f86bff616b9ff00d35ea1401fd840e82bf2cbf6f5fd82fc79fb5c78f3c3be29f0b788b47d1ad746d1e4d3a58b518ee1e49247b8336e5f2400140c0e4faf15fa9808c0a7645007f37dff000e4ff8bbff0043d785bfefc5f7f8d2ff00c393fe2eff00d0f5e16ffbf17dfe35fd20645191401fcdff00fc393fe2effd0f5e16ff00bf17dfe340ff0082287c5d0411e3af0b71ff004c2fbfc6bfa40c8a322803e62fd8fbe06ebbfb397c04d0be12788f51b4d56ff49b8d4a692eac55d60717d7935ca85127cc36aca01cf71d6bd13e3dff00c90df887ff0062b6b3ff00a472d7ace45792fc7b23fe146fc43ffb15759ffd2396803f865b9ff583feb9c7ff00a00afde9ff00821dff00c85be30ffd7af867ff0042d46bf05ae7fd60ff00ae71ff00e802bf7a7fe0877ff216f8c3ff005ebe19ff00d0b51a00fe82e8a28a0028a28a0028a28a00ffd7fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf04158a1cafa11f81183fa57f4a3ff00056df805f183e36ea9f0c24f85be14d4fc4b1e8f6daf2df369d12cbe435d358f941f73a63788df18feed7e38ff00c303fed6dff44b7c4fff008091ff00f1fa00d9d0bfe0a23fb5c787344d3fc3da4fc44d52dec74bb586ceda14b7d34ac70dba08e3405ac59b0aaa00cb13ea4d6aff00c3cabf6c8ffa295ab7fe03697ffc815c8ffc303fed6dff0044b7c4ff00f8091fff001fa3fe181ff6b6ff00a25be27ffc048fff008fd0075dff000f2afdb23fe8a56adff80da5ff00f2051ff0f2afdb23fe8a56adff0080da5fff00205723ff000c0ffb5b7fd12df13ffe0247ff00c7e8ff008607fdadbfe896f89fff000123ff00e3f401d77fc3cabf6c8ffa295ab7fe03697ffc8147fc3cabf6c8ff00a295ab7fe03697ff00c815c8ff00c303fed6dff44b7c4fff008091ff00f1fa3fe181ff006b6ffa25be27ff00c048ff00f8fd0075dff0f2afdb23fe8a56adff0080da5fff0020527fc3cabf6c8ffa295ab7fe03697ffc815c97fc303fed6dff0044b7c4ff00f8091fff001fa3fe181ff6b6ff00a25be27ffc048fff008fd007cebf123e2478c3e2cf8c351f1df8ef519755d6b559566bbbb99634795d23484332c291c6088e345f9514614719c93f457ec0ff00f276ff000b7fec67b4ff00d153d1ff000c0ffb5b7fd12df13ffe0247ff00c7ebe98fd8f3f63cfda5be1f7ed2df0e7c57e2bf875e20d3748d37c416d737979736c890c10a24aa5dcacac4005864e2803fa9fafe7cff00e0b6dff21ef865ff00608d73ff004af4eafe832bf9f3ff0082db7fc87be197fd8235cffd2bd3a803f066cd16499838c8114ac3eab1b107f022bfaf9f879fb057ec81ab7807c35aaea3f0bf459aeaf347b0b89e5612e5e596046763fbcea58926bf907b26559cee38cc5328f72d1b003f12715fd90fc39fdb1bf655d37e1ef8634ebff8b3e1082e6d746d3e19a29357b7578e48edd1595817c82a41047ad007ce1fb5bfc16f85bfb227c0ed63e357ecdbe1db3f0178dec6f34bd3edf5ad3503dc476ba8dec305cc616e3ce888923623e68db0704722bf157fe1e53fb647fd14ad5bff0001b4bffe40afdbcfdb5be2a7c36fda5ff67bd6fe15fecfde27d27e2178c6eafb48bd8343f0f5ec37b7f2dbd8df4335c48b1abfdd8e352cc4900773c8afc15ff8607fdadbfe896f89ff00f0123ffe3f401fd28ffc13bbe28f8ffe30fecd965e35f895accdaf6b72eb5abdabde5c2431bb436d7063894ac11c51fcaa3b28cd7d85e31ff914f5affb07ddff00e897af8bff00e09bdf0fbc6ff0cbf663b1f0a7c41d12f3c3fac47adeb170f657e82399629ee0bc6c402c30ca72082457da1e31ff00914f5aff00b07ddffe897a00fe0eb56ff8f98ffebdad7ff44257ee77fc112bfe471f88dff601d2bff4b6f2bf0c756ff8f98ffebdad7ff44257ec37fc123be317c2af84be27f1ddefc4ef16691e1682fb46d360b57d5aee3b459a58aeee9dd10c840665575240e8181a00fe988f435fc77ffc147ffe4f03e257fd8717ff004dd615fd399fdb4ff64ac1ff008bbde0dffc1c5b7ff175fcb57edebe2ef0b78e7f6a3f1f78a3c1bab59eb7a46a1ab89ad2fac6659ede78fec3671ee8e4525586f465c8e32a476a00e8bfe09c1ff2781f0d7fec38dffa6ebfafec407415fc66fec15e2ef0b781bf6a3f00f8a3c65ab59e89a469fab99aeefafa6582de08fec3791ee924621546f755c9e32c077afea547eda7fb25607fc5def06ffe0e2dbff8ba00fc82ff0082daff00c8e3f0e7fec03aaffe96d9d7e16e9d124b70cb20c8582e1c76f99227653f8100d7ef97fc1483c33ac7ed87aef8375ffd98adcfc4dd2b43d3751d3b53bdf0cbc57f059ddcd716b3c714ae24550ed1a16db9ce3071820d7e665afec21fb5b5a3c92ffc2abf13b930cd181f648c64c91b20ff0096c7b9f43401fd24f863f603fd8eef7c39a55ddcfc2dd1249a6b1b69247226cb3bc4a589fde7524e6be31ff828bfec8dfb37fc25fd9a2ffc61f0e7c07a6685acc7ad68f6cb796a24122c3717212551b9c8c3a9c1e3a57e81e8bfb5e7ecbda069165a16b7f153c2763a8e9d6d0da5ddadc6ab04734171022a491488cc0aba3a95652320820f35f29fedc9f12be1ff00ed41f002ff00e177ecefe23d2fe22f8b9f54d2f515d17c3b790df5e9b3b3b9579e6f2d1f848c119624004819c91401fcb00eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2bf9d6ff8607fdadbfe896f89ff00f0123ffe3f5fbd5fb14fc54f86dfb347ecf7a27c2bfda07c4fa4fc3df18dadf6af7b3e87e21bd86cafe2b7bebe9a6b791a367fbb246c1948241ec783401fa39f10f50bdd27c03e25d574d99adeeecb47bfb8825500b472c503b230c8232ac01190457f2572ff00c1493f6ca80a46df13355663144cc45ae96012e8ac703ec070327d6bfa35f88dfb637ecaba97c3df13e9d61f167c213dcdd68da8430c51eaf6ecf24925bbaaaa80f92589000f5afe37af595a71b4e71142a7d8ac6a08fc08c5007f4b7ff04aff00da53e35fc7ed77c7d17c59f155df88a0d2b4dd227b18eea1b58fc892e2e2f2394a9b6820cee5853ef6718e3ad7ecb57f3e7ff0449ff90f7c4dff00b04687ff00a57a8d7f419401fc647edf1ff276ff0014bfec67bbff00d15057cebf0dfe2478c3e1378c34ef1df81351974ad6b4a95a6b4bb85637789de3784b2acc9246498e475f9918618f19c11fa45fb61fec79fb4b7c41fda5be2378afc29f0ebc41a9691a97882e6e6cef2dad91e19e1748943a16954904a9c1c57ccfff000c0ffb5b7fd12df13ffe0247ff00c7e803adff0087957ed91ff452b56ffc06d2ff00f90297fe1e55fb647fd14ad5bff01b4bff00e40ae47fe181ff006b6ffa25be27ff00c048ff00f8fd1ff0c0ff00b5b7fd12df13ff00e0247ffc7e803aeff87957ed91ff00452b56ff00c06d2fff009028ff0087957ed91ff452b56ffc06d2ff00f902b91ff8607fdadbfe896f89ff00f0123ffe3f47fc303fed6dff0044b7c4ff00f8091fff001fa00ebbfe1e55fb647fd14ad5bff01b4bff00e40a3fe1e55fb647fd14ad5bff0001b4bffe40ae47fe181ff6b6ff00a25be27ffc048fff008fd1ff000c0ffb5b7fd12df13ffe0247ff00c7e803aeff0087957ed91ff452b56ffc06d2ff00f902b2b5dff8288fed71e23d1350f0f6adf11354b8b1d52d66b3b985edf4d0b2437086391095b156c32b1070c0fa1158dff0c0ff00b5b7fd12df13ff00e0247ffc7e8ff8607fdadbfe896f89ff00f0123ffe3f401f1e3317396f403f00303f4afdefff00821dff00c85be30ffd7af867ff0042d46bf34ffe181ff6b6ff00a25be27ffc048fff008fd7ec77fc124be017c60f825aa7c4f93e29785353f0d47ac5b682b62da8c4b179ed6ad7de684daef9d8244ce7fbd401fb53451450014514500145145007ffd0fdfca28a2800a28a2800a28a2800a28a2800a2bf0abe25ff00c161b5ef87df117c55e051f0e34bb94f0e6b9a9e8e93c9ad5cc6f32e9f7525b891916c64552fe5eeda19b19c64d713ff000fb6d7bfe899691ff83dbbff00e575007f417457f3e9ff000fb6d7bfe899691ff83dbbff00e5757eacfec69fb48ea3fb537c239be276a5a15bf87a44d62f34c5b4b7ba7bc42b6823fde798f14272c5cf1b78007738001f5951451400514507819a0028afc69fda53fe0aa1aefc00f8d7e2af84d0f8074ed520f0eddc56b1df4fabdc5bc93f996b05c9631476532ae3cfdbf7ce719e2b1be037fc15975ff8d1f183c23f0bdbe1ee99a7c5e25d561d3a4bc8758b89dedd65576de237b28836021e370e6803f6bebf9f3ff82db7fc87be197fd8235cff00d2bd3abfa0caf85ff6befd877c39fb5dea7e1ad4b5ff0014df787bfe11bb5bdb548eced60b813adec9048c5fcece369817181dfad007f1d757d755d4d542addce00180048d8007e35fd1d7fc394be198ff009a91ad751ff30bb1ff00e26bf9f8f8abe0fb7f00fc43f11783ad2e1eea1d1b57d4b4e8e67508d22d8de4d6cae557805c441881c027038a00fd19ff0082465d5cdd7ed69a6b5ccaf295d075e00bb16c7c96deb5fd50d7f153fb23fed2573fb2e7c56b7f89969a2c1aec9058df590b5b8b992d2322f563058c91c533657cbe06c39cf518afd4bff87db6bdff0044cb48ff00c1eddfff002ba803fa0bae6fc63ff229eb5ff60fbbff00d12f5e09fb21fed037ff00b4cfc1bb7f8a7a968b068134fa95fd81b2b7b97bb8c0b297cade2578e263bf19c6c18afa3b57d3c6ada5de696ce621776f2c05c0c95f350a671c6719cd007f04fab7fc7cc7ff005ed6bffa212a9c175736a49b695e22c304a315c8fc2bfa433ff0454f86ee13cdf895adbb2a2264e996478450a3a827a0ee49f7af863f6eeff827df847f650f00787fc5da078b2ff5d9b58d5a6d3de1bbb3b7b758d23b39ae770308049262db8391839eb401f95bfdadaaff00cfe4ff00f7f1bfc6a94b2c933996676776e4b31c93f526a3a280248a5921712c2ec8ebc8653823e8455dfed6d57fe7f27ffbf8dfe35eeffb2cfc1cd37e3cfc6ff0a7c2ed5b509b4bb5d7efcd9c97504692c91016d713ee5493e4273005c1e3049ea057ee00ff0082297c33233ff0b235affc15d8ff00f13401d37fc11964925f82be3a92562eede26b52598e493fd9567d49afd8d35f865e2bf1ec7ff048fb1b3f873e14b31f112d7c74f71afcd79ab5c7f653d9b592dad888512d6de7575652a724291839ce45705ff0fb6d7bfe899691ff0083dbbffe575007e3c7c74bfbeb7f8b9e308edee258d3fe121d6ced472a32752baec0d7e85ffc11eeeae6ebf6a22d732bca57c2fae805d8b6079ba77ad7e5cf8f7c527c6be2fd5fc52d6eb6a755bfbdbe30ab17119bcb896e0a86201214c9b4120120670338afd3ff00f823a7fc9d03ff00d8b1aeff00e8dd36803fa8eafe57bfe0ae775736bfb5a6a4d6d2bc45b41d04128c573f25cfa57f5435f9a9fb507fc136fc21fb4f7c54b9f8a3aef8d352d167b8b1b3b1fb1dad95b4f105b30e15b74d96c9f30e7a7e3401fc9eb6aba9b2956bb9c82304191b041fc6a857f44de30ff8235fc37f0e784b5bf1043f113589a4d334ebbbc48db4db250ed6f13481490b90095c64735fcf05c4421902039ca46fff007da86c7e19a00fde9ff8224ffc87be26ff00d82343ff00d2bd46bfa0cafe7cff00e0893ff21ef89bff00608d0fff004af51afe832800a2bf143e3cff00c15975ff0082ff00183c5df0bd7e1ee99a845e1ad566d3a3bc9b58b881ee162546de634b2942e438e371e6bc8ffe1f6daf7fd132d23ff07b77ff00caea00fe82e8afe7d3fe1f6daf7fd132d23ff07b77ff00caeafd39fd88ff006acd47f6b5f00ebfe34d47c3b6de1c6d1f591a5a416b7925e24aa6d60b9126f9218083fbedbb76f6eb401f69514d63b549f415f819af7fc1683c47a0eb7a868f2fc33d249b2bbb9b5dcdae5d02df6795e2dd81a7b01bb667193d6803f7d68afcabfd8c7fe0a33ac7ed5df16a6f86b79e0bb1d0608b45bdd585e5aea735e396b496da2f2cc725ac0006fb46770638dbd39afd54a0028a28a0028a28a0028a28a0028a28a0028a28a00fffd1fdfca28a2800a28a2800a28a2800a0d141a00fe1e3f69cff009388f89fff0063af893ff4e5715e155eebfb4e7fc9c47c4fff00b1d7c49ffa72b8af0aa002bfab3ff82427fc9a74dff635eaff00fb46bf94cafeacff00e0909ff269d37fd8d7abff00ed1a00fd49a4cfd7f2ac8f10b32685a8ba31565b49c865382088db9047435fc4a4ff00b467c73b63144be3df14bfee6262cde20d5324b20249c5d81c93d801401fdc167ebf95213c1ebf957f0eff00f0d27f1d3fe87bf147fe143aafff0025d4f6bfb47fc729ae6289fc77e28daeeaa7fe2a1d57a138ff009fba00f7ff00f829242d3fed95f13111e30cbaada92af2221c369761838620e0e0f35c8fec1b09b7fdadfe16798f165bc4d6980b2a39e239bb2927b8fcebfa4bfd887c2be18f1bfeca7f0e3c57e34d22c75fd6f53d27cfbdd4b54b68ef6f6ea532c837cd3ccaf2c8d80065989c003a015f59d9fc38f87ba75dc37fa7786346b5bab771243343a7dbc7246e3a32b2a02a47620e6803b4a4cd2d7e137fc1623e2278ebc07e21f86ede0df10eafa22cfa56b524c9a6ea37560b33c77360a8d20b6962de543b05dd9c64d007eeb93f5ea2bf877fda5011f1d7c780f1ff001547887ff4eb794a3f694f8ea0e478f3c51c7fd4c3aaff00f25d78cea3a8deead7b3ea3a8cf25cdcdcc8f2cb2caed248f248c599999896666624b3124b124924926802951451401fd6bffc1293fe4d0f4eff00b1875eff00d2b6afd25afcdaff0082527fc9a1e9dff630ebdffa56d5fa07e2e778fc2dac491b3232d85d156524302227208239045007439fafe55f8ddff059e23fe149f817732a6ef13dca82ec1172da5de0032d81c9381ef5f82377fb457c72b478e14f1ef8a587d9edd8b3788354c96789189e2ec0e493d062bf5fff00e0929e22d6fe3178b7c7b6ff00152faefc5f6f65a369b716906bf733eaf15b4d25d5d46f242b7b24fe5b3a22ab15c640e6803f047ec13ff7a1ff00bff17ff1555648da26d8f8cfa82181fa11907f0afeef0fc2bf86183ff148683ff82cb6ff00e375fc8eff00c143f4cd3748fdadbe2358695690595b43ad2ac70dbc6b1468bfd9f6270aa80003249e07527d6802f7fc1383fe4f03e1affd871bff004dd7f5fd880e82bf8eff00f82707fc9e07c35ffb0e37fe9bafebfb101d05007f3cff00f05b101fc6bf0de20e8acfa16aa143baa648bcb438058819c027f0afc29fb04ffde87feffc5ffc557f799ad783bc25e249a2b8f11e89a76ab2c0a5227bdb48ae19158e4aa991588048c903bd629f857f0c4608f08e83ff0082cb6ffe37401fc20b2953b4f515fadbff000474ff0093a07ffb1635dffd1ba6d7e757c73b7b7b5f8b5e2d82da248624d7b595548d42aaaaea372a000300000000760315fa2bff000474ff0093a07ffb1635dffd1ba6d007f51d4514500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afeecfe2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b401fbc7ff0449ff90f7c4dff00b04687ff00a57a8d7f4195fcf9ff00c1127fe43df137fec11a1ffe95ea35fd065007f191fb7c7fc9dbfc52ff00b19eefff0045415f1d57d8bfb7c7fc9dbfc52ffb19eeff00f45415f1d50015fd397fc117ff00e48278d7fec6d4ff00d355857f31b5fd397fc117ff00e48278d7fec6d4ff00d35585007ec3cbfeadbe87f957f085f137fe47dd7ffec2ba8ffe95cd5fddecbfeadbe87f957f085f137fe47dd7ff00ec2ba8ff00e95cd401fa71ff00046bff0093a3bdff00b13f58ff00d2ad36bfa88afe5dff00e08d7ff27477bff627eb1ffa55a757f51140099a33f5fcabf97cff00829cfc5ff8a1e08fdae3c51a4f85bc5bafe9760b61a33a5a596b17d696e8d25a02ecb1413c68a58804e07279ea493f9f3ff0d27f1d3fe87bf147fe143aafff0025d007f7139fafe54b9afe1d7fe1a4fe3a7fd0f7e28ffc28755ffe4bafdb7ff8236fc49f1dfc41d57e2baf8d3c41aaeb6b636de1e36eba96a3777e213335fef31fda669766ed8bbb6e33819e82803f74e8a28a0028a28a0028a28a00ffd2fdfca28a2800a28a2800a28a2800a28a2803f9a0f8c9ff0004b1fda8fc77f16bc6de32d22c3447d3f5df126b1aa5a33eb29139b7bebc9a78b72181b6b6c719193835e6dff0e85fdacffe81da17fe0f53ff0091abfaa8a2803f956ff8742fed67ff0040ed0bff0007a9ff00c8d5fa2bfb337c5ef007fc13cfe1c37c01fda5f50934af17cda85cf8892df48b3bbd62dffb3f516d90399eda02818bc12295201f9738c115fb295fcbbffc164ffe4e8ecbfec4fd1fff004ab51a00fd71bcff00829a7ec8fad5acba2d87887566bad414d9dbab683a8a2b4d71fba8d4b340146e76032481cd7e38bffc122ff6b5b811bc9a7683b8471a12baea00762819c7d98e338f535f9c9f0cbfe47dd03fec2ba77fe95c35fddec7f717e83f95007f2b5ff0e85fdacffe81da17fe0f53ff0091aa487fe0915fb59c12a4c34dd0498d8363fb753f84e7fe7dabfaa4a43d0d007e517c16fdadfe077ec89f0b7c3bfb36fc6ad5ef2c7c6fe02b34d375ab7d3f4bbdd42d63b871f68511dcc10b4720314c8dc1c8dd820115ef9e03ff008288fecbbf123c67a2f807c29aeea771acebf789636314da25fdba493c8090a64961545e149c93d057f3b3ff00052aff0093c8f895ff00616b6ffd35e9f5c87ec0ff00f276ff000b7fec67b4ff00d153d007f66f5fcf9ffc16dbfe43df0cbfec11ae7fe95e9d5fd0657f3e7ff05b6ff90f7c32ff00b046b9ff00a57a75007e07d145140051451401fd6bff00c1293fe4d0f4effb1875effd2b6afd0bf11d9cfa8e81a969f6a034d7367710c609c02f246caa33db93d6bf3d3fe0949ff2687a77fd8c3af7fe95b57e92d007f2b937fc1237f6b7ba31c92e9ba08758a28ce35d40331a2a671f6738ce338c9fad7e99ff00c136bf63af8cff00b2ef8a3c65a8fc50b4d3aded359d2ec2d2cdacb505bd6692dee2795f7811c7b462518e0f4afd70a28010f435fcf67ed75ff04defda47e367ed0de36f88de11b0d1e4d135bd4d6eaca4b8d592de668fec96d09df19864dbf34271f374ed5fd0a51401fcf5fec8bff04defda47e09fed0de09f88de2eb0d1e3d1344d4daeaf64b7d592e2658fec97308d918863ddf34c33f374ed5fd098e82968a00f9a3e3b7ed6ff0004ff00670d5349d1fe2aea77b6175addb4d7566b69a6dd5f878addd6390936f1becc33a8f9b19c8af076ff0082a47ec76aacede23d602a02cc4f87b53002a8c924fd9f8000c935f9e7ff0005b4ff0091c7e1cffd80755ffd2db3afc31d2bfe3e64ff00af6baffd10f401fadbe3bff825bfed47f107c61ad78d348d3f443a7eb7a95f6a566d26b291486dafee65ba88ba181b6bec94061b8e0f7afaff00fe09f3fb07fc7bfd9b3e3a1f1d7c45b2d2e1d18e85a9d8f9967a9ade4bf68bb7b4641b0451e1710373cf26bf667c1dff00229e8bff0060fb4ffd1295d250014514500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afeecfe2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b401fbc7ff0449ff90f7c4dff00b04687ff00a57a8d7f4195fcf9ff00c1127fe43df137fec11a1ffe95ea35fd065007f3a5fb507fc1343f698f8bdf1f7c75f113c33a7e8cfa3ebdaddc5f58b4daba412986448d4178cc2fb4fc99c67bd7c83f15bfe099dfb47fc1ff00879ae7c4bf175968f168fe1fb6fb55e3c1aba5c4a23dcabf2c62042c72dd370afebaebe37ff82837fc99a7c55ffb021ffd1d1d007f19d246d148d13fde462a7ea38afe9bff00e08bff00f2413c6bff00636a7fe9aac2bf997beff8feb8ff00aeafff00a11afe9a3fe08bff00f2413c6bff0063627fe9aac2803f621c165207706bf978f157fc1277f6b0d7fc49aa6b11e9ba108ef2faeee23ff89da29d93cf24ab91f676c101b07935fd44d1401fcf5fecbdf00bc67ff04e6f8852fc77fda58d9e93e0fb9d2aebc3b1dc6957126b170750d466b69605305bc01c215b671bb07e62a38cd7e837fc3d1ff63dff00a18b58ff00c27b53ff00e47ae0ff00e0af7ff269d0ff00d8d7a47fed6afe54109debf51401fb93fb477eca7f137f6f6f8a1a87ed23fb3f269fa9781b5f82d6cec2e751bc6d2ae9a6d255ad2e43db4f019102ce8ea37004edcf422be68f157fc129bf6a4f09786757f156aba7e88b65a358dcea170535a4918436b1b4ae557ece371daa703233eb5fb79ff04b4ff932bf06ff00d7febfff00a75bbafacbe3dffc90df887ff62aeb3ffa472d007f0bce850e1bd01fc08c8fd0d7ef7ffc10effe42df187febd7c33ffa16a35f82d73feb07fd738fff004015fbd3ff00043cff0090b7c61ffaf5f0cffe85a8d007f417451450014514500145145007ffd3fdfca28a2800a28a2800a28a2803f2b7fe0a43fb657c54fd94350f005bfc371a518fc4d06b125e7f69583de9dd60d6823d9b6e6df6e44ed9ceecf1d3bfe62ffc3e27f6a370e224f0b9708eca1b4394025549c1235138ce3ae0d7d03ff05c4ff90b7c1eff00af5f137fe85a757e0adb902439fee49ffa01a00feedbe14f88753f16fc30f08f8af5b3136a3ace85a6ea176604314467b9b78e590a21662abb98e14b3607193d6bbfaf06f813e2cf0bc1f047e1f4336b1608e9e17d19595aea2041167164105f822bd57fe131f09ffd0674ff00fc0b87ff008ba00e92be3ef8e7fb0dfc04fda27c6b1f8ffe255a6ab3eaf1d841a687b2d4e7b38fecf6ef2c91829110090d339c9e79afa77fe131f09ffd0674ff00fc0b87ff008ba3fe131f09ff00d0674fff00c0b87ff8ba00f81ecbfe095ffb2569f7b6da85b69fe20135acd15c445b5cba602485c3a1209c1c3283cd7e8d8014003b715ce7fc263e13ff00a0ce9fff008170ff00f1747fc263e13ffa0ce9ff00f8170fff0017401d2515cdff00c263e13ffa0ce9ff00f8170fff001747fc263e13ff00a0ce9fff008170ff00f17401f1efc51ff82777ecd9f183c7dacfc4af1ad96b52eb7af4c97178f6babdc5b42cf1c31c0a5628c855fddc4a38eb8af09f895fb0e7c00fd97fe1f788ff00688f85d61aa278bbe1d69779e22d15b51d52e6f2cc5ed8c2ef1f9d03b01221e430c82413820f35fa71ff00098f84ff00e833a7ff00e05c3ffc5d7ce7fb5e6b5a46bffb2f7c54d1342bdb6d4751bef09eab6f6b6969324f713cd240ca91c71a333bbb1202aa8249e073401f85375ff0584fda86d6e65b665f0bb189d9091a14b83b4e3fe8255f1efed3dfb60fc4dfdaaae743baf88aba609341b7bab6b63a758b590d977243249bc35c5c6e3ba04c1057033c1ce479aea1f033e2e5c5f5c5c47e10f106c925775ce89a90386248ff00975ae13c53e03f17f828dbaf8a748bfd28dd2b3422facae2ccc8a84062a2e228cb004804ae402467191401ccda46b2ca55fa08e571f544661fa8afea8fc07ff04b8fd93b5cf03f87b5bbfd3f5f373a86956375314d72e957cc9a0476c28380324e076afe57ec48133678fdccff00fa2dabfb92f85fe2ef0b45f0d3c251c9ac69eac9a169aac0ddc20822da3c83f3d007e3cfedd1fb00fecebf03ff00674d67e21780acb588758b4d4748b689eef55b8ba8847797b1412e63909527639c1c641e457f3cf5fd6a7fc14efc47e1fd43f642f10dbd8ea7677129d5fc3e4245711bb1035280938562781cfd2bf92b3d6803fad7ff0082527fc9a1e9dff630ebdffa56d5fa4b5f9b5ff04a4ff9343d3bfec61d7bff004adabf496800a28a2800a28a2803c3bf696f881aff00c29f807e3cf893e16101d5fc37a15e6a3662ea332c0668137289103296538e4061f5afe7aaebfe0b09fb50dadd4d6acbe17630c8d1923429704a9233ff00212afde8fdb46c2ff54fd947e2ae9ba5db4d797775e17d42282dede3696596478f0aa8880b3313d00049afe42f50f819f172e2fee6e23f08788364b348eb9d1352070cc48ff975a00fde7fd9bfc33e1fff00829bf84350f887fb4ddaf9fa9f837513a36927c3d2cfa3442cef2d6daf64f3512698bb99180c97c61460039cfd0adff04a3fd90d9190e9de21c3ab29ff0089edd1c861823ae0820f43c578effc1242ceebc01f083c69a6f8e2197c3f753788ede4861d5a37d3e59634d36d633224774b13b26f465dc1704835fac9ff00098f8487275ad387fdbdc3ff00c5d006be9d630699636fa75b6ef26d618e08f71cb6c8d42ae4f7381cd5da623ac8a1d0865600820e4107a1069f40057e21feddff00f0506f8e7fb35fc7bbdf875e061a11d1a1d2f4cbc8fedda6497771e6de2ca5f2eb7700da3cbe06d3d6bf6f2bf98dff0082aafc37f1ef8bff006aad4af7c37e1dd5f51b5fec3d1505c59e9b77750978d2e372f9904322ee5dcb919ef401c76bdff0574fda5bc41a1ea3a0de2786bc8d4ad27b497668922b6c9d1a36c31d45b070dc1c1c7a1afcab9a5699f7b765551f4450a3f415ea971f037e2cdadbcb753f84b5e48a14691d9b46d455555064925ad400001c92401dcd793b295386e0d007d67fb30fed83f137f655b9d72ebe1d2e9864d7aded6dae4ea362d7a365a493491ec0b716fb4ee9df2496c8c703193f5dff00c3e2ff0069ff00ee7863ff000452ff00f2cabf2428a00fd6ff00f87c5fed3ffdcf0c7fe08a5ffe59579bfc5eff00829f7ed01f197e1b6bdf0c3c529a07f64f886dbec977f65d224b79bcbdcac76486fa50872bd7637d2bf3628a007cb234b23caff79d8b1c7a939afb83f66afdbd3e30fecbde11d4bc1df0f9347365aa6a03529cea1a73de49e70822b7f9596eadf6aec8578da79c9cf381f0e5779e19f867e3bf18d949a8786741d5354b6864f25e5b2d3eeeed164da1b6b35bc32aab6d20e0907041c6083401fa67ff000f8bfda7ff00b9e18ffc114bff00cb2a3fe1f17fb4ff00f73c31ff0082297ff9655f9dff00f0a17e2fff00d09fe20ffc126a5ffc8b5e4d3c12db4af04e8d1c91b156560559594e0820f2083c107906803f7b3f671fda0fc57ff0526f1d4bf007f68ab7d3e7f08dbe9973e2355d0ede6d22f3edfa6cd6d1404ce2ea7263c5d392a029dc14e78c57dcbff0ea4fd90ffe81de21ff00c1f5dff8d7e4e7fc11affe4e8ef7fec4fd63ff004ab4eafea22803cb7e0d7c1ef057c07f87f61f0cfe1f45730e87a6cb7334097770d73307bb99ee252d2bfccd992463cf4cd769e29f0e69be30f0ceade13d64486c35ab1b9d3ee844e6390c1751b4526c71cab6d63861c83cd3eefc4be1eb0b86b5bed4ecede64c6e8e5b88e3719191956604645431f8b3c2f34890c3abd83c92305545ba88b331380000d9249e82803f3e57fe0945fb2122aa269be200aa028035dbb0001c00066be8efd9f7f649f83bfb325c6bb75f0aedb51b793c4496b1df1bfbf96f772d9194c5b7cd27660ccf9c75cfb57d3345001451450014514500145145007fffd4fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf03ba722bf7c7fe0b89ff216f83dff005ebe26ff00d0b4eafc0ea00d04d4ee514204b721400336d093c7a92849fa9a7ff6b5d7fcf3b6ff00c0583ff8dd6657d21fb2bfecf97dfb4cfc5ed33e1569fab5b68d36a36f7b3add5d4325c44a2ce1f3482913c6c4b740430c1eb9a00f02fed6baff009e76dff80b07ff001ba3fb5aebfe79db7fe02c1ffc6ebf73bfe1c97e31ff00a28da0ff00e0aaf7ff00932bf33bf6bffd98350fd94be265bfc39d4b5ab4d7269b48b4d57ed1676f2dbc616ee5b88c26c96495b2bf67249dd83b870307201f317f6b5d7fcf3b6ffc0583ff008dd1fdad75ff003cedbff0160ffe37599450069ff6b5d7fcf3b6ff00c0583ff8dd1fdad75ff3cedbff000160ff00e3759952c3119a68e10706460b9f4c9c5005efed6baff9e76dff0080b07ff1bafaeff610ba7bafdadbe15f9b1c20a789ed30638638cf31cddd154f61f957d6bf017fe0941e26f8e1f083c2ff0015ec7c73a3e9d07892cbed896971a75d4b2c20bb2ed674b98d58fcbd4281ed5efde19ff826febbfb1e6b36ff00b4eebfe32d375bd2be19193c4d7ba669fa75c437779058452178a2927ba78d5cab1dbb86338c9033401fbf58afe7d7fe0b6dff0021ef865ff608d77ff4af4eaf7c93fe0b37f056291a293c0de2657425581bad2b208ea3fe3f2bcffc7be15b1ff82b84767e2bf87378fe04b5f8762e349bc875fb75bd6bc7d57ecf748f09b1ba0aab1ac001258e4b630319a00fe74c120823823a1ad05d52e514284b7200039b6849e3dca64d7ee87fc392fc639e7e23683ff82abdff00e4cafc55f88fe0e97c01e37d73c1b35c25d3e8ba9dfe9cd346a51246b1ba96d99d5589203988b0049201009279a00e5a5d46e26428cb0a83de38228dbfefa55079efcf35468a2803fad7ff0082527fc9a1e9dff630ebdffa56d5fa4b5f9b5ff04a4ff9343d3bfec61d7bff004adabf496800a28a2800a283c0cd7e617c6cff0082a37c2df81df147c45f0b3c41e10d7efaf7c3b76b6735d5b4fa7c704aed0c53e6313dcc72602ccb9caf5a00fd3da4c57e61fc13ff0082a37c2df8e3f147c3bf0b3c3fe10d7ec6f7c4576d670dd5ccfa7c9044eb0cb3e64105cc9260ac2d8c2f5afd3d1c8cd007f3cfff0005af7f27c6df0de6548dd9341d58af991ac8013796632038233824671debf0eac6fa5b99658668adca9b6b93c5b42a41585c820840410464115fb85ff05b5ff91c7e1cff00d80755ff00d2db3afc31d27fe3e64ffaf6baff00d10f401fde2f83b8f0968a076d3ad3ff0044a57495cdf83bfe453d17fec1f69ffa252bc9ff00692f8fda0fecd5f0c66f8a1e24d2ef758b282facec4db69ed12ce5ef64f2d0833bc6980c4672c38a00f7ca315f8ddff0f9ff00825ff423f897ff0002b4affe4ca3fe1f3ff04bfe847f12ff00e05695ff00c99401faa9f153fe498f8bff00ec03a9ff00e93495fc26dfff00af5ffae307fe8a5afe9853fe0abbf097e2d67e17e8de0ef10da5ef8bca787edee679f4e921b79b5665b38a59562ba790c6924ca5b6a938ce057ce8dff044ef1ab85f33e24684eca889b8e937809d8a14138bc03381ce00a00fc1ca2bf787fe1c97e31ffa28da0ffe0aaf7ff9328ff8725f8c7fe8a3683ff82abdff00e4ca00fc1ea2bd7be3b7c2ab8f829f15fc4ff0c6eafa2d4a5f0e6a52e9ef75046f1472b44a8db951d9d947cf8c16278eb5adfb387c17bafda03e2ff873e14d9ea3069537882ea4b64bbb889e68a231db4f739648dd19811015c061cb039c02080785d7f4e5ff000460e7e01f8d7fec6d4ffd355857cddff0e4bf18ff00d146d07ff0557bff00c995fa89fb0efecabacfec99f0ff005ff05eb3af59f8824d635a1aa47359db4b6a9120b482d846565924627f71bb3bbbf4a00fb4e4fb8dc9e87f957f087f137fe47dd7ff00ec2ba8ff00e95cd5fddecbfeadbe87f957f085f137fe47dd7ffec2ba8ffe95cd401fa71ff046bff93a3bdffb13f58ffd2ad3abfa88afe5dffe08d7ff0027477bff00627eb1ff00a55a757f511401fc9cff00c156a6fb1fed97e2b9218a1dd269da1ee2f0c72127ec6067e753ce0019f403d057c95fb355f4b73fb42fc308e48e003fe135f0df290448c3fe2676fd1950115fbf1fb5c7fc132fc55fb4c7c70d67e2cd8f8d74ad22d352b6b0b78ecaeb4fb99e58fec70088b1922b8894ee6c9e9c0c77ce7e79d1ff00e0945e27f811aad8fc6cd43c73a4ea569f0feeedfc55716369a6dcc5717716892adf3c113cb76d1a3cab09456705416c9e2803fa091457e362ff00c1683e0932abff00c20de270180601ae74b5386191906f011c1e879afb0bf64dfdb5fc11fb5c5d789ed3c1fa0ea9a33785e3b192e0ea32dac82517e6709b3ecd34b8dbe4367763a8c679c007da3451450014514500145145007fffd5fdfca28a2800a28a2800a28a2803f9fbff0082de5add5d6aff0007c5b4324c56d7c4b911a96c65f4eeb806bf063fb2356ff9f2b9ff00bf4ffe15fdc9fc4bf815f07be3249a7cdf14fc21a478a5f4a5996c8ea96cb706dc5c143288f77ddde517763aed1e95e5ff00f0c3ff00b227fd123f09ff00e0b62a00fe2f3fb2356ff9f2b9ff00bf4ffe15fa51ff0004a2b4bbb5fdb23c2c2e619212da76b84798857205a7b815fd0dff00c30ffec89ff448fc27ff0082d8abacf03fecb7fb3bfc34f125b78c7c01f0f3c3fa06b766b22dbdfd859243711acc863902baf23723153ec6803df2bf97eff82c65add5d7ed4966b6b0c9315f0768d911a96c7fa56a5d700d7f5035e21f10bf66cf80bf1635e4f147c4af02687e25d5a3b74b45bcd4acd2e265b789999230cdc855676207a93401fc41ff00646adff3e573ff007e9ffc28fec8d5bfe7cae7fefd3ff857f687ff000c3ffb227fd123f09ffe0b62a3fe187ff644ff00a247e13ffc16c5401fc5e7f646adff003e573ff7e9ff00c2ad59693aaadedb9365700095092627f51ed5fd9cff00c30ffec89ff448fc27ff0082d8a90fec3ffb22019ff8547e13ff00c16c5fe1401cff00fc13e881fb1a7c2ac9ff009820ff00d1d25757fb6991ff000c95f17b9ff993758ffd267afc1efdad3f6a9f8e3fb36fc7cf17fc1cf831e2abdf09f827c357b05a68da1e9715947676303d8dadc324625b595c0696776c6ec0ce0002b0ff00669fdb03f683f8f3f1c7c19f093e2978cb50f117843c55abc5a56b7a3dfc764f6b7f63711cbe641288ed227d8fb40601864641eb401f97daae93aab6a776cb6770419e420889882371f6afe8b3fe08ad14b0fc36f8931cc8d1baeb5a582ac0820fd857a835fa11ff000c3ffb227fd123f09ffe0b62ff000af5bf86bf067e15fc1db5beb1f85be16d33c2f6fa948935dc5a65badba4d2460aab385e0900900d007a61e9f88afe20bf68dd3efee7e38f8f24b7b69a54ff0084a7c4237246cc32355bbee01afedfabe65d53f632fd9575bd4ef35ad5fe16785ef2ff0050b896eaeae26d3e379669e762f248ec7966662493dc9a00fe2bbfb2356ff9f2b9ff00bf4ffe147f646adff3e573ff007e9ffc2bfb43ff00861ffd913fe891f84fff0005b151ff000c3ffb227fd123f09ffe0b62ff000a00f03ff8252ab2fec8ba72b0208f10ebc083d47fa5b57e925715e00f871e05f859e1e4f09fc3bd0ecbc3da3472c93a58e9f10860592639760838058f26bb5a0028c81d68afcc2ff82a37c6cf8a3f03fe16f843c41f0b3c4577e1dbdbed7e7b6ba9acd61679608f4fb99c467cf8a6503cc8d4e76e6803f4ec9183cd7f1f5ff0518b2bcbafdaff00e25b5ac12cc175c504c68580ff008975875c034597fc1463f6bfbabc82d9be25eb8a269523242e9dc6e2067fe3c3debf7f3f67afd9ebe077c79f81de05f8c5f187c0ba0f8b3c69e2cd06c752d6b5ad4ac2296eefaee58943492300067000000000000000c5007e01ff00c139ecaf6d7f6bff00868d7304b086d718032232e7fe25d7fd32057f60c3a0af9e7c27fb267ecd7e03f11d8f8bfc19f0dfc3ba2eb5a64865b3bfb2b1486e2072a509475e412ac41f635f43d007f3d3ff0005ae827b9f1a7c398ede3795ce83aa9da8a58f17b67d866bf0e74ed33528669659ad27445b5ba259a36007ee5fa922bfb7ef893f00be0c7c62bbb2bff8a5e0dd1fc5371a6c6f0da49a9db2dc3411c8c19d537740c40271d715e69ff0c3ff00b227fd123f09ff00e0b62a00fa13c1c40f09e8bcff00cc3ad3ff0044a57e7f7fc156c83fb21ea3ff00630e83ff00a56b5f89ff00137f6f7fdab7c17e3ef10f85f44f88bac5be9da5eafa9d959dbc4b62120b6b4bc9ade18d77d9bb9091c6a32cc49ea4d7817c4ffdb3bf68af8c1e1397c15f107c69a9eb5a3cd3c372f6b742d04665b76df1b7ee6d617ca3608f9b191c83401f2b51451401ee5fb367fc974f01ff00d8d1e1ff00fd3ada57f71008f5ee6bf821f0df88b56f09eb963e22d0ae1ed6ff004eb982eeda64c6e8e7b6916689c6e0cb94911586411903208c83f679ff0082907ed804e7fe1656b7ff007ce9dffc81401fd88641a5afc64ff8255fed1df19fe3d6bde3f8fe2af8aafbc450697a6e913d94778b6e3c892e2e2f5252a60821cee5893ef038c57ecdd007f1a5fb78d8dedd7ed6bf14dadade5940f13dd826346600f9507a035d77fc1372caf2d7f6c6f868d7504b086d5ae403221504ff0065ea1d32057f4e7e25fd913f664f196bf7fe2af157c33f0deababea93b5cde5eddd824b3cf33fde7776e4935f347ed41f01be0c7ecf1f00bc6bf1a3e09f82742f0778e3c2ba6b5e68dae699610c777653b3ac6cf1b32b0f9a3764604105588ef401fa4208c0e69d907a57f1e971ff051bfdb0219e4847c4bd7088dd973b74ee7071ff3e15fbabff04b9f8d7f13fe38fc21f16788fe29f886efc457f65e245b3b59ef161578adce9f6937963c88a1523cc958e76e79eb401fa672ff00ab6fa1fe55fc217c4dff0091f75fff00b0aea3ff00a57357f77b2ffab6fa1fe55fc217c4dff91f75ff00fb0aea3ffa5735007e9bff00c11b3fe4e8ef7fec4fd63ff4ab4eafea2323d6bf85bf843f1afe23fc0cf12c9e2df865addd685aa4b692d8b5cda7926436f3b46ee9fbf8a64c334484fcb9ca8c11ce7e955ff828f7ed80cc07fc2cad7393fddd3bff00902803fb10eb5e4df1effe486fc43ffb15759ffd2396bc17fe09f5f123c6df16bf659f0c78ebe21ead3eb9aedf5e6b31cf7b702359244b6d46e21881112469f2c68abc28ce2bdebe3dff00c90df887ff0062b6b3ff00a472d007f0c973feb07fd738ff00f4015fbd3ff043cff90b7c61ff00af5f0cff00e85a8d7e0b5cff00ac1ff5ce3ffd0057ef4ffc10effe42df187febd7c33ffa16a3401fd05d14514005145140051451401fffd6fdfca28a2800a28a2800a28a2800a28a2800a28a2800af10f885fb49fc05f84faf2785fe2578ef43f0d6ad25ba5dad9ea57896f335bcacca92056e4ab323007d41af6fafe5fbfe0b1977756bfb52599b59a484b783b46c98d8ae7175a975c11401fbb3ff000dc1fb227fd15cf09ffe0ca2a3fe1b83f644ff00a2b9e13ffc19455fc5e7f6beadff003fb73ff7f5ff00c68fed7d5bfe7f6e7fefebff008d007f687ff0dc1fb227fd15cf09ff00e0ca2a43fb707ec88463fe16e784ff00f06517f8d7f17bfdafab7fcfedcffdfd7ff1ab565ab6aad7b6e0dedc1065404195fd47bd007eb17ed69fb2b7c71fda4be3e78bfe31fc18f0adef8b3c13e25bd82ef46d734b96ca4b3be812c6d6dd9e332dd44e42cb03ae76e0e320914cfd91ff0061cfda87e1afed1ff0f3c65e2af87dab58e8fa56bf6d757d773b59797040892a97223bb95c805c670a78cd7edcff00c13e803fb1a7c2ac8ff9820ffd1d257d9181e9400b5e47f12be3d7c1bf83b716169f14bc63a4785a6d512592cd354b95b73709095590a6efbdb4b2e71d322bd72bf9f8ff0082d84f3dbf887e18c96f23c4e347d746e462a79bbd3bb8a00fd5ff00f86e0fd913fe8ae784ff00f065151ff0dc1fb227fd15cf09ff00e0ca2afe2f3fb5f56ff9fdb9ff00bfaffe347f6beadff3fb73ff007f5ffc6803fb76f017ed39fb3ffc52f1147e12f875e3fd07c45accb1493a5969d7893ce628865df6af6507935eeb5fcaf7fc1232eeeaebf6b4d35aea69262ba0ebc01918b606cb5e9926bfaa1a00f0ef881fb4b7c03f853aff00fc22df123c79a1786f573047742cf52bc4826304a5824815baab15600fb1ae4ac3f6d2fd94755bfb6d2f4df8abe17babbbc9a3b7b7822d423792596560888aa392ccc4003d6bf05ffe0b0775756bfb5106b59a484b785f4204c6c5491e6ea5e8457e7a7c0bd42fee3e2e783e3b8b99a54ff8487443b5e466191a95af626803fb91afc6eff82cf7fc912f03ff00d8cd75ff00a6abcafd9015f8dfff00059eff009225e07ffb19aebff4d579401fcd16972245a95a4b2b05449e36663d000c0935fd5dfec8dfb5cfecc9e0bfd993e197853c55f133c35a56b1a5786ac2d6f6caeafe38a7b79e38f0f1c88795653c106bf936abd1ea7a9448b145773a228c2aac8c001ec01a00fed9fc27fb59fecd7e3cf11d8f843c19f123c3bad6b5a9c862b3b0b2be49ae2770a5c8445e490aa49f615f43d7f1f1ff0004e7bdbdbafdaffe1a2dccf2cc175c62048ecd8ff8975ff4c935fd830e82801690d2d1401fc97fc55fd803f6b4f147c47f136bba77c37d664b4bdd6b55b8b7911ac0ac90dc5ecf346e37dea300c8e0e0a822be7cf89ffb18fed15f07fc272f8d7e20f82f53d174786786d9eeae8da18c4b70db235fdcdd4cf976c01f2e327922bfb52c0f4afcdaff0082ad803f643d47fec61d07ff004ad6803f929a28a2800a28a2803f683fe0923f1afe12fc1fd5fe20dcfc50f16e93e178b51d334786d1b54b95b713c905cdf3c8a85bef145910b01d030f5afdb4ff0086e0fd913fe8ae784fff00065157f1676f77776a58dacd2425baf96e5738f5c11567fb5f56ff009fdb9ffbfaff00e3401fda1ffc3707ec89ff004573c27ff8328abc13f6a0f8f3f063f687f805e35f82ff0004fc6da178c7c71e2ad35acf46d0f4cbf864bbbd9d5d64648d59947cb1a33b12400aa4f6afe4dffb5f56ff009fdb9ffbfaff00e35f767fc1372f6f2ebf6c6f868b753cb305d5ae481239600ff65ea1d324d0053b8ff82727ed8134f24c3e1a6b8048ecd8dda771939ff9ff00afd73fd80b58d07f62df867e21f027ed47aad87c35d735bd746aba658ebf796f0cf7562b636b6c674114b2a14f3a175fbd918e40c8cfecb003038afe717fe0b53713dafc60f02c96d23c4dff0008b5c0dc8c54e0df0ee2803f645ff6dffd91594a8f8b9e13e41ff98945fe35fc707c41bcb4d43c67ad5ed8cab3c13ea37d2c7221caba49732ba303dc32b023d8d73dfdafab7fcfedcffdfd7ff1ace249393401eaff00087e0a7c47f8e7e2593c25f0cb44bad77548ad25be6b6b4f24482de068d1dff7f2c298569501f9b39618079c7d2abff04e1fdb01581ff856bae707fbda77ff0027d7d0ff00f046cff93a3bdffb13f58ffd2ad3abfa88c0f4a00f8b7fe09f5f0dfc6df097f659f0c7817e21e933e87aed8de6b324f65706369234b9d46e26889313c89f346eadc31c66be89f8cda56a7aefc21f1be89a25ac97da86a1e1dd56d6d2d622a249e79ad6448e34de55773b1006e60327920735e95d28a00fe3c1bfe09c9fb61ca11dfe19eb6adb10300fa7b005540383f6e19191e82bf5ebfe0947fb377c64f801a97c4b97e2b7862fbc3a9ae5be84962d786dcf9ed68d7be685fb3cf3fdd12a6776debc679c7eca607a52e00e9400514514005145140051451401ffd7fdfca28a2800a28a2800a28a2803f39ff6effdb835cfd8f6f7c176ba4786ac3c40be2a87549646bdbb9ad7c83a7b5b0017c9866ddbfed0739c636f7cd7e7e7fc3ed3c63ff44e741ffc1adeff00f21d687fc1713fe42df07bfebd7c4dff00a169d5f81dd781401fbc3ff0fb4f18ff00d139d07ff06b7bff00c875f4c7ec8fff000534f157ed31f1c346f84d7de0ad2b48b4d4adafee24bdb5d42e67963fb1c0650a2396de253b9b03af033df19fe65934cb9750e1edc06008cdcc20f3ea0b823e86bf49ff00e0949035afed95e158e5788b3e9dae60472a487fe3cffd866f43f91f43401fd62d7e7cfed3dff04f1f875fb51fc478be24f8afc4dad6937716956ba48b6b08aca480c56b24f22bff00a4c12b07267607040c01c57e82e696803f1bbfe1cc1f04bfe878f12ffe02e95ffc8747fc3983e097fd0f1e25ff00c05d2bff0090ebf6468cd007e377fc3983e097fd0f1e25ff00c05d2bff0090e957fe08c7f0523612278e7c4c194e4116ba57047fdb9d7ec8521e86803f02fc5bfb7ceadfb086b771fb28785fc2763e23d1be1c98b4ab2d5b55d42682f2ee39208af374b1dbdabc4a57ed3b3e5233b73819aeebe017fc158bc5ff001a7e31f843e184de03d1ac20f12ead0e9d2ddc1a95d4b240b2abb175492da356384200ddd4d7e5cffc1486d9eeff006c9f89ab1490864d56d4959268e36c1d2ec3070eca71c1e7dab92fd842d5ed7f6b6f857e6c9092fe27b4c08e68e43c473764663dc7e7401fd9757c53fb57fec47e08fdad753f0eea5e2ff10eada29f0e5b5e5ac31e9d1dac8b32dec90c8e641730cbca98176edc77afb5734b401f8dbff0e60f823919f1c7897aff00cfae95ff00c875fcee7c5bf08d97813e247893c21a74d25c5b68fac6a7a7452ca1448f1d8de4f6cacc10050ccb1066c00324e001815fdda1e9f88afe203f68ab47bbf8e5e3d7865b7c2f8a7c40ac1a789181fed4bb3c867047041e9d0d007db1ff000485ff0093b2d3bfec05aeff00e816d5fd5457f2b9ff00048d85adbf6b6d36291e2673a0eba711ca926014b6c6763363383d7d2bfaa2cd007f2e5ff058bff93a04ff00b16342ff00d1ba957e787c05ff0092bfe0ff00fb18344ffd395ad7e87ffc162c1ff86a04ff00b16342ff00d1ba957e787c04cffc2dff0007e3fe860d13ff004e56b401fdce8afc6eff0082cf7fc912f03ffd8cd75ffa6abcafd9006bf1bffe0b3dff00244bc0e7fea65baffd355e5007f3294515a09a65cba86dd02e7b3dc448c3eaace083ec45007b07ecedf196ebe027c5bf0e7c52b3d3e1d526f0fde9bc4b5b895e18a56304f06d678d5d946272d90adc8031824d7eb8ff00c3ed3c63ff0044e741ff00c1adefff0021d7e18ff64dd7fcf4b6ff00c0a83ff8e51fd9375ff3d2dbff0002a0ff00e39401fb9dff000fb4f18ffd139d07ff0006b7bffc8747fc3ed3c63ff44e741ffc1adeff00f21d7e13dc5a4d6d8f30c6c0f78e44900fa946600fd6abaab310aa0924e001d49a00fde0ff0087da78c7fe89ce83ff00835bdffe43af9dbf6a3ff829bf88bf694f84d73f0bb52f06e95a3c3717f637df6ab4bfb9b8903594a250bb25b78970c4609dd903a035f97034bb9600892d8679e6e611ff00b3d47369f71026f668587a47347237e48c4e3f0a00a55fb93fb1d7fc133fe16fed0df02349f89fe22f14eb9a65fdf5f6a96af6f670583c216c6ee4b756067b7964cb2c619b2d8c938006057e1b8eb5fd76ff00c12e3fe4cf7c3bff00618f10ff00e9cee2803e57f197fc11e7e0c7877c1faef882dfc6be24925d334dbbbc446b6d2c2b3410b48012b681b04af3820fa1afe706ea258650abd0a46ff4de81b1f866bfbb7f8aa40f85fe2f2c400341d4c927803fd1a4afe16ae74f9ae1a396292df69861eb710a9c88d410417041078208a00fd15ff827a7ec6de08fdacb50f17d978bf5bd4b471a058e9d750369d15ac8646bc9aea3657fb4c5280144008da01cb1c93c0afd3cff0087307c12ff00a1e3c4bff80ba57ff21d7847fc113a3f2bc45f13e12d1bb2691a106f2e4590026eb5138250919c10715fd03e6803f87cfda5fe17e93f06be37f8c7e1a687773df58f877579f4f82e2e96359a448d6360ce2255404ef23e55038e951fece1f1a2ebf67ff8bfe1cf8ad67a741aacde1fba92e52d2e25786294c96d3db619e34765004e5b214f2a0630491ea3fb7c67fe1adfe29678ff008a9aefff0045415f1fc30bcee238f6e4f76608a3eacc401f89a00fddcff87da78c7fe89ce83ff835bdff00e43af5af04fc39f0d7fc15974b9be30fc4796f3c0d77e0db86f0c5bd96852417b6f730491417e6691afad4b07dd385c050005ef935fceb7f64dd7fcf4b6ffc0a83ff008e57f4c3ff000462017e03f8dd032395f172a928eaeb91a5d88382a4838231c1a00aff00f0e60f825ff43c7897ff000174affe43a3fe1cc1f04bfe878f12ff00e02e95ff00c875fb23466803f3e7f661ff008278fc3afd973e23cbf127c29e26d6b56bb974abad24db5fc5651c022ba920919ffd1a0898b830281924609e2bf41a8a2803f1f3f6b8ff00829a78abf667f8e1acfc26b1f05695abda69b6d61711dedd6a17304b27db20129531c56f2a8dad91d7918ef9c7ccff00f0fb4f18ff00d139d07ff06b7bff00c875f2a7fc156a1fb67ed97e2b8e1961dd1e9da1ee0f3471907ec60e3e761ce0838f423d457e6cbe99728a5cbdb90a0938b9849e3d00724fd05007ee8ffc3ed3c63ff44e741ffc1adeff00f21d7e81fec21fb706b9fb615ef8d2d757f0d58787d7c2b0e972c6d657735d79e7506b904379d0c3b767d9c6319ceeed8afe46fa706bf7c7fe0877ff00216f8c3ff5ebe19ffd0b51a00fe82e8a28a0028a28a0028a28a00fffd0fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf056dc0321cff724ff00d00d7f547ff0521fd8d7e2a7ed5fa87802e3e1b9d2847e1983588ef3fb4afdec8eebf6b431ecdb6d71bb0206ce76e38ebdbf317fe1cedfb51a07313f85c3947552dae4a402ca46481a70ce33d322803fa13f813e13f0bcff00047e1f4d368f60eefe17d19999ad622493671649253926be67ff0082935bdbf837f643f167883c251268ba9dbdf686b15ee9c3ec7731acba9db472049a0d9226f462adb5865491d09afb4be14f87b53f097c30f08f8535b112ea3a3685a6e9f7620732c427b6b78e290239552cbb94e18aae47381d2be33ff82a5ffc995f8cbfebff0040ff00d3ada5007f2dcdf1e7e2f0623fe130f10704ff00cc6f52ff00e4aafe9a7fe093fafebbe24fd976e354f116a579aa5db78a7554f3efae65ba97620842aef99ddb6a8e833fa939fe4e5fefb7d4d7ef57fc13c7f6ebfd9fff0067ef8012780fe235e6ad0eacdafea17fb2cf4b9eee210dc797b3f79182bbbe5391d45007efb7884b2e83a8b292a45a5c1041c107cb6ef5fc415c7c73f8bb6e628d7c63e216fdcc4c59b5bd4724b2024f1740753d862bfa60d5ff00e0aa5fb23dee937b690ea3e20f326b79a35ce857606e742064edf535fca1de4892caa53a2c51a1faaa007f51401fbbdff0476f1e78d3c67f173c6c9e28d7754d56287c330bc715f5fdd5e468e6f769655b89640ac400323b7e35fd0a57f27fff0004cbfda5fe16fecd9f10bc55e20f8a1717d05a6a9a145636e6c6ca4bd73325d79a772c592a369ea78cf15fb37ff0f5bfd90ffe823e21ff00c10ddff85007e845cf863c397b3bdd5e697653cd21cbc925b44eec718c962a49381debe6cfdaf345d2340fd97be2a6b7a1595b69da8d8f84f55b8b5bbb48520b88268e066492391155d1d4805594820f239af0aff87adfec87ff00411f10ff00e086effc2b88f895fb71fc00fda83e1ff88ff677f85d7faa3f8bbe22e9779e1dd15751d2ee6ceccdedf42e91f9d3ba911a0e4b1c12003804f1401fce4dff00c73f8b96f7d71047e2ff00106c8e5745ceb7a91385240ff97aafdfff00f823a78afc4de2df87df116f7c4dab5fead347ace9ab135fde4f78d1ab59862a8d3c923282c49c035f065d7fc11eff006a1bab996e59bc2ea65767206bb2e06e39ff00a06d7d95fb346a5a3ffc130341d63c21fb4edd086f3c77771ea5a30f0ea4faca1b7d3618ada7f3996084c6c1e44c65704370720d007ede573afe11f0b48ed249a3d833b92cccd6b1124939249dbc926bf3f3fe1eb7fb21ff00d047c43ff821bbff000a3fe1eb7fb21ffd047c43ff00821bbff0a00bff00f0525b5b7f087eca1afeb7e138d745d462d57428d2f34d1f62b9449b508239156683cb9143a3156dac32091debf978ff0085f3f17ffe870f107fe0eb52ff00e4aafe87be37fed11f0c3f6fdf871a87ecddfb3e5e5d4fe32d566b2d56dd35cb3b8d2ac8db691770dcdc16b878e4c1d80050118ee61c63247e78ff00c39d3f69ff00eff863ff0007b2ff00f2b6803f2cfc4be2df1178c2f86a7e25d46ef52ba08b1f9d797335d49b133b57cc9de47da3270376064e00c9ce2d9de5cd85cc7796723c3342eaf1c91b146564219486520820804104104641079af7cfda3ff670f1cfeccbe395f0178f5ac5b516b1b5bfff0040ba3771795766658ff78d0c0776607c8d9c71c9cf1f3d5007afff00c2faf8be79ff0084c3c41ff83bd4bff92ab9af137c4bf1d78c6d22b1f13ebba9ea9042e648e3bdd42eaed11c82a5956e269554ed2464007048ce0915c2d1401a1a400755b204641b88b20ffbe2bfb27fd8a3c2fe1bbcfd92be135cdde956334b278534d6791eda26662631c9254927debf8d4d3e64b6bfb6b8973b229a376c7270ac09afe953f661ff008290fecc5f0c7f67bf87df0fbc537fada6afe1fd02cac2f56df47b99e213c09b5c248836bae7a30e0d007ebc7fc21de13ffa0369ff00f8090fff001147fc21de13ff00a02e9dff008090ff00f115f1cfc32ff828a7ecd7f173c79a2fc37f065eeb52eb5afdc35ad9a5ce91716d099163794869640157e48d8fe15f755007f3a9ff0005a5d274bd2fc5ff000ed34db3b7b40fa16a858410a45922f6cc0276819c03fe735f87fa5806e5f233fe8d7279f685ebf743fe0b6bff00238fc39ffb00eabffa5b675f863a4ffc7cc9ff005ed75ffa21e803fba4f09784bc2b278574677d1b4f2c74fb4249b4879fdca7fb15f037fc153fc3ba069dfb24ea37161a6d9dbcbff090684bbe2b78e36c1ba5c8caa83835fa35e0eff914f45ffb07da7fe894af97ff006e8f81fe37fda17e01ddfc36f87df61fed79f56d32f57fb42e1ada0f2ace612c999163948240c0f90f26803f8c81d6bfaedff825c7fc99ef877fec31e21ffd39dc57e407fc39d3f69ffeff00863ff07b2fff002b6bf43be087ed11f0c3f602f871a7fecddfb41de5d41e32d2a6bdd56e1343b3b8d56c85b6af7735cdb95b848e3c9d84860514ee53c6304807ebdcb147346d14aa1d1d4ab2b004329e0820f04115cfff00c21de12ffa02e9dff8090fff00115f0bf877fe0a7bfb2a78a7c41a5f86748bfd79efb58beb5d3ed449a25cc6867bc95208833b00aa0bba8249c0cd7e865007e2cffc15ef51bef879e00f87ba87816e26f0f4f73abea31cf26912be9cf3469665d5247b5689d94300c0138cd7e047fc2f9f8bff00f4387883ff00075a97ff002557f4fdff000519fd963e26fed4be12f06e83f0d8e9825d0f52bdbabcfed3bc6b3531dc5b185446cb04f96dc7272bd07bf1f923ff000e74fda7ff00bfe18ffc1ecbff00cada00fca3d6358d4f5fd467d5b58b99af2f2e9cc934f712bcd2c8e7a9692466763eecc4fbd7db3ff04deb4b5bdfdb0be1bdbde431cf13eab72192540eac3fb32fce086041e403f515f41ffc39d3f69ffeff00863ff07b2fff002b6be94fd917fe09a9fb40fc0afda17c17f137c54de1f3a36857d3dcde7d975692e6e363d95d5ba88e336508277cc09cb8e01a00fdd41e0ef09e07fc4974effc0487ff0088ad6d3f4ad374a8da2d32d20b4476dccb044b1066c632420009c77abc3a0af94be3efed9df047f66bf11e9be16f8a375a9dbdfead64d7f6cb63a74d7a8d02c86224b440ed218743ea3d6803eab93ee37d0ff2afe24fc7bf1abe2be99e33d72ced7c5fe201147a9dfaa0fed9d4142aa5d4aaaa02dca8015400302bfa486ff82ad7ec86ca47f68f8879047fc806efff0089afe553c6faa5a6b7e2bd5b55b02cd6f777f793c4597692935c4922e4763b5864763c5007ebff00fc124be23f8efc63fb4dde59789bc41ab6a76abe12d5a416f7ba8ddddc3e625ce9e15f64f348a1943b00400704d7f4a75fc86ffc137be3f7c3afd9dbe3a5d78e3e25cf7906972787351d3d5acad24bc90cf733d9ba0f2e305b6ed85f2dd0703b8afdcdff0087adfec87ff411f10ffe086eff00c2803f42aefc35e1ebfb86babed32cee267c6e925b78e4738181966524e05790fc76f09f85e0f823f10668747b0474f0beb2cacb6b10208b39704109c115d3fc1af8c3e0af8f1f0fec3e267c3e96e66d0f5296e6181eeeddada62f6933dbca1a27f9971246c39eb8a83e3dff00c90df887ff0062b6b3ff00a472d007f0c770009063fb91ff00e802bf7abfe0877ff216f8c3ff005ebe19ff00d0b51afc16b9ff00583feb9c7ffa00afde9ff821dffc85be30ff00d7af867ff42d46803fa0ba28a2800a28a2800a28a2803fffd1fdfca28a2800a28a2800a28a2800a2bf27bfe0a5dfb60fc5ff00d95f51f87b6ff0bae74fb78fc4906b325f7db6c16f496b16b4116ccc91ede267cf5cf15f971ff0f7afdacffe823a17fe0893ff00926803faa8afcf4ff82a5ffc995f8cbfebff0040ff00d3ada57e317fc3debf6b3ffa08e85ff8224ffe49af26f8ddff000517fda0fe3dfc38d4be1878eaf3499b46d4e5b59664b5d292d65dd673a5c478944ef8c491ae7e5391c719a00f81dfefb7d4d3294924927bd7efdffc13a7f621fd9ebe3cfecf9278e7e24691a85e6aebe20d46c7cdb6d5af2ce3f22dc47b079704a89919396c64f73401f80983460d7f5dbff0eb8fd8f7fe85dd63ff000a1d4fff009228ff00875c7ec7bff42eeb1ff850ea7ffc91401fc88d15fb9dff000531fd8dfe03fecf3f08fc33e27f861a4df58ea1a8f885ac6e1eeb54bbbe5300b0ba9f012e257553e644a77000e0119c135f8716b1acb750c4fcabc8aa7e8481401060d7d8bfb03ffc9dbfc2dffb19ed3ff454f5fb2dfb1fff00c13e3f662f8a9fb357807e2178c743d4ee35ad734b1737b2c1ad5fdb46f2995c65628a648d0600e14015ea9f153f629fd9eff667f86de27fda07e15e8b7d6be31f87ba4def887439ef757bebdb78afeca17689a4b79a668e45ea0ab0e41238eb401fa9b5fcf9ff00c16dbfe43df0cbfec11ae7fe95e9d5f355d7fc15cff6b4b5b996d9b52d04989d9091a1260ed38ff9f9afb9bf652d3b4aff008297f8775bf177ed4f6a357bef03de45a6e8ada34b71a22c76da9431dcce245b59ff007859e34fbcc40da30064d007f37f83495fd777fc3ae7f63d183ff08eeb1d47fccc3a9fff002457f2cdf1bbc37a4f847e2b78b3c35a146d0d8699aeeaf656c8eed232c3697f710440bb659888e350589258f24e4d007dfdff000485ff0093b2d3bfec05aeff00e816d5fd54d7f0e9f007e3f78eff00672f1dc5f10be1ecb6b0eab0dadcda23dddb0bb8c47761049fbb2e992762e0eee39e0e6bee2ff87bd7ed67ff00411d0bff000449ff00c93401b7ff00058bff0093a05ffb16342ffd1ba957e48d7bd7ed09fb447c40fda4fc6a3c79f1166b59b531656d61bad2d45a47e4da195a31e5879390667c9ddce470315e0b40051457eb1ffc12fbf665f847fb46788fc69a77c53d3aeefe1d2349b0bab516b7f71625659ee6e2372cd6ef19605625c06240e48e49a00fc9dc1a4afebb4ffc12e3f63d033ff08eeb1ff850ea7ffc915fce0fedadf0d7c25f097f68ef1b780bc0f6d2dae8da36a82dad229a792e6458cd9da4d869652d239df2b9cb127040ce00a00ef3fe09c1ff002781f0d7fec36dff00a6ebfafec407415fc29fc1ef8b1e28f827f10747f891e0d7823d5b44b93756ad7108b8884862961cb4659370d933f1b873839e39fd04ff0087bd7ed658c7f68e85ff008224ff00e49a00fa3bfe0b69ff00238fc39ffb00eabffa5b675f863a4ffc7cc9ff005ed75ffa21ebe8efda47f6b3f8a9fb515fe8fa8fc4cb8b19e6d16da7b4b636564b64045712472b860249371dd1ae0e4606477af9c749ff008f993febdaebff00443d007f78be0eff00914f45ff00b07da7fe894ae92b9bf077fc8a7a2ffd83ed3ff44a57caff00b78fc6bf1dfc00fd9faefe22fc399ed6df598357d2ecd1ef2dc5d43e55dce239331965c9c1c8f985007d995fcab7fc15ebfe4ecb51ff00b01685ff00a05cd1ff000f7afdacff00e823a17fe0893ff926be1df8fdf1fbc77fb46f8ee5f885f10a5b59b559ad6dad1ded2d85a4663b40e23fdd877c11bdb27773c7031400bfb367fc974f01ff00d8d1e1ff00fd3ada57f7123a7e26bf82af06f8af54f04789f4bf15e8c516f749bdb5bfb732209104d673c7711165246e0248d4919191c646735fa5dff0f7afdacbfe823a17fe0893ff00926803faa8a2bf957ff87bd7ed67ff00411d0bff000449ff00c9347fc3debf6b3ffa08e85ff8224ffe49a00feaa28afe55ff00e1ef5fb59ffd04742ffc1127ff0024d7d35fb1effc147bf690f8e1fb457827e1b78c2ff479344d6efa7b7bd4b7d252da6289657570bb24133edf9e15cf1d3bd007f41d5fcdf7fc16c3fe4aef817fec569fff004b857f4803a0af97fe39fec79f033f68cf10e9de28f8a9a5df5fea1a5d93585ac96ba9ddd8aa40d2194a95b7910312c7249c9e07a0a00fe2970692bfaec7ff00825cfec7aaa4ff00c23bac700ffccc3a9fff002457f27fe39d32cf46f16eafa5d8294b7b5bfbc8225662c4243712468093c92154649e4f5a00e4e9e80ef5fa8afd0fff00826afc08f86ffb40fc78bbf057c4eb2b9bed2a3f0dea5a82c76b7935938b8b79ec9233e640c8f80b33e573839048e057eee7fc3ae3f63dff00a17758ff00c28753ff00e48a007ffc12d3fe4cafc1bff5ff00afff00e9d6eebeb2f8f7ff002437e21ffd8adacffe91cb5f851fb477ed59f137f609f8a1a87ecddfb3f3e9fa6f81b4082d6f2c2db51b36d56e966d595aeee4bdccf3891c34eeec371246ec7402bcd7c03ff052efda43e3478e3c3df08fc637ba3cde1ff1b6afa77873568adf4916b33e9fabdcc76772b1ce970cd1486295b6b804ab608a00fc85b907cc1ff5ce3ffd0057ef4ffc10f3fe42df187febd7c33ffa16a35f7da7fc12dbf63a44548fc37abaa280aaa3c41a98000e0003ed1c002be83f80ff00b2a7c1afd9b67d6ae7e1469b7961278812da3be377a8dd5fef5b33218b6fda647d9832be76e339e7a0a00fa368a28a0028a28a0028a28a00ffd2fdfca28a2800a28a2800a28a2803f0b3fe0b25f0dbc77f10755f850de0bf0feabadad8db78845c369ba75ddf884ccd61b049f66865d9bb636ddd8ce0e3a1afc48ff866cf8e9ff42278a3ff0009ed57ff00912bfb8ac5263ebf9d007f0edff0cd9f1d3fe844f147fe13daafff00225617897e097c56f0868f36bfe26f09ebda669d6e5165babdd1efed2043230440d2cf6f1c6a59880016049200c938afee9b1f5fcebf3d3fe0a9591fb15f8cc0cf37da07fe9dad2803f90fafeacffe0909ff00269d37fd8d7abffed1afe539fefb7d4d7f563ff0484ff934e9bfec6bd5ff00f68d007ea33ba468649085550496270001c924d70a3e2afc302323c5fa0ffe0cedbff8e5747e23ff00917f52ff00af3b8ffd16d5fc19dddd3c2d1c71a42079309e618c9c9452492572493401fd1bff00c161fc67e10f117c0af06db681ae69ba9cb1f8addde3b3bc8677553a5df2e488d988192067a64815fcde5910b7b6ecc7004a8493f514d96ea59976b88c0ff623443f9a819aaf401fd81fec13f10fc01a4fec81f0bf4ed57c4ba3d9ddc3a285960b8bf822950f9b270c8ce181f622ba9fdb1be237c3dd4bf655f8b361a7789f46bab99fc21abc71430ea16f24923b5bb00aaaae4b127a00326bf8dd4bc9a3508162207768a363f892a49a56bd9d94ae2219eeb0c6a47d085c8fc28024d5595f53bb6520833c8411c82371afe85ffe08d7e30f097873e1c7c4487c41ade9da64936b1a6b469797715bb385b2504a89194900f048ef5fcecd4f15c490821021cff7e357fcb7038fc2803fbb83f153e1863fe46fd07ff0676dff00c72bf90bf8e9f02fe2ff0089fe2ff8d35bd13c19e23bbb0bbf11eb93db5cc1a1ea33c13c13ea3732c52c52c56cf1c91c91bab2b2b10c0820d7c936b7524cef1c8909530cc7886307223620821720835fdd57c2b1ff0016c7c21d7fe403a677ff00a768e803f895f127c11f8ade11d264d77c4be13d774cb089911ee6f747bfb4855a46da80cb3dbc7182cc40505b2c4e064f15e555fd777fc151b8fd8f7c458cff00c863c3dffa73b7afe448f5a00f51f0cfc15f8a7e32d2975bf0c78535cd52c5dde35b8b1d22fef212f19c32896dede48f2a78237641e0806b765fd9c3e384313cd2f817c4ea91ab3b31f0fea80055192493680000773c0afe9a3fe0949cfec87a775ff91875effd2b6afd01f1871e13d648cffc83eefbff00d317a00fe0a9d1e3628e30460fe07907dc11d0f7afdc1ff8232f8a3c37e1bf16fc4397c43ab58e9892e87a5a46d79731db876179764853232e48041207622bf13b56ff008f98ff00ebdad7ff004425528a7921cec0a73d9d15c7e0181c5007f7727e2a7c31c1c78bb41ffc19db7ff1cafe5c3f6ebf84bf127e21fed47f103c51e0af0c6b7ade8d7fab24f67a8e9ba4df5f595d446c6cd37c3716d04b148bbe3652558e0a915f9d3a65dc936a56914b1c0c8f3c6ac0c116082c011f76bfb39fd87c67f644f8479ffa14f4dffd142803f91eff00866cf8e9ff0042278a3ff09ed57ff9128ff866cf8e9ff42278a3ff0009ed57ff00912bfb89c7d7f3a31f5fce803f83bf18fc38f1bf8025b787c65a1ea7a2bdd23490aea3617562d22210acc8b731445802402541009009c915cde93ff1f327fd7b5d7fe887afdceff82da7fc8e3f0e47fd40755ffd2db3afc31d27fe3e64ff00af6baffd10f401fde2f83bfe453d17fec1f69ffa252be17ff829ef877c41e29fd952ff0048f0ce977dac5f3ebda2482d74fb596f27291dc867611408ee42a82490a702bee8f077fc8a7a2ffd83ed3ff44a5749401fc3affc3367c74ffa113c51ff0084f6abff00c895e67e2bf06f89fc11aa1d1bc57a5dee937aa8921b7bfb59ece609202558c77091c80360e095c1c1c13835fdeae3ebf9d7f2b1ff00057aff0093b2d47fec05a17fe8175401f95745145001451450015f72ff00c139b54d3747fdaf7e1cdfead770595b43aa5c34935cc8b14680e997eb96672140c90393d4815f0d53e391a270e98c8ecc0303f504106803fbbc1f153e18607fc55fa0ff00e0cedbff008e52ff00c2d4f861ff00437e83ff00833b6ffe395fc267dbe7feec3ff7e22ffe268fb7cffdd87fefc45ffc4d007f76127c53f8625180f17683d0ff00cc4edbd3feba57f0f5f12258a6f1cebb2c2eb2236a9a832b210ca41ba988208e0820e47b5729f6f9ff00bb0ffdf88bff0089aa6492726803f5cffe08d7ff0027477bff00627eb1ff00a55a757f5115fcbbff00c11aff00e4e8ef7fec4fd63ff4ab4eafea22803f934ff82b27fc9e3f8a3fec1da1ff00e9257c87fb31ff00c9c47c30ff00b1d7c37ffa72b7afaf3fe0ac9ff278fe28ff00b07687ff00a495f21fecc7ff002711f0c3fec75f0dff00e9cade803fb87145028a0028a28a0028a28a0028a28a00ffd3fdfca28a2800a28a2800a28a2800a28a2800af9c3f6b0f81579fb48fc0fd6be11586af168536ad71a74e2fa7b76ba8e3fb0ddc374418924899b7f95b78718ce6be8fa2803f9f43ff00044ad7c9c9f89ba473ff00502bbffe58d7eacfec69fb376a3fb2cfc239be18ea5aedbf88647d62f3535bbb7b57b340b7623fddf96f2cc72a50f3bb90477193f59514014354b56bfd3aeac55821b882488311900ba95ce3db35f8087fe0899e22709e6fc4fd25d9111371d0aeb24228504e351033c7602bfa0aa2803f9f4ff8724ebdff00453748ff00c115dfff002c68ff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f4ff008724ebdff453748ffc115dff00f2c68ff8724ebdff00453748ff00c115dfff002c6bfa0ba2803f9f4ff8724ebdff00453748ff00c115dfff002c6be01fdb5bf629bdfd90ef7c336779e26b4f113788ad2fae95ad6c65b2108b296de22ac25b8b8dc5bed00820ae369e0e411fd84d7f3f1ff05b0826b8f10fc318ede3691ce8fae9da80b1ff008fbd3bb0a00fc11b7944321723394913fefb52b9fc335fd0ff0083ff00e0b29f0dfc39e12d13c3f37c3bd626934cd3ad2cde45d4ac943b5bc4b19600b640257383cd7f3d5fd93aaffcf9cfff007edbfc28fec9d57fe7ce7ffbf6dfe1401fd17ea1fb56f87bfe0a5fa55d7ecb1e10d12f3c0f7dab88b585d6b52961d4ada35d12e20ba68cc16d246ec64f957efae01272718af30ff8724ebdff00453748ff00c115dfff002c6be69ff82465adcdafed69a6adcc4f116d075e203a95cfc96deb5fd50d007cc1fb21fecfd7ff00b337c1bb7f859a96b506bf341a95fdf9bdb7b67b48c8bd97cdd822792561b338cef39afa2b5cb07d5347bed311c46d796d3401c8c8532a1404818ce335ab45007f3ecfff00044ef11cbb0cdf13f487748e38f71d0aeb2446a10138d440ce073803e94dff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f883fe089fe21b69a3b88fe26e8e1e26575ce8577d54e47fcc46bd5c7fc1417c1ffb1359d97ecade21f09ea5e26bdf8676969a04dacda5d5b59dbdf3c36d14be6a4333978c159572a4b60f426bf6bcf435fc77ff00c147ff00e4f03e257fd8717ff4dd61401fabdff0faef867ff44df5affc1a58ff00f1547fc3ebbe19ff00d137d6bff06963ff00c557f36d45007e8a7edf7fb65786ff006b7d6fc2daa787bc3b79a02e85a75e5948977730dc191ae678660ca61c801445820f3935f9ed65702da6672321a2963fa7988c99fc3766920b2bcba52f6d0492aa9c12885803f80a9ffb2755ff009f39ff00efdb7f85007f449a27fc167be1b695a35869aff0e75976b4b58202c353b10098e3552402d9e48ef5f547ecb1ff000519f097ed4bf137fe15ae83e0dd4b439469979a9fdb2eaf6dae232b66d02b4616125b71f3d4e4e07d7b7f267fd93aaffcf9cfff007edbfc2bf593fe08f96b736bfb5095b989e22de17d7480ea5723cdd3bd6803fa87afc99fdaff00fe09b1ad7ed47f192efe28daf8dec343b7b8d3ec2c858dce973ddba9b25906ff00323bb807cde61e369c60735facd45007f3ade29ff82316b9e1cf0ceafe217f893a4caba5d8dcde98d744ba56716f1b49b413a83004edc64838f435f865711793204ce7288e3e8ea1b1f866bfbbaf8a9ff24c3c5fff00601d4fff0049a4afe132ff00fd7aff00d7183ff452d007dcdfb14fec537bfb5e5ef89acecfc4d69e1d6f0eda58dd335d58cb7a2617b2dc4415445716fb4afd9c92496cee1c0c127efeff008724ebdff453748ffc115dff00f2c693fe0893ff0021ef89bff608d0ff00f4af51afe832803f9f4ff8724ebdff00453748ff00c115dfff002c68ff008724ebdff453748ffc115dff00f2c6bfa0ba2803f9f4ff008724ebdff453748ffc115dff00f2c6bf39bf6d2fd902f3f645f16e85e18bcf11daf885b59d2df5112dad9cb66b1049fc9d85659e72c4e7390c0638c57f6495fcdf7fc16c3fe4aef817fec579ff00f4b85007e2251451401fae9ff046bff93a3bdffb13f58ffd2ad3abfa88afe5dffe08d7ff0027477bff00627eb1ff00a55a757f511401fc9a7fc1593fe4f1fc51ff0060ed0fff00492be12f84be31b6f87df13bc25e39bcb77bb83c3dafe95abcb046ca8f2a585d4770c8acdf282c13009e013cf15f7a7fc1576d2eaebf6c8f148b685e52ba76864ec52d8cda7b57e6b3e97a922977b49955412498d8000773c5007f475ff0fadf867ff44df5affc1a58ff00f155f687ec7bfb6ff863f6bfbcf15da787bc337de1f3e158b4f9666bcba82e04c350338409e4938dbf6739cfa8afe39abf7c7fe0879ff216f8c3ff005ebe19ff00d0b51a00fe82e8a28a0028a28a0028a28a00ffd4fdfca28a2800a28a2800a28a2803f15bfe0adbf1f7e307c12d53e1847f0b7c57a9f86a3d62db5e6be5d3a558bcf6b56b1f28bee47cec123e31fdeafc71ff0086f8fdadbfe8a9789fff0002e3ff00e315fa59ff0005c4ff0090b7c1effaf5f137fe85a757e082a97385f427f00327f4a00fb0ff00e1be3f6b6ffa2a5e27ff00c0b8ff00f8c51ff0df1fb5b7fd152f13ff00e05c7ffc62b6742ff82777ed71e23d134ff10e93f0ef54b8b1d52d61bcb6992e34d0b243708248dc06be56c32b0232a0fa815abff0ed5fdb23fe89aeadff00813a5fff0027d00723ff000df1fb5b7fd152f13ffe05c7ff00c628ff0086f8fdadbfe8a9789fff0002e3ff00e315d77fc3b57f6c8ffa26bab7fe04e97ffc9f47fc3b57f6c8ff00a26bab7fe04e97ff00c9f401c8ff00c37c7ed6dff454bc4fff008171ff00f18a3fe1be3f6b6ffa2a5e27ff00c0b8ff00f8c575dff0ed5fdb23fe89aeadff00813a5fff0027d1ff000ed5fdb23fe89aeadff813a5ff00f27d00723ff0df1fb5b7fd152f13ff00e05c7ffc628ff86f8fdadbfe8a9789ff00f02e3ffe315d77fc3b57f6c8ff00a26bab7fe04e97ff00c9f49ff0ed5fdb23fe89aeadff00813a5fff0027d00725ff000df1fb5b7fd152f13ffe05c7ff00c62be98fd8f3f6c3fda5be20fed2df0e7c29e2bf88be20d4b48d4bc416d6d796773728f0cf0ba4ac51c2c4a482546466bf377e247c37f187c26f186a3e04f1de9d2e95ad6952ac377693346ef13bc693056685e48c931c88df2bb0c30e73903e8afd81ff00e4edfe16ff00d8cf69ff00a2a7a00fecdebcabe22fc0df83ff001767b1b9f8a1e0dd17c552e98924766fab5947766dd662a6411f980ed0e5549c75c0af55a2803e62ff00862cfd92bfe890f837ff0004f6dffc451ff0c59fb257fd121f06ff00e09edbff0088afa76be48d63f6effd91b40d5efb41d67e2668f69a869975359dddbcbe70786e2ddda39118797d55d483f4a00f9a7f6d6f857f0dbf668fd9ef5bf8a9fb3f786349f87be31b5bed22ca0d73c3d650d95fc56f7d7d0c3711ac8a9f7648d8ab02083dc702bf057fe1be3f6b6ffa2a5e27ff00c0b8ff00f8c57ecb7fc141bf6c0fd9abe2a7ecc5ae783be1ef8fb4bd735ab8d4f459e2b2b632195e3b6bf8659580280612352c79e82bf9a43401f62ffc37c7ed6dff00454bc4ff00f8171fff0018a3fe1be3f6b6ff00a2a5e27ffc0b8fff008c564fc2efd8a7f68ff8c9e128bc6ff0f3c157fad68d34f3db25ddbcd628865b76d922e27bb864cab020fc98f42457a27fc3b57f6c8ffa26bab7fe04e97ffc9f401c8ffc37c7ed6dff00454bc4ff00f8171fff0018a3fe1be3f6b6ff00a2a5e27ffc0b8fff008c575dff000ed5fdb23fe89aeadff813a5ff00f27d790fc62fd923e3d7c07d0ecbc45f147c277ba0586a172d696f35ccb6722c93244d31402dee67604468cdf30030a79cf0403b0ff86f8fdadbfe8a9789bff02e2ffe315f34f8e7c77e2bf891e26bef1878d353b9d5f57d4a5f3aeaf2edc3cd3481123dce40504ec455e00e14572914524f2a4110dcf230451ea58e00fcebec9f067ec09fb5478ffc29a4f8dbc27e02d4b51d1b5bb48ef6c6ee19f4f58e78261947512dec7200c39c322b7a81401f19515f66f8cff604fdaa3c01e14d5bc6de2cf016a5a768da25a497b7d7734fa7b4704108cbbb08af6490851ce1519bd01af8da58a482578251b5e3628c3d0a9c11f9d007f415ff0004a1f803f04be2bfc21f17ea9f12bc0da0f89ef2cbc416f6f6f71aad845752450b69b6b29446914955323b36071b989afd51ff00862cfd92bfe890f837ff0004f6dffc457c17ff000461ff009225e38ffb19ad7ff4d5675fb23401f317fc3167ec95ff004487c1bff827b6ff00e22bb4f017ece5f01fe176ba7c4ff0e7c03e1ff0d6ac6092d4dee97a7c56b398252a5e32f1a82558aa923a702bcb352fdbd3f642d1f52bbd2354f89da3db5ed8cf2dadcc1279caf14d03949118797d55948aed7e197ed5ff00b3c7c64f137fc21df0c3c6fa778875916d2de1b4b4f30b8b784aabb92c814052ea393ce78a00fa1e8a2be74f893fb5afece7f07fc512782be25f8e74df0feb714115cbd9ddf9a24114e098db2a8548600f43f5a00f4ff8a9ff0024c3c5ff00f601d4ff00f49a4afe132fff00d7affd7183ff00452d7f5edf10ff006f5fd90356f00f8974ad3be2868b35d5e68f7f6f044a65cbcb2c0ea8a3f77d4b10057f20d78eb24ca50e408a253f558d411f811401fbcdff000449ff0090f7c4dffb04687ffa57a8d7f4195fcf9ffc1127fe43df137fec11a1ff00e95ea35fd065007f2c1fb61fed87fb4b7c3efda5be237853c29f117c41a6e91a6f882e6dacecedae5121821448982206898800b1c0cd74bfb09fed71fb477c4cfda9be1ff84bc69f10b5ed5b46bfd4ae22bbb1bbb94782741a7de4aa1c2c484e1e35239c7147ed71fb09fed4ff00133f68ef885e34f097c3fd4aff0046d5b5eb9bbb1bb8ae34f093c0e912870b2de46e3250f054715d27ec41fb107ed3bf09bf69df01f8e3c71e03d474bd0b4bd46e27bdbd9ee2c1a38236b0bb8549586ee590e6495470a7ae4d007f4aa3a0afe6ff00fe0b61ff002577c0bff62bcfff00a5c2bfa401d057f37fff0005b0ff0092bbe05ffb15e7ff00d2e1401f88945145007a47c30f8b7f10fe0e7881fc51f0db5ebef0fea925acb66d7561208e5304cc8cf1e595c6d668d09e3f84735efcbfb7bfed6ccc17fe169789f938ff008fb8bff8c57897c1ef823f12be3bf89e4f087c2fd12e35dd562b39afdadadde08dc5bc0d1a3be6e268530ad2a023767e618079c7d36bff0004d6fdb21581ff00856bab7073ff001f3a5fff0027d007eddfec4bf097e17fed2bfb397877e2f7c7cf09e8de3ef1aeab75ab417baf6bd6105e5fdc47657f3db5bac92b2024450c688a30000a38af65f8cffb1ffecb5a2fc1ff001ceb1a4fc29f08da5ed8f87356b8b6b88749b749219a2b59591d182e559580208e845783fec87f1c3e137ec8ff0001741f817fb46789ac7c0de3bd16e353b9bed13517df71043a85f4f756cccd6fe6c444904a8e36c8d8ce0e0822bd9be227eda5fb2efc47f007897e1e781be21e93ac788fc51a45f68da3e9d6ed209af350d4217b7b6823de8abbe595d517730193c9039a00fe412e0059001c6510fe25413fad7ef57fc10eff00e42df187febd7c33ff00a16a35f01bff00c1367f6ca936b3fc33d555b622b0175a59195500e0fdbc6471e95faedff04a6fd9a3e34fecf9a97c4b9be2d785eefc3b1eb96fa125835d4b6b279ed68d7a660bf66b89f1b04a9f7b6e73c6707001fb23451450014514500145145007ffd5fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6be0201ff0a37e1e7fd8aba37fe91c55eb5815e4df013fe486fc3cff00b15b46ff00d238abceff006c1f8e5aefece5f0135df8b7e1cd3ad355bfd26e34d863b5be67581c5f5e436cc58c7f30dab29231dc74a00fa77028c0afe6fcff00c16bfe2e8241f02f85b8ff00a6f7dfe147fc3ec3e2effd08be16ff00bff7dfe1401fd206051815fcdfff00c3ec3e2eff00d08be16ffbff007dfe149ff0fb0f8bbff422f85bfeff00df7f85007f483814d2060d7e59fec15fb7a78f3f6b8f1e788bc2de29f0ee8fa35ae8da3c7a8c52e9d25c3c9248f7021dade710028193c0f4e6bf534f43401fc7bffc14abfe4f23e257fd85adbff4d7a7d723fb03ff00c9dbfc2dff00b19ed3ff00454f5d7ffc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f401fd9bd145140087a7e22bf87dfda3eeae62f8e5e3c58a69117fe128f109c2b103fe42b77e86bfb823d3f115fc3b7ed27ff0025d3c79ff6347883ff004eb77401e2925d5d4abb259a4753d99891fa9a828a2803fad6ff00825201ff000c87a77fd8c3af7fe95b57e936057f27ff00b347fc14d3e217ecd9f0b6dfe17f87fc2ba16a76905f5edf0b8be96e9262f7b219597116570a4e077c75afab7c07ff000589f8b9e33f1a685e177f04f86618b55d52c2c659126bd2e91de5cc56ecca1b00b289323247f4a00fe84f02bf1bbfe0b3c07fc292f03ffd8cd75ffa6abcafd91afc6eff0082cf7fc912f03ffd8cd75ffa6abca00fe69747ff0090b597fd7cc5ff00a18afed0bf61f03fe1913e11ff00d8a7a6ff00e8a15fc59daced6b7315d2005a1916400f425483cd7eb6fc1fff0082b4fc4cf841f0bfc2ff000c349f077876f2cfc31a5dbe990dc5ccd7826952dd768770836863dc0e2803f77ff6e003fe1913e2e7fd8a7a97fe8a35fc5eeb1ff216bdff00af997ff4335fbc9f0eff00e0a0de3afdb63c4f6dfb2ef8cbc35a3687e1ff00890b73a06a3a8e9335c35fdb413dacf297805c0316ff00dce01756033f74d7bfc9ff000464f80f348d34be36f16b3c84b3129a56493c93ff001e14015bfe08c3ff00244bc71ff6335aff00e9aacebf640d7cbdfb2cfecabe10fd947c2dacf84fc1dac6a9acdb6b5a8a6a32cbaa0b71224890476e113ecd1429b3644bd5739cf35f511a00fe1a7e3bdd5cc5f17bc60914d222ff00c241adf0ac40ff009095d7a1afd0dff823ccd34dfb50b19a46908f0c6ba01624ff00cb5d3bd6bf3afe3d7fc95ff187fd8c1adffe9caeabf43ffe08e9ff002740ff00f62c6bbffa374da00fea3abf95bff82bb4d343fb59ea46191a32741d0812a48cfc973e95fd5257f2afff00057aff0093b2d47fec05a17fe8173401f96a6f6f581569e520f04173cfeb5568a2803f7c3fe0893ff21ef89bff00608d0fff004af51afe832bf8dbfd903f6d2f16fec8b79e22bcf0c685a5eb2de21b5b3b5946a2f3a0896ce59e5529e4e7258cec0e78c018afb8ff00e1f61f177fe845f0b7fdff00beff000a00fe903028c0f4afe703fe1f61f177fe845f0b7fdffbeff0a3fe1f61f177fe845f0b7fdffbeff0a00fe902bf9beff82d87fc95df02ff00d8af3ffe970a3fe1f61f177fe845f0b7fdff00beff000af817f6bcfdaf3c51fb5bf8a345f13f89f45d37469b46d35f4e8e3d39e6747479bcedcde772181e38e31401f1f51566d6159a4656e8b1c8ff00528a5b1f8e2bfa5ad2ff00e08d5f01afb4cb4bd7f1a78b15a782294809a5e01740c719b1cf7a00f83ffe08d9ff0027477bff00627eb1ff00a55a757f511815f007ecc9ff0004f2f863fb2dfc4597e24783fc49af6ab7b2e9775a51b7d4859080457524123b816d6d0b6f0605032c4633c57e805007f271ff00055f9a683f6c8f14986468f3a76879da4ae7fd13dabe46fd99eeae65fda1be18a4b348ea7c6be1bc866247fc84edfd4d7d6bff000564ff0093c7f147fd83b43ffd24af90ff00663ff9388f861ff63af86fff004e56f401fdc3003d2970074a051400514514005145140051451401ffd6fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054bff932bf197fd7fe81ff00a75b4a00fe445fefb7d4d329eff7dbea69940051451401fb77ff00044fff0092bbe3affb15e0ff00d2e35fd201e86bf9bfff008227ff00c95df1d7fd8af07fe971afe900f43401fc7c7fc14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f5d7ff00c14abfe4f23e257fd85adbff004d7a7d721fb03ffc9dbfc2dffb19ed3ff454f401fd9bd145140087a7e22bf876fda4ff00e4ba78f3fec68f107fe9d6eebfb893d3f115fc3b7ed27ff25d3c79ff006347883ff4eb77401e25144d336d52a3dd9828fcce055afece9bfe7a41ff007fe3ff00e2abf4f7fe091d696b79fb5769f0ddc31cf19d0b5d25644575384b6c70c08afea5bfe11cd03fe81b67ff0080f1ff00f13401fc167f674dff003d20ff00bff1ff00f155eaff00032dcdb7c5df0734b243f3788744550b2a3127fb46d8f00127a027e95fdbeffc239a07fd036cff00f01e3ffe2681e1ed055832e9d680820822de3c823a1fbb401b22bf1bbfe0b3dff244bc0fff006335d7fe9aaf2bf646bf1bbfe0b3dff244bc0fff006335d7fe9aaf2803f994a28a2803eebff82707fc9e07c35ffb0e37fe9bafebfb101d057f1dff00f04e0ff93c0f86bff61c6ffd375fd7f6203a0a005a0d141a00fe18be3d7fc95ff187fd8c1adffe9caeabf43ffe08e9ff002740ff00f62c6bbffa374dafcf0f8f5ff257fc61ff006306b7ff00a72baafd0fff00823a7fc9d03ffd8b1aeffe8dd36803fa8eafe55ffe0af5ff002765a8ff00d80b42ff00d02e6bfaa8afe55ffe0af5ff002765a8ff00d80b42ff00d02e6803f2b554bb051d4d5dfecf9bfe7a41ff007fe3ff00e2abd87f66f4493e39781639155d5bc4fe1f04300410755b40410783915fdb97fc23ba01e4e9b67d4ffcbbc7ff00c4d007f04b35b3c001668db3fdc757c7d76938aaf5fd1dff00c168b4cd3ac3e1a7c396b2b582dcb6b3a9ee3144a84e2c5b192a066bf9d4d2403aa5983c8f3e3ffd08500469632ba860f0ae7b34a8a47d41208a24b296342e5e26c7659518fe001cd7f67dfb19687a2dc7ec9ff08e69ec2d6491fc1da396668236624db272495c935cafedfda268f6bfb1cfc539edac2d62917443b5d204561fbe8fa10b91401fc6fd156af862f6e07fd357ff00d08d55a00bda7ffad93feb84dffa2dabfbd5f0e7fc8034dffaf3b7ff00d16b5fc1569ffeb64ffae137fe8b6afef57c39ff00200d37febcedff00f45ad006c160a324e29be627f787e62bf317fe0ad9a8ea1a57ecaf05e699753d9cebe2ad2544b6d2bc326d613023746cad823debf97bff00859be3dffa0feabff831bbff00e3d401f7affc158c83fb63f8a31cff00c4bb43ff00d24af913f663ff009388f861ff0063af86ff00f4e56f5e37aaeafa96b778da86ab7335ddc3801a59e5799c851800bc8ccc703a64f15ec9fb31ff00c9c47c30ff00b1d7c37ffa72b7a00fee1c5140a2800a28a2800a28a2800a28a2803fffd7fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc16b6ff00587feb9c9ffa01a00fee6fe027fc90df879ff62b68dffa47157c9bff00054bff00932bf197fd7fe81ffa75b4afacbe027fc90df879ff0062b68dff00a47157c9bff054aff932cf197fd7fe81ff00a75b4a00fe445fefb7d4d32a5747dedf29ea7b537cb7fee9fca8019453fcb7fee9fca8f2dffba7f2a00fdb8ff8227ffc95df1d7fd8af07fe971afe900f435fce07fc114011f177c7408c7fc52d07fe971afe8fcf43401fc7c7fc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f5d7ffc14abfe4f23e257fd85adbff4d7a7d721fb03ff00c9dbfc2dff00b19ed3ff00454f401fd9bd7c9bfb48fed97f08bf659d4342d37e26c5ac3c9e21b7bab8b46d32cc5da84b378924f33f78854e665c7041f50719facabf9f3ff82db7fc87be197fd8235cff00d2bd3a803ec23ff057afd93b04987c5785058ffc4a3b2f27fe5b0ed5f9dde3aff82597ed1bf173c5dac7c4dd02ebc2b1e95e2ad46fb5cd3d6eb55b98ae05a6ab732dec2268d6c245491639c0755760181c3115f8d761febdbfeb8cff00fa29abfbb2f857ff0024c3c21ff601d33ff49a3a00fc85fd843fe09e7f1cff00669f8f167f11bc7973e1d9747834bd4acdd74dd427bab832de2c4130925a40bb47967277679e95fb6f451400552d4afe1d2f4fb9d4ae0318ad6192770a32db6252c7038e7038e6aed737e31ff914f5affb07ddff00e897a00fcd85ff0082bdfeca0c88ed6fe2c42e8afb5b4819018023a4c41e0f50483d8e2bf3c7fe0a33fb72fc13fda6be1a7867c2ff000dd35a5bdd2b5a9efae3fb46c45ac7e4c963716e36b798d96df2ae47a64d7e37eadff1f31ffd7b5aff00e884acd0a5bee827e94009453fcb7fee9fca98411c1a00fa83f63bf8afe15f82bfb41f837e23f8cc5d1d2343d48dd5d7d8e2f3e6f2cda5d43f226e5dc77ccbc67a64f6afe8487fc15ebf64ec7fa9f15ffe0a3ffb757f29a013c0a7f96ffdd3f95007f6dffb38fed3df0e3f6a2f0eeade28f86b16a715968d7eba75c7f6a5b0b591a668639c1450ee4aec917938e7a0c609fa28d7e377fc11878f827e38ff00b19ad7ff004d5675fb206803f863f8f5ff00257fc61ff6306b7ffa72baafa67fe09e7fb447c3ff00d9b7e36b78efe22aea0da61d1353b1ff008975b8b99bcebb7b468fe4dcbf2e207c9cf1c7ad7cd1f1e558fc5ff181009ff8a835bedff512baaf202ac39208fc2803fab0ff0087bd7ec9dff3c7c57ff828ff00edb5f1a7c7dfd97be217fc1463c663f695f81136956de0fd5acedf4ab78fc4575369da80b8d1e49ede72d0456d74a10bb1db970d81c819afc191d6bfaedff825c7fc99ef877fec31e21ffd39dc5007e597817fe0965fb46fc23f1768ff001375fbaf0ac9a5785751b1d73505b5d56e65b8369a55cc57b308636b08d5e468e021159d416232c057e888ff0082bd7ec9d80443e2bc300c3fe251d9b91ff2d8f6afd0ef8a9ff24c3c5fff00601d4fff0049a4afe132ff00fd7aff00d7183ff452d007ebff00fc148ff6d9f833fb4ff833c1da17c344d656e745d46f6eae7fb4ac85aa7973db185761f31b71dc791d857e42691ff215b3ff00aef1ff00e842b3c2b37dd04fd2b4b49471aad9fca7fd7c7dbfda1401fda67ec59ff2695f087fec4dd1ff00f4992b94ff0082837fc99a7c55ff00b021ff00d1d1d757fb167fc9a57c21ff00b13747ff00d264ae53fe0a0dff002669f157fec087ff004747401fc6b5ff00fc7f5c7fd757ff00d08d54abb7c8ff006db8f94ffad7edfed1aabe5bff0074fe54016f4fff005b27fd709bff0045b57f7abe1cff0090069bff005e76ff00fa2d6bf82cb04712c84a91fb89bb7fd336afef4fc39ff200d37febcedfff0045ad007e67ff00c15eff00e4d3a1ff00b1af48ff00dad5fca657f567ff00057aff00934e87fec6bd23ff006b57f29fe5bff74fe5400caf4ff82be2bd2bc0bf173c13e33d6fcd3a7e85e24d1f54bbf2137cbf67b2bc8a79762e46e6d88768cf278af33f2dff00ba7f2a3cb7fee9fca803fab0ff0087bd7ec9dda2f15ffe0a3ffb757d3bfb367ed8df08ff006a9b9f105a7c304d5964f0d476725eff0069d9fd97e5be3308b67cedbb981f3d31c57f165e5bff0074fe55fbdfff00043d046adf184118ff0045f0cffe85a8d007f415451450014514500145145007ffd0fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf05adbfd61ff00ae727fe806bf7a7fe0b89ff216f83dff005ebe26ff00d0b4eafc15b72164249c7c920fcd4d007f739f013fe486fc3cff00b15b46ff00d238aaff00c5bf849e05f8e1e05bef86ff00122c64d4740d464b796e2de2b89ad5d9ed6649e222581d245db2229f95867183c578f7c0ef8e1f05ecbe0bf80acaf7c7be1982e2dfc33a4452c526af689247225a44acaca650432904107906bd4ffe17d7c0effa287e16ff00c1cd9fff001da00f933fe1d6bfb167fd09b7ff00f8506abffc9547fc3ad7f62cff00a136ff00ff000a0d57ff0092abeb3ff85f5f03bfe8a1f85bff0007367ffc768ff85f5f03bfe8a1f85bff0007367ffc76803e4cff00875afec59ff426dfff00e141aaff00f2551ff0eb5fd8b3fe84dbff00fc28355ffe4aafacff00e17d7c0eff00a287e16ffc1cd9ff00f1da3fe17d7c0eff00a287e16ffc1cd9ff00f1da00f3af81bfb1f7c04fd9cb5dd47c47f09342b8d26ff54b45b1ba926d4af2f83c0afe6050b73348ab86e72a01afa70f435e51ff000bebe077fd143f0b7fe0e6cfff008ed21f8f5f03b07fe2e1f85bff0007367ffc76803f950ff82957fc9e47c4affb0b5b7fe9af4fae43f607ff0093b7f85bff00633da7fe8a9eb67fe0a23aee89e23fdae3e226ade1fd42d753b1b8d52dde1b9b3992e2191469b6284a49196561b948c83d411dab1bf607ff0093b7f85bff00633da7fe8a9e803fb37afe7cff00e0b6dff21ef865ff00608d73ff004af4eafe832bf0abfe0b0df0d3e227c40d7be1c1f02f8575cf11a5b697ad453be8fa65d6a0b0bc97362c82436f1c9b0b2a315dd8ced38a00fe772391e26dc8704ab29fa302a7f435fa35a2ff00c153ff006bcd0b47b1d134ff001369e96ba7db4369029d16d18ac702045058f24800727ad7cac3f663fda209c7fc2b0f1ae4ff00d4b7a9ff00f23d78bea3a6dfe917b3e9baa5b4d67756d2490cd05c46d14b1c913147474601959594ab020104107906803fa0aff827c7edd5fb467ed0bfb42d8f813e24ebd697da14ba4eab74f6f06996f68e66b5584c67cc8c16c0f30f008f7cf6fde5afe4e7fe094be29f0cf84bf6a4d3f55f156af63a3592e89ad235c6a1731db421a44b7daa5e4655cb60e0679c1afe9bbfe17d7c0eff00a287e16ffc1cd9ff00f1da00f58ae6fc63ff00229eb5ff0060fbbffd12f5c5ff00c2faf81dff00450fc2dff839b3ff00e3b5cf78b3e3afc129bc2fabc30fc40f0bbbbd85d2aaaeb3664926170001e6f24d007f11fab7fc7cc7ff005ed6bffa212bf547fe097bfb327c1afda3bc47e34d3be2de8d3ead0e93a4d85d5a086feeac4a4b3dd5cc6e49b69622c0ac6a30d903191c935f957a9babdc2142180b7b65c839e5614047e07835fba3ff00044aff0091c7e237fd8074affd2dbca00fd0f3ff0004b5fd8af07fe28cbfff00c28355ff00e4aafe6fbf6d6f869e0df847fb4778dbc03e02b27b0d1347d505b59dbc93cb72c911b3b49b064999e46f9e573966279c670063fb4c3d0d7f289fb7e7c0cf8cfe2ffdabbe22eb3e19f00f8ab55d3ee7584960bcb0d0efeeada64363669ba39a281a371b9194e18e0822803e6efd8a7e1a7837e2e7ed1de09f00f8f6c9eff44d635436d796f1cf2db33c42ceee6c092164917e7890e5581e319c139fe9047fc12d7f62b23fe44cbfff00c28355ff00e4aafc3efd86be11fc51f863fb4df813c69f113c1fe21f0c787b49d524bbd4357d6b48bcd3b4eb2b74b1bc432dc5d5cc51c3126e915773b819615fd3a0f8f3f0380c1f885e16ff00c1cd9fff001da00c9f81bfb3bfc28fd9cf42d47c39f09b4b9b4ab0d56f05f5d473dedcdf33ceb1242183dcc92380234518040e3a57b7d793ff00c2faf81dff00450fc2dff839b3ff00e3b4c93e3f7c0b891a597e2278511114b3336b56602aa8c92499780075a00f98b5bff8266fec79e21d62fb5ed5bc237d35eea5753de5c38d77548c34d73234b23044ba0aa19dc9c280067815f0a7fc1403f614fd9a3e077ece97be3df86fe1abad3b5a8b58d26d1279756bfbb510dd5c08e51e5cf3c919254e01db91d4106bf77a19a2b8892781d648e450e8ea432b2b0c8208e0823a1afcfcff00829cf85fc4de2ffd95750d1bc25a3ea1ae6a0daee8b28b3d32d26bdb931c57219d845023c842a8c920702803f90c1d6bfaedff00825c7fc99ef877fec31e21ff00d39dc57f30dff0cc9fb43e7fe498f8d7ff0009bd4bff0091ebfa93ff00826bf86fc47e13fd93f40d17c55a4dfe89a8c7aaebb23d9ea56b2d95ca24da84ef1b3433aa48a1d1830ca8c839a00fae3e2a7fc930f17ffd80753ffd2692bf84cbff00f5ebff005c60ff00d14b5fdd9fc54ff9261e2fff00b00ea7ff00a4d257f0997ffebd7feb8c1ffa296803f58bfe0977fb2ffc17fda3b55f1cdafc5cd1a7d5a3d1b4ed2ae2cc437f75626392e6e2f2390936d2c45b2b0a0c3640c71824d7ec1aff00c12dbf62d460cbe0ebf041c823c41ab0208ffb7bafcd2ff8237f8f3c0fe0bd6fe23bf8c3c43a5686b71a568a909d4af61b412325d5f9609e6b2ee2a1d490338047ad7eef8f8f5f038f4f885e16ff00c1cd9fff001da00eb7c0be0af0efc37f0768de02f08dbb5a68be1fb2874ed3edda4799a2b6b75091a99242cef8500658927b9aa5f123e1d7853e2cf81f57f875e39b57bed075c83ecd7d6e9349034916e0d81244caebca8e54835d3e91abe95afe996dad687796fa8e9f7b124f6d776b2acd04f138cabc72212aeac390c090474a8f5bd7346f0de9771adf886fedb4cd3ad177dc5dde4c96f6f0ae40cbc9210aa3240c92393401f049ff00825b7ec584927c1b7e49e49ff848356ffe4ba3fe1d6bfb167fd09b7fff008506abff00c955f59ffc2faf81dff450bc2dff00839b3ffe3b47fc2faf81dff450fc2dff00839b3ffe3b401f24bffc12dbf62b08d9f065f9054823fe120d57904723fe3eba115f8bde26ff0082a2fed79e16d7f51f0f69fe27d3d6d34dbcb9b3b756d16d1d961b699e18c16206e3b5064e3935fd2a49f1e7e0714603e21785ba1ff98cd9ff00f1dafe253e234f05d78df5cb8b691258a4d4efd91d1832b2b5d4a4104704104107b83401fb41fb25fc71f1e7fc1437e245c7c0cfda8aea0f1278320d1eef5f8ecacedc69128d474f9ed63824fb459ba4a42adcc9f2ee009c120e057e8dff00c3ad7f62bffa136fff00f07faaff00f2557e417fc11aff00e4e8ef7fec4fd63ff4ab4eafea22803f3d3fe1d6bfb167fd09b7ff00f8506abffc9547fc3ad7f62cff00a136ff00ff000a0d57ff0092abf42eaadeded9e9b673ea1a84f1db5adb46f34f3cce238e28e31b99dd9880aaa012492001401f9fdff0eb5fd8b3fe84dbff00fc28355ffe4aaf7ff811fb297c11fd9ae7d6ae7e1068b3e912f8852d63d41a7d42eefbcd5b3329840fb54d2ecda667fbb8ce79e82bb31f1efe069191f10fc2b83c8235ab3ffe3b5d57863e20f813c6cd709e0df11e93af3598437034cbd86ecc224cec32089db6eeda719c67071d2803afa28a2800a28a2800a28a2803ffd1fdfca28a2800a28a2800a28a2803f9f4ff0082e27fc85be0f7fd7af89bff0042d3abf03abfaeefdba3f617bcfdb26f3c1b7769e328bc29ff0008a45a9c4cb2e98751fb4ff689b63918b9b7d9b3ecff00ed67776c73f01ffc38ef56ff00a2c36bff0084d37ff2c6803f0505c48a000138f58d0feb8a5fb4c9e91ffdfb4ff0afdeaff871deadff004586d7ff0009a6ff00e58d1ff0e3bd5bfe8b0daffe134dff00cb1a00fc15fb4c9e91ff00dfb4ff000a3ed327a47ff7ed3fc2bf7abfe1c77ab7fd161b5ffc269bff0096347fc38ef56ffa2c36bff84d37ff002c6803f057ed327a47ff007ed3fc28fb4c9e91ff00dfb4ff000afdeaff00871deadff4586d7ff09a6ffe58d1ff000e3bd5bfe8b0daff00e134dffcb1a00fc15fb4c9e91ffdfb4ff0a3ed327a47ff007ed3fc2bf7abfe1c77ab7fd161b5ff00c269bff96347fc38ef56ff00a2c36bff0084d37ff2c6803f03d98bb6e38fc0003f21c57d87fb03ff00c9dbfc2dff00b19ed3ff00454f5fa5bff0e3bd5bfe8b0daffe134dff00cb1af62f805ff0492d53e097c5ff0009fc5293e27db6af1f86b548b516b15d05ad5ae3ca575d825fb748133bfaec6e9401fb514628a2801a47d7a8afe1dff694ff0092e9e3cffb1a3c41ff00a75bbafee28f35f859f123fe08dbaafc41f1df883c66bf15ed6c975bd5751d496d8f879a6308bfbb9ae7cb2ff6f4ddb3cddbbb68ce3381d2803f9d84728772e3f1008fc8f1537da64f48ff00efda7f857ef57fc38ef56ffa2c36bff84d37ff002c68ff00871deadff4586d7ff09a6ffe58d007e0afda64f48ffefda7f8521b8908c613fefda7f857ef5ffc38ef56ff00a2c36bff0084d37ff2c68ff871deadff004586d7ff0009a6ff00e58d007e06f5e4d7ef0ffc112bfe471f88dff601d2bff4b6f2b47fe1c77ab7fd161b5ffc269bff009635f79fec3ffb085f7ec79ae789757bbf1a43e2a5f10585a592c7169874f307d9669a6dc49b99f7eef3718c0c63be7800fd17a4c52d1401f2b7edc031fb227c5cff00b14f52ff00d146bf8c2d525316a77714691aa24f22aa88930006200e95fdc77c75f86b27c64f83de2ff8570ea0ba4bf8ab49b9d2d6f5a13702dcdc2edf30c41d0bedebb772e7d457e2a5d7fc110b57bab99ae4fc60b453348d211ff08d39c6e39ffa08d007e077da64f48ffefda7f855ed3a4f3a7912548d97ecf727fd5a75585c83d3a82322bf773fe1c77ab7fd161b5ffc269bff0096353dbffc110b57b77671f182d4ee8e58ff00e45a71feb11933ff00211ed9cd007eeff83bfe453d17fec1d69ffa252ba4acbd16c0e95a4596965fcc3676d0c05c0c6ef2902671ce338cd6a500263ebf9d2d14500707f153fe49878bff00ec03a9ff00e93495fc265fff00af5ffae307fe8a5afef3bc5ba2b7897c2dac78712616e755d3eeac84c57788cdc44d1eedb919dbbb38c8cd7e0d49ff000440d62560cdf186d321113fe45a7e88a147fcc47d05007e06a48d1fddda73fde50dfcc1ad1d32532ea36b14891b23cd1ab03126082c323a57eeeffc38ef56ff00a2c36bff0084d37ff2c6a7b6ff0082216af6d7315cafc60b426275700f869f9da73ff411a00fd66fd8b3fe4d2be10ffd89ba3ffe9325727ff0506e7f633f8ab9ff00a021ff00d1d1d7bbfc11f87327c21f843e0ff85d2df8d51fc2ba2d9692d7ab11805c1b48c47e608cb3ecdd8ceddcd8f53591fb447c2697e3afc16f167c2487535d19fc4d63f6317ed01b9107ceafb8c41e32ff007718debf5a00fe1f2f2764bb9d11630ab23800469c004fb557fb4c9e91ff00dfb4ff000afdee97fe087fac4d2bcadf186d32ec58e3c34fd49cff00d046a3ff00871deadff4586d7ff09a6ffe58d007e0afda64f48ffefda7f8557afdf2ff00871deadff4586d7ff09a6ffe58d1ff000e3bd5bfe8b0daff00e134dffcb1a00f01ff008235ff00c9d1deff00d89fac7fe9569d5fd4457e587ec63ff04dfd43f64cf8af3fc4ab9f1fc1e278e6d16f349165168ed60c0ddcb6d2f986537738217ecf8dbb0677673c60fea7d0015e4df1ec03f033e2183c83e15d6baffd79cb5eb35c87c41f0c3f8dbc09e23f0647702cdb5ed26f74d17053cc109bb85e2de532bbb6eece32338c645007f07f34cd1b2aaac607971ffcb343d547a8afde6ff821f36fd5fe3092147fa2f867eea851f7f51ec00a56ff00821eeaee416f8c369c2aaffc8b4ffc200ffa08fb57df5fb0bfec2f79fb1b5e78caeeefc65178affe12b8b4c89562d30e9df66fece37272737371bf7fda3fd9c6def9e003f43a8a28a0028a28a0028a28a00fffd2fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd3fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd4fdfca28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2800a28a2803ffd9\",\n        expectedOutput: \"http://en.m.wikipedia.org\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"Parse QR Code\",\n                \"args\": [false]\n            }\n        ]\n    },\n    {\n        name: \"Parse QR Code : PNG\",\n        input: \"89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 7d 00 00 00 7d 08 00 00 00 00 aa eb 33 f9 00 00 01 06 49 44 41 54 78 da ed da cb 0e c3 20 0c 44 51 fe ff a7 db 5d a4 54 d8 99 29 01 24 73 59 75 91 f8 b0 70 79 38 6e 9f 9d a3 a1 a3 a3 a3 af d6 db f3 c8 9f b3 a2 a0 a3 5f ef c5 e9 79 8f 2f fc ca a3 a0 a3 77 22 f4 72 b9 17 eb 37 a0 13 05 1d 5d 7c cf 7a 0e 1d 7d 46 ce e7 2b 32 3a fa 2b fb 7b b8 c8 2e 39 5d a0 17 d4 d5 bb 8c ba fa ce b9 49 a1 97 d1 e5 62 4b 94 ee 4b aa 46 e8 b5 f4 5e de 86 1b b5 ba e7 87 41 d1 d1 c5 ac 56 27 28 ac d2 e8 e8 e9 dd c3 af e0 c8 45 1e f4 d3 75 f5 b3 47 5e b7 b6 8a d7 e8 e8 cf b5 67 6b f5 f5 6e d0 e8 a7 eb ea 5d 26 9f a5 90 f8 e8 c7 eb 23 75 1b 61 5a 6f 76 bc a0 d7 d2 d5 de 03 6b 82 61 64 74 74 eb 4b 9a d3 e2 12 9f 0d d0 d1 ff ea 3d b0 56 5f 74 f4 51 5d a8 4c e7 7f 0b 74 74 bb f7 40 d8 f8 27 76 3e a0 97 d1 47 8e 8c d6 77 3b 74 f4 4d 03 1d 1d 1d 7d cd f8 02 b8 db 6e a8 48 1e c7 f1 00 00 00 00 49 45 4e 44 ae 42 60 82\",\n        expectedOutput: \"Hello world!\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Space\"]\n            },\n            {\n                \"op\": \"Parse QR Code\",\n                \"args\": [false]\n            },\n        ],\n    },\n    {\n        name: \"Parse QR Code : Transparent PNG\",\n        input: \"89504e470d0a1a0a0000000d49484452000000e8000000e80806000000e5a9a878000000017352474200aece1ce900000a49494441547801edddcb6e6db90d04d09ba0ffff97936b0499b20e4ccbb51fab8120035a22b5740a7b2024fdaf3f7ffefce7ef7f9efccfbf96874b3e69ffedfae5f8ebfb4de74bf3a5f3a7f5a9ff76ffd4bf5aff77b5bbe604088c02023af22812e80a0868d75f7702a380808e3c8a04ba0202daf5d79dc02820a0238f2281ae808076fd7527300afc3356ff574cef501f6c71f44fdaef60a9ffd6eff4feedf9d2f9b63f9eedf9b6fdd3faf1fcbea0894f9d405140408bf85a134802029a84d409140504b488af35812420a049489d405140408bf85a134802029a84d40914053e79074de38def3869f107f5f63b56ea7ffafc1f108d7f727afed3fb8f87fb81e2e9fb4b3ee3117c41471e45025d0101edfaeb4e601410d091479140574040bbfeba1318050474e45124d01510d0aebfee044601011d79140974057ee21db47b827df7f40e96deb1527d3fe1bc439a7f5efde74f9afff4fe69be57d77d415f7dfd0e7f750101bdfa0d99efd50202faeaeb77f8ab0b08e8d56fc87caf1610d0575fbfc35f5d4040af7e43e67bb58080befafa1dfeea02de41f33be0f60ed33b627a87dcf64ffb6fe7dbaedf9eefd1eb7d411f7dbd0e77770101bdfb0d9affd10202fae8eb75b8bb0b08e8dd6fd0fc8f1610d0475fafc3dd5d4040ef7e83e67fb480803efa7a1deeee023ff10e9aded9ae6e94def1d2fce9fca99efa6fd7a7f9533dcd97d65fbd9e7cabf3fb8256f93527300b08e8eca34aa02a20a0557ecd09cc02023afba812a80a0868955f7302b38080ce3eaa04aa02025ae5d79cc02cf0c93be8d3dfc166a1fcbf174d3e977e674b87ff5b4ff3a7f3a716697deabfdd3fadafd67d41abfc9a1398050474f65125501510d02abfe6046601019d7d5409540504b4caaf3981594040671f5502550101adf26b4e6016d8be31cdbbdfa39adee1d229b686dbfe69be544ff3a7f9b6ebb7f3a5f5b7aefb82defafa0cff7401017dfa0d3bdfad0504f4d6d767f8a70b08e8d36fd8f96e2d20a0b7be3ec33f5d40409f7ec3ce776b0101bdf5f519fee902e90debebfce91d2c197dd223ed31d5d37cdbfe69ff69b6afdad5fb5ffd7cc9eff4fc69ff345ffa7d8c755fd091479140574040bbfeba1318050474e45124d01510d0aebfee044601011d791409740504b4ebaf3b81514040471e45025d814ffe7f714f4f98de9952ffed3bd4b67f9a2fd553ff74beb43ef53fbd7feafff4faea7e7c419ffef370be5b0b08e8adafcff04f1710d0a7dfb0f3dd5a40406f7d7d867fba80803efd869defd602027aebeb33fcd30504f4e937ec7cb716486f603f71b8f40eb49d61bb7f5abf35d89e6fdb3fad4fe73f3dffe9fe57df7f9ccf1734fd7cd509140504b488af35812420a049489d405140408bf85a134802029a84d409140504b488af35812420a049489d4051e0eb8d6b7c87f95b3ffd0e76faf8e97ca97f3a7fda3fad4ffdb7f5f67ca9fff67cedf547efd717b47dbdfa1318040474c05122d01610d0f60de84f601010d0014789405b4040db37a03f81414040071c25026d01016ddf80fe040681af379cb7bf539d3effd177b2e16e3f2da5f39f9e3ff5fff41cdffdbb74be34dfd1f5bea0dfbd56eb08fc828080fe02b21604be2b20a0df95b38ec02f0808e82f206b41e0bb0202fa5d39eb08fc828080fe02b21604be2b20a0df95b38ec02f08fcc4bf1f74fb0eb43de6b6ff76fd76feedfaed3bdde9fe69ffe49fd6b7cf9fe64be71be7f7054dbcea048a02025ac4d79a401210d024a44ea02820a0457cad092401014d42ea048a02025ac4d79a401210d024a44ea02890de688aa37ddc7a7c47fabbcbddcfd83e5feafff1457df30fb7f797e64ffba7f5e958abfd7d4113af3a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280aa4379a9f186dfb8e9466d89e613b5fea9ff6dfae3fed93f64fe74beb4fd76fedeb0b7afae7617f020b01015de0594ae0b480809e16b63f818580802ef02c25705a40404f0bdb9fc042404017789612382d20a0a785ed4f6021f0f546b47dc74aef4c8bf13e5a9ae6dfceb7ddfff4fa8f908a7f94fcdb3e69be2d5d3adfb8bf2fe8c8a348a02b20a05d7fdd098c02023af22812e80a0868d75f7702a380808e3c8a04ba0202daf5d79dc02820a0238f2281aec0d71b507aa749ef44edf549703bdf76ffb47e5b4ff793f6dffa6cd76fe74bebdb3e69bed1cf1734f1a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2c0f68de86bf4f11da778b6ffb74e67dcce7f7affff9fe3bbffbd9d2fad4f7325dfedfea97faaa7f9d2fa544fe71bfbfb82265e75024501012de26b4d20090868125227501410d022bed604928080262175024501012de26b4d200908681252275014f87aa319df617e60b6f40e945aa4f9d2fea7d7a7f9b7f5edf9eedefff4fc69ffd3bf9ff17e7d41d3f5a813280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2c0f806f3e15ca7df893e1ce3d89f25a3f6f9d37c09663bff76ffb4feeaf5e4bff2f505bdfaf59befd50202faeaeb77f8ab0b08e8d56fc87caf1610d0575fbfc35f5d4040af7e43e67bb58080befafa1dfeea02027af51b32dfab05d21bce1b70d23b5532681b6ee74fe74bf5edf9d3fcdbfdd3fca97f5a9fe65bedef0b9af8d509140504b488af35812420a049489d405140408bf85a134802029a84d409140504b488af35812420a049489d4051e09fbfbd57ef34c5d93f6d9ddea9523df5b9bbdff6fcc9e7743df99f3edfb6ffb8de17f4f4cfc7fe04160202bac0b394c06901013d2d6c7f020b01015de0594ae0b480809e16b63f818580802ef02c25705a40404f0bdb9fc042e0eb1d34fd73fa1d29f54ff5f11d292dfea0bedd7feb97faa7fdd3fa4490d69fee9fe64bfdd3fa6d3df55ff9f9826eafc77a02070504f420aead096c0504742b683d818302027a10d7d604b60202ba15b49ec04101013d886b6b025b0101dd0a5a4fe0a0c027efa0a97d7ae749eb533dbd33a5f5ed7af2d99eeff4fe57f73b3ddff67e56f3f982aef82c2670564040cffada9dc04a4040577c1613382b20a0677ded4e602520a02b3e8b099c1510d0b3be7627b01210d0159fc504ce0afcc43be8d909afbf7bf59decfa3c71c2e4b77de74debe380e10f8eeeef0b1af49509340504b4a9af37812020a0014899405340409bfa7a130802021a809409340504b4a9af37812020a00148994053c03b68fef7a3a677baedfd1d7d47fb3b5cda7f7bbeedfe69fdd6f7eaebc7f3fb825efdfaccf76a01017df5f53bfcd50504f4ea3764be570b08e8abafdfe1af2e20a057bf21f3bd5a40405f7dfd0e7f750101bdfa0d99efd5023ff10eba7d47bbfa058cef547f873f7dfeb4ffe9f9b6fdb7f79bfaa7fdb7eb4ffb8ef3fb828e3c8a04ba0202daf5d79dc02820a0238f2281ae808076fd7527300a08e8c8a348a02b20a05d7fdd098c02023af22812e80a7cbd11a5779eee84fbeea7dfc1f613ce3bb4e7dff69f4fb7fffda5f94effbe53ffd5f97d41139f3a81a2808016f1b5269004043409a913280a0868115f6b0249404093903a81a2808016f1b5269004043409a913280afc1753e7a9d79c5c60f40000000049454e44ae426082\",\n        expectedOutput: \"http://en.m.wikipedia.org\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"Parse QR Code\",\n                \"args\": [true]\n            },\n        ],\n    },\n    {\n        name: \"Parse QR Code : Angled code\",\n        input: \"89504e470d0a1a0a0000000d49484452000000d9000001010806000000f5a4008000000c45694343504943432050726f66696c65000048899557075853c9169e5b52496881084809bd89d2ab94105aa44a156c84249050624c082276655905d72e2260435744145d5d01592bf6b228f6fe5017959575b16043e54d0aaceb7eefbdef9d7c73ef7fcf9cf94fc9dcb93300e8d4f2a4d27c5417800249a12c31329435213d83457a0468f087003fe0c9e3cba5ec8484180065e8fe77797b035a42b9eaa2e4fa67ff7f153d8150ce07004980384b20e71740fc330078295f2a2b0480e80bf5d6330aa54a3c096203190c1062a912e7a871a91267a97195ca26399103f12e00c8341e4f960380762bd4b38af8399047fb16c4ae12815802800e19e220be882780380ae2510505d39418da0187acaf7872fec69935ccc9e3e50c63752e2a218789e5d27cdeccffb31cff5b0af215433eec60a389645189ca9c61dd6ee54d8b56621ac4bd92acb87888f5217e2f16a8ec2146a92245548ada1e35e5cb39b0668009b1ab8017160db129c41192fcb8188d3e2b5b1cc18518ce10b4585cc84dd68c5d2c94872769386b65d312e38770b68cc3d68c6de2c9547e95f6271579296c0dff2d91903bc4ffa644949ca68e19a3168953e320d6869829cf4b8a56db603625224edc908d4c91a88cdf06627fa1243254cd8f4dc99645246aec6505f2a17cb1c52231374e83ab0b45c9511a9e5d7c9e2a7e23885b851276ca108f503e2166281781302c5c9d3b76592849d1e48b75490b431335635f49f31334f63855981fa9d45b416c2a2f4ad28cc5830ae18454f3e371d2c28464759c78562e6f5c823a1ebc18c4000e08032ca0802d0b4c03b940dcd1dbd20b9fd43d11800764200708818b463334224dd52381d7245002fe804808e4c3e34255bd425004f59f87b5eaab0bc856f516a946e4812710178068900f9f15aa5192616fa9e037a811ffc33b1fc69a0f9bb2ef9f3a36d4c468348a215e96ce9025319c18468c2246101d71133c080fc063e035043677dc17f71b8af62f7bc2134227e111e13aa18b707baa78a1ec9b7c58201674410f119a9cb3bece19b783ac5e78281e08f92137cec44d800bee093db1f160e8db0b6a399ac895d97fcbfdb71cbeaabac68ee24a412923282114876f476a3b697b0db3286bfa7585d4b1660dd79533dcf3ad7fce579516c07bf4b796d8626c3f76063b8e9dc30e612d80851dc55ab18bd861251e9e45bfa966d190b744553c799047fc0f7f3c8d4f6525e5ae8dae3dae9fd47d85c262e5fa0838d3a43365e21c51218b0d577e218b2be18f1ec572777583abb6f23ba25ea65e3355df078479fe2fdda20b00049e1b1c1cfce52f5d743600fb4e0040fdcace7e9b7a2d3e5bcf57c88ad43a5c7921002ad0816f94313007d6c001e6e30ebc41000801e1601c8807c9201d4c815516c1f92c0333c06cb00094810ab002ac05d56013d80a7680dd601f680187c071701a5c0097c1757017ce9e6ef01cf481b7600041101242471888316281d822ce883be28b0421e1480c9288a42399480e224114c86c64115281ac42aa912d4803f2137210398e9c433a91dbc843a40779857c443194861aa066a81d3a06f545d968349a8c4e4673d0e968095a8a2e43abd03a7417da8c1e472fa0d7d12ef439da8f014c0b636296980be68b71b0782c03cbc664d85cac1cabc4eab026ac0dfecf57b12eac17fb80137106cec25de00c8ec253703e3e1d9f8b2fc5abf11d78337e12bf8a3fc4fbf02f043ac194e04cf0277009130839841984324225613be100e1147c9bba096f89442293684ff4816f633a3197388bb894b881b887788cd8497c4cec279148c624675220299ec4231592ca48eb49bb4847495748dda4f7642db205d99d1c41ce204bc80bc995e49de423e42be4a7e4018a2ec596e24f89a708283329cb29db286d944b946eca00558f6a4f0da4265373a90ba855d426ea29ea3dea6b2d2d2d2b2d3fadf15a62adf95a555a7bb5ce6a3dd4fa40d3a739d138b44934056d19ad9e768c769bf69a4ea7dbd143e819f442fa327a03fd04fd01fdbd36437bb436575ba03d4fbb46bb59fb8af60b1d8a8ead0e5b678a4e894ea5ce7e9d4b3abdba145d3b5d8e2e4f77ae6e8dee41dd9bbafd7a0c3d37bd78bd02bda57a3bf5cee93dd327e9dbe987eb0bf44bf5b7ea9fd07fccc018d60c0e83cf58c4d8c638c5e836201ad81b700d720d2a0c761b7418f419ea1b7a1aa61a161bd6181e36ec62624c3b269799cf5ccedcc7bcc1fc38c26c047b8470c492114d23ae8c786734d228c44868546eb4c7e8bad147639671b8719ef14ae316e3fb26b88993c9789319261b4d4e99f48e34181930923fb27ce4be91774c515327d344d359a65b4d2f9af69b999b459a49cdd69b9d30eb35679a8798e79aaf313f62de63c1b008b2105bacb1386af13bcb90c566e5b3aa5827597d96a69651960acb2d961d960356f65629560badf658ddb7a65afb5a675bafb16eb7eeb3b1b089b5996dd36873c79662eb6b2bb25d677bc6f69d9dbd5d9addf7762d76cfec8decb9f625f68df6f71ce80ec10ed31dea1cae39121d7d1df31c37385e76429dbc9c444e354e979c51676f67b1f306e7ce5184517ea324a3ea46dd74a1b9b05d8a5c1a5d1e8e668e8e19bd7074cbe817636cc6648c5939e6cc982fae5eaef9aedb5cefbae9bb8d735be8d6e6f6caddc99def5ee37ecd83ee11e131cfa3d5e3a5a7b3a7d073a3e72d2f8657acd7f75eed5e9fbd7dbc65de4dde3d3e363e993eb53e377d0d7c137c97fa9ef523f885facdf33be4f7c1dfdbbfd07f9fff9f012e0179013b039e8db51f2b1cbb6dece340ab405ee096c0ae20565066d0e6a0ae60cb605e705df0a310eb1041c8f690a76c47762e7b17fb45a86ba82cf440e83b8e3f670ee75818161619561ed611ae1f9e125e1dfe20c22a2227a231a22fd22b7256e4b128425474d4caa89b5c332e9fdbc0ed1be7336eceb893d1b4e8a4e8eae847314e31b298b65834765cecead87b71b67192b8967810cf8d5f1d7f3fc13e617ac22fe389e313c6d78c7f92e896383bf14c1223696ad2cea4b7c9a1c9cb93efa638a42852da53755227a536a4be4b0b4b5b95d63561cc8439132ea49ba48bd35b334819a919db33fa27864f5c3bb17b92d7a4b2493726db4f2e9e7c6e8ac994fc2987a7ea4ce54ddd9f49c84ccbdc99f98917cfabe3f56771b36ab3faf81cfe3afe734188608da04718285c257c9a1d98bd2afb594e60ceea9c1e51b0a852d42be688abc52f73a37237e5becb8bcfabcf1bcc4fcbdf53402ec82c3828d197e4494e4e339f563cad53ea2c2d93764df79fbe767a9f2c5ab65d8ec827cb5b0b0de086fda2c241f19de2615150514dd1fb19a933f617eb154b8a2fce749ab964e6d39288921f67e1b3f8b3da675bce5e30fbe11cf69c2d7391b95973dbe759cf2b9dd73d3f72fe8e05d405790b7e5de8ba70d5c2378bd216b5959a95ce2f7dfc5de4778d65da65b2b29bdf077cbf6931be58bcb86389c792f54bbe940bcacf57b85654567c5aca5f7afe07b71faa7e185c96bdac63b9f7f28d2b882b242b6eac0c5eb96395deaa92558f57c7ae6e5ec35a53bee6cddaa96bcf557a566e5a475da758d7551553d5bade66fd8af59faa45d5d76b426bf6d49ad62ea97db741b0e1cac6908d4d9bcc36556cfab859bcf9d696c82dcd757675955b895b8bb63ed996baedcc8fbe3f366c37d95eb1fd73bda4be6b47e28e930d3e0d0d3b4d772e6f441b158d3dbb26edbabc3b6c776b934bd3963dcc3d157bc15ec5dedf7fcafce9c6bee87dedfb7df737fd6cfb73ed01c681f266a47966735f8ba8a5ab35bdb5f3e0b883ed6d016d077e19fd4bfd21cb4335870d0f2f3f423d527a64f068c9d1fe63d263bdc7738e3f6e9fda7ef7c48413d74e8e3fd9712afad4d9d311a74f9c619f397a36f0eca173fee70e9ef73ddf72c1fb42f345af8b077ef5faf540877747f3259f4bad97fd2eb7758eed3c7225f8caf1ab61574f5fe35ebb703dee7ae78d941bb76e4ebad9754b70ebd9edfcdb2fef14dd19b83bff1ee15ef97dddfb950f4c1fd4fdcbf15f7bbabcbb0e3f0c7b78f151d2a3bb8ff98f9fff26ffed5377e913fa93caa7164f1b9eb93f3bd413d173f9f789bf773f973e1fe82dfb43ef8fda170e2f7efe33e4cf8b7d13faba5fca5e0ebe5afadaf875fd1bcf37edfd09fd0fde16bc1d7857fedef8fd8e0fbe1fce7c4cfbf87460c627d2a7aacf8e9fdbbe447fb93758303828e5c978aaad00061b9a0df70dafea01a0a703c0b80cf70f13d5e73c9520eab3a90a81ff84d56741957803d0046fcaed3ae718007b61b30b814712d8945bf5e410807a780c378dc8b33ddcd55c3478e221bc1f1c7c6d0600a90d80cfb2c1c1810d83839fe13e06bb0dc0b1e9eaf3a55288f06cb0394889ae1b09e6836fe4df597b805de7ee9c9f0000000970485973000016250000162501495224f00000020469545874584d4c3a636f6d2e61646f62652e786d7000000000003c783a786d706d65746120786d6c6e733a783d2261646f62653a6e733a6d6574612f2220783a786d70746b3d22584d5020436f726520352e342e30223e0a2020203c7264663a52444620786d6c6e733a7264663d22687474703a2f2f7777772e77332e6f72672f313939392f30322f32322d7264662d73796e7461782d6e7323223e0a2020202020203c7264663a4465736372697074696f6e207264663a61626f75743d22220a202020202020202020202020786d6c6e733a657869663d22687474703a2f2f6e732e61646f62652e636f6d2f657869662f312e302f220a202020202020202020202020786d6c6e733a746966663d22687474703a2f2f6e732e61646f62652e636f6d2f746966662f312e302f223e0a2020202020202020203c657869663a506978656c5944696d656e73696f6e3e3531343c2f657869663a506978656c5944696d656e73696f6e3e0a2020202020202020203c657869663a506978656c5844696d656e73696f6e3e3433343c2f657869663a506978656c5844696d656e73696f6e3e0a2020202020202020203c746966663a4f7269656e746174696f6e3e313c2f746966663a4f7269656e746174696f6e3e0a2020202020203c2f7264663a4465736372697074696f6e3e0a2020203c2f7264663a5244463e0a3c2f783a786d706d6574613e0adc0adfa8000040004944415478019cbdf9975dc775df7b7ac24080004180a448511249c99235d8966d65bdacf592b59265ffd779ebe5b767bf48721249912c5103e70100310f0da0877c3fdfefde75ea369a92e2eabee754edda73cd75eadcbbf5ebdffcea78d93a5e8e8f75d3dfb21c2fcbd696d2a4880f9060cef2dd70650d60c5c34544fe175fc375114ff3260aec9400ee966527937882734cde38cdcb3a8154a8a4c9b32d86152d602319c1a941642d5b5665816d5dc44b59e11bd88a019cfcd0c6872634bcf19acf69e9a6ed3ceecdb3ef336cc6fb6371680933ffd360338f39bfe39d9fda30fbae732443605597e7bcdb856f5a5cd44e1466d50c9753b2567d2d7b2a7bd30fdac8ed3201171b3bbd6af5c7636ddf5c365f166f4e737ecbedbce6b7a689510f97651bd3d09f44ecda56ba5d4ae32363f54f5ca1ab329ca76c3bd319182b647b3d98e1cb3569d04b9aef51c35849836654f88786dbf1f151f8926dc08aa75882e016ef4b039bf79c4e3cb80de73e841b38cbb75d6048467f409a1d1fdb632bbce77cf2fad370e3072d57f4970adb2e8df0eeece7703b63bab7be806659a4c96b1e8d27d0149240efc63dc9a34c9a682a3a32a8375350a22186b753d0456881e175c92e5cb4b09ead5ceb78c27f9dbddad4729ba0d3cfdfdbbe9339ed17e073bcf166d82a77aea7916d3cd5d7e3c383e5e0c9e365d7a3556a75f342827aa63802320cb27f6c19de60a4239f5c5cf57c0136b3129ba4e98bc43c424d66f02a2d9670c790964dc327173da24651b40a834303cc6140c36b430a621d36f31a7afa7dc6b50fa4103042a7d1d38e2e9833ebd2387defbc4eb7f63436bb56b7d6ba71bfecde7a9c963fe7b56ec18beeed5b6065ce6033db3c8015712382850ac58da5e12e287497f6cac71ef39d990beef2c55f8d37d313177eebfba7fc6091ae27c5e4cfb835ef3f0375e8b1892bcd514c8deae8480debe870397af66c79f6787f79f6e8e1f2ecdedd65d71e282f1859c66e0d8fc439669a4c7b05a77fb9e39168afc3c9b1a1d4e4603bd7685c4211bcb872ae14cc459ad47445614a5f22b325870f054ca8ab6f9b186d52f3063b7611db0ca71546ebd8b6741acaf0c1976b2569f87c274e9869ad651947e53bc92314ffb62bbc36f895f214f99f533f5b4ffcd17c28e594a22d4961dab991451934aeb56edbe05166cc1c00b5bf5bde067dd1ccb73f953fe3126ffe27e19dee7cea864de98c519f54328787cbe1c1b3e5e8e9538d58fbcbd3876a540f1f2c4f6edf591eddb8b91cdcbebdec3e7a442373ed1b37f3a2209a690b99a4b502a0b812142e701bab341ce05cdc577e0573ae4a95fcc80aa6c50816caa185215cc81f8c9ddd12227b20a281f23302029df8979ec16d0d5a560b5839756c2df086c01fb99d4e835af192d1e9c63aedde38eddbd60a5ccbe07e1ae1bf11863cf8b69c3fd5c05a3fc40d1ddb76ddbb14ad6529bad60dca79d51e7497bcdc93788c58750261aa8325b375b0b89054198bfb0cacbcbea51e44efe6d1797db78fcb270dc396233a78b487bf3e47079a023e7db21cee6ba452a37a72f7f6f2548d695f1feecbfd07cb9646b1ad274f9733c2df113335b254dca16319de70ee0eab8fecae51afdc44a2c46906acae8a9ba1335ef1b3b3cd231c576729cdff9837910f913e78ad528e983e318842439ab87d33eeb133f44d01acf15a7e446ce2813f170638276d1eb61569f0a1dce405deccabe51ad3ca102354212731ae4d1f8c786664e2a64d7123ab23a7c93e690bb88d77926e54023b1447805c9f386f52818c350cd50c5e956d1fac7566a2299e40120d17afd5075a735ee58567c38338fb6e902ad2f2811167ea77cc14506bab43462a8d4afbf7ee2dfb376f2ecfbef86239be7b6f39d474f0588d6d5b53c4b34792cb47817add76ecba3cdd8d29d3baa4624a8a915bc9e019948a55f93638a8c10fcab892d5386d2a3c5da0ca2c29c6273f0e2082a16b7a6d38e1b78e5026d5a598990eae70eb7b70da86be0345dec93474316f73946afcdcb96e86b6cbfc2acb766ea23915bf62233aae21f6c73f23af1434af3865836e93837861fa9f08ad57dfff04bab3db3e27ec5e49463782ed507c86b76db38385bada0859d1c3823c7d4e86933a468f889cf3da5f1b7a8ad90c5f7997d7748b9a891c69fa77a80673f0e489d754079afe3dbb777f797cebe6f2f8fa8de5e88ed6586a703bcf0e962d8d6a3b6a885e5d89f1f6b6360d6580b6e80453a444ecb602162e2095b97d63981c84e1c1afd8405819e16c70c020147fc7406f3879ddc21def4222e1b052a630d0298ee00e6df8b534e456af015eb3d13dac1b2f192d2e3cd7bc2eac1450d346eb86c12171e0c90bd75c078fa1c58ab3d2cd14a7c79b4fe70edae177f45b79cf781dff3fb90ffe220a5b7c1c4f22a7f323b37ca69bcb31682e01bbc4d95526662640c1d098b272d2e558e566c8691e0d7e49423b9b75d2741aea66275c3a1b3b6556d1615794922e1aa98ed4605853b151f1e4ee5d4dfdee2c4f6fdd5a9edcbcb11caa51ed686ab8abf5d7ae1ad5b6eedb6597b5c1980a8caa19c8c8296b955f1b1f8525b89d6b26e56860cd8518792e000c0bbe5d55307883afdb44078340cc6b6568a4b5c2a4008c5a1489a767870bb23a804d41afb2120b4471c9318e49bab23435a690e1cce1fc5597156f8e9d963f5c3210c3732415398d6eceffb2f84c67bf5ae7609f6c0827793cafd74a37f39de9d2b6ba802cd1badb22fc29a61bd691287468cd97bbffe02cbf0b4757fd811c02f884ae888b0db97398a575dd6cd8ea0af15ed98cb24462fb88fbb15a8077ff98fe69b38251eb68ffc9f2f4fedde5e1679f2d4f3eff7c3950a33abaff68d9d6d63b53c0ddc3238d505b79ac82ed8c56f0425e2d652c9acb5042564c83d5aebc62a2184766706d40807106706506946b0409360cb4e4f808064140baffa082038f07089b3d10f846883ee8652c2e2d4f1a0cc3820c6702a2c8ebca33ec755e70c90b9e4936e24d979cf5da85b442d658f8c5e11d5f73d7d81fcb036b96ddf26658739a61e035dfa669bcbe071edf358cfbcce7cbd233ed285ff96fcb0e0c0fe3c4fd66ef1e5ef92913c945f4c88f1ed01b0453d711488364bde00fc4e4ba2499db363929430a2f7680104121a53c80e57eacd1e79091eae9fe72f0f891362bee6bb4d2ba8ad18a4d0a8d52db9a121e6b6db568c38246c57c4fed4a5c75d9d1d6056a7888125ffe4bc7966be54a1f978991d035a1363ee022a5cd43ac95245dba0793247042df0bc54ae46279a0b42241a5c108d9817b417573cc595c700c72920f890d5512fa829a8be18e91573a83655e95a15bf36ab8cb75c800994f38a373e3c3e1641ad81cda46601db7935b58217f595ee3aef921681dfa5e6c9a9b64ad9e681c78ccf1e26458f3dfe4737aca9c27fdf1733f338daf442797ade5a9b49ceac6830e4e52def844170560433f700196db31a5a2c11b59e604a6f37d2dbbbb36b55ddcad0f02794e45c3d20e20cfaa9e3ea051a941ddd6f44f5340b6d58fb4c6dad19a6b4b5345b775d1b10b68855b19cb25e148e95c56c720c9744408dc09b95baf22055a0fa351bb9cd9569346e9a6171cac0e8e555e8c4d2258e49640c74217a5e0401eb0152f71e5984c1767b5c4d057260c148283ba6938c13179107cb509134e574473b0b0e20dc7081fd49b492b34f2be2c125f8457c7072eca28445f9958e9913ff2563c618f0a0a3e3a46afd5d2e6d3f7e6076ec3d67259750b07ae92c77fb3447cc7615669f350ad6c9f92e5204050565ec06716eddb3486cea47ca14c883b90a22065a69c19418c93033ef6b116626d45c37acaee9f1ad2935bda52d70ee0b33bb7976335b4ad478f972de5ef6aaab8a551894171e8241e8ee32ffd5903f8a246e5b9a32979f125f60140b5d09ba2d2403bec0e58792442c88655196a41e1576e44ba718482b6254cf802bb70615101b53b385605d5b0cd7bf14a4d2aa1d2c484d2cefc237ea0d888c2519ca475b08ee096134b8f766ee4b6ce9b5a745e712b7ea7e13c0f9bf9b71eab075af74046239031ad67db85ec8621a5f9ceb0193e6b020e9f91af68ebd27001064e1751f2a29be5150f51a38df0e123aebabb83133ce9c802af03f84e0a116a279c9d385176e19a7285e223530ba3e874077654d3363812bc850fae46af7d3d00bef18b5f2c4f3efc70d979fc78d9d1ba6b5bf02d3542ada4740f2ff44e508438707800240aaea21c6f033739954f4607e1cec9e661184e299e355d2c062299771741b66b8d4f450d776ebd7075a17486b351d51841f635e922b74da93082b46523b3c8cb50c831193f8062b48aaf921203670dadef738ccdcb5c4bef0df5ad50d3c02df14ddeab9493b1b921745ec348c3cd15686248befdd80485037ce86607449799df44926839c5388a938477a72dbf881a3edf11c89f6b44f97396670d1adebe313f744d39a536159fb2933274b04ee4b904ca21d1936bfbe1083a7d1075bce8791571362e048096ed723e5b5bbbde98d8daa2196d2fe71f3ef636fbd6b3a7cb4e2b0b4d8b870f4c140c4b34b0c2210ff9d6127f340edaf8bf0020965dd6cfca89093442040b969a2e628b922db19a790480d892433243906828178299590f4595d03fbc07eb4093766648206dc34d6d1d4acd123f942ea3a30d94044b6b6de02c7e82cef20d35b2e0d2bb758e05c9f0b5044e10a2a968abd455dfe4753af64a1f1bddf6ff319e628eae11c2d5a1e99d90b256d7bedce4157960098e5120f229346ea155c401dd1283b6f506b229133a959d89c9ed48712c2683bfd28887397fd03a8d22fc2baf3990e1b881d091023f4854f0d60b4ce25bda8dd8dadad13e441a971b18542241efeddddde5ec850bcbf98b9796fd9ddde5588d0c5af4e332362e4896ee2d0f1c8f58c8e9047404eb92e8c615c162cc5548beae51242720c32319c856665c950a60724e3943f03880c2222166856ec645e7782ba204b8293010c83530c4bab656653f10818a9953a592c8a0f634c30cd18382888c422d7ec983323a0b2cc476727172baf3c9ebf8e045c40242dff0c635cfc9094d6f9bbf84dfaa83b8c9cc62bfa19ff9e2107d363d610f580db261e0fa6a1d92e70a96ccd022c0f99b3698892e436770f01119451ffb4803c4d1755714aeded6870c705d89190d48e191e7b8113b9ef594f928d3eb2be46b6462eb7c57bb7bf623fc0483ab7731a51bb8c8b02f85b77dfedcb2f7d2e565fbcc99e560ff917c821712845d31f449bcf5b3cec0fc69fc4d3cd30c02f42b7e2bdbe821f2e6df02d5e4a9882411e2ff11075c035b60ba62642a48718718e54a01aa03a699165cd3cc2811e2fcce0c57634eac422b9c48328571cc9564cb2ef1d0761895d8b0e26079144e154c21cf4ee97850c10dedc466b55f799b72c210d8a02bfa81d70a4ef7c60534e3cdf1091dac918c2de52381e735ce402292427641a45456bbd6525a29bc1ca0fcaa23037bb0200eaa2e862b623d025d6d074721bea301cd1f352ea5d769217506795b1aadd460f4d9613ac85ebaf927df820c81b3e456edb0fc336797b32fbdb4ec9d3bbf1cead9978f5e28bfed6b3f5b77518f7a0ab1b9e11ee2b1ca40524ab6edc082be7219facd48c26b0c3d27834c41ad29420104e8b404d0d00c71bc009040e700ad707c697af026dac223177e56bc5b3069321ccc2431450758111f5511c4fa80c667040a4889268f27d6dc89512a4e238aaff24ea00f3630a0e0bb804877dc8da008c30d25c1d80c27e9e75cf328007cc3b300a7f06a9cbe0ffac26d3fba62ad265ae7c675b9768518860bb9edc48ce6070f0279be2963d060ee8a085f07e513f35591dc69508a731650b51f1650322a6df568a5b8d3c0f8e8cfbe1662fc0251e4b50473b73ec2d69471efc517fd7972f3bae41c926df950c567d6d0da398f7cd1c3d6b23adb54d11190d0da5263c497d2820ce73a923c684d105d359229e538177d246d804c52d9945ef149419a08cd0276cdc72dc1a7ff48ab6bb2e01b4d1758995db12951a2023029091b32851c9e1d2f60d02dcb7a1bb9882467759e33203a11da516b3e0e9c1d3ec7e35ce982ddfa741a3ffca930708538f384ce79f0301b2eab3ee43b08dc3cfade599dbf4166165c563dc379e5bdf2699b831f73ac0c428badf2caceca096f1282e7e6abd1d29894e61f3af8707a82cd0ae2e62b9eba3b381a59a0eb5f2132c175f9230774d8393fe42e0fed74ec5c7c61397ff5caf2f8c3dde5e0705d970d7f979ec53c1b1c1a1d90e750bc93884dc4bdd32824b22377a54163c891e178f38250c123d9fc70b39d11a5b0442c4f5a05653352b6dd8296c08c5f8a94f350b5661d500ed2d2d6b0be84ad38c612239bbfe9e0334c3449a647162c9aa2f6ad0d175af1b213ccb899c322f154b6769ce418cc3d952ff916e9cb69e9f82c389d3fee25c73a58aa042043bab67d0658ae2f61345fbf043c504ecd8f4fc059ed1f14ae5cd111bc3090d543a7b91c867d42ebb8ef2a7beffcc99801879bd989971ad68ed653dc91d53e592bc2aa4f3400a7612913f80aaaaa9872b5e3f01de9296febfcf9e5eccb57979d73e7fc5672d8ace568c6e8ab8f836e6d39323d7d7506d20420d3b8c8ef007da58ad8fa166cd867526d7cf86853d362045c4b69839b997986a385c3c01a9021f8d080781c5139ca575a89193a0c683ac446e0067da8c801b1f818315af411ada8221cf8344f25f04feb6b4794148b32cfc4b81a1716d00459f1a98084d3f0956a8d7581af10144519eedcd0642857e0356fce9a796dc485beda17b67fce159b4e0bb33d91c3142e98d0988efbb001187e9d3ef5ec0a113ee747836293427ce0393e06346f38cab710159e45206b8b0d0d9428455c1625cfe4e5c7ca06041be3efee2d3bdafcd8bd74510fa6b52e3bd6f67fe55b0fe2362af8660d9f82592eb64155ba377728d61089236ff004a375a7eef880f040ab2c41f50f8b080cdb912659fc61e5f69d16341c160af25241c10f89380a1774f3e362e5e014d5ac9533c3dd3c9c8ba410da018abb81156eb08da88b81e668273618fa1301c7cf017566d8896c1750f25b0626c499f618043071b674b47d6d9bee9535549133a2fbca0f7d9aeeb9f8503754abee915b620bdc38b9a377d4914c401580c5a6e095cad14109d2f8bc759aef50b8616db30bd81b1535bd9290494c8b1b7772573da49bfeb8126c8763d1098061e62998f50ac55a5e826b5db6adadfc33973465dcfd5c6f2df3e209feafbae80e2172b12bf24b4b009682fe099d8fcdf19fb5b02fa04a3eb03544aba461e9e764a870321832d1665a7612133789a59115d3bde902d2553cc286abb17defca068753833dd0f2e083eae2013a96c1cec98a3793ee1a9d160278f14463f8de0eeb0a034ad0108038083b54018919e07678e7ba908daf4c33090ff2d70a30f3acfc4637a395a6f9fef1fbac5fbce8adf4e788525da207764c748a762ae6625f20dc735ca97600cb76cccb43606db12b015ff3ae7bd30b587541fc142738cfc9d0b83c0daf0b68a3d369187c526f6243fba97858cf2e1f89e221f5f917bc95bfa3dd469e973948aee55b4640a5951356d1a60b5a22b8f1313824e30a7e7c5438e537fbc3584dc573b2d1326c5f9828df4ccc1e13c9932113ee2a79564384030721cae3dff214315f390cc74440e4c1e24440d9e11411c2c25745fcb4c4f9a517422cc3970d4e29a00d502a854056a148ca475d1f8c837c172c99e089001a82f31c130098f5113e305dacfb0975e2c9a0173bf3b5cb8ade2ca7cbf0c304db8c46a7d63f79d66203cdfa18b2e6356cbe139fd390a0c3b6a78092e5531658527cfa568eb1bf202adb1baf4bd0591b7e11836af891abb41c02bb9477ca20cfc8065bb48295823da97bd2d4ab1d6de19f7de9eab2abc676f8f8a150d8d10443d742dff0d79418fa0bd1fa800fad1522d175a25921374ccb0560172cd1750b9f7431a4d0a10b63c515ec56c1ad0ff984bac7912230a1e0e683a3aaa11855f112003e0e349d6136bfe8904b14051042080569624e950c72d10954e04efbca05992331225d898c31eb780277387cf5dee031f20a02cf8675bcef4d14f6b1bd6136165ba7d07e9bef64b7de9b70b8429fbb7d03b242cb6fba2fbbf32d4b8dcf7d3db2246e65bb7d2b119d36415f100f8b1858d02a14cac01078a5ac0ac16a53275c7ef0e60f1e260833a81b94ba4766f2c2a7e38173e561f4eee5cbd9cabf7d933656219ccc1e1ef99784408c64218a055539659850d00d7065f95eca965f845478f08a5d7aa09ec45a414857bd0f7d730cfd2a01c48d00a2826e422d05b8a752a5d1140e8aa8f61b8f565060d317f5cc239a0bcd25704280f05de9fa0ee19f085d51a874edd39324aea003b8a9a3f3ac0b8e8c7ceb30c5216d39838d22f821e6ae3ceda349117812e67b78a512261b2ef2a151c3911ee564a7028f533fd283ff98015f5506ad67b8b7de7db732272fa5233a38441d94367d5b194bda172005b2dad61c94a7ff869b9dd11d8b14a2412a3ef107d0391cebe8d5cec50b6a6497f4407b7739d4dbcc84e60d0fdb8d0df8a0d3c5c47827ed1322a0f8ab10eb869f56de2da7edac8d0f3bd306a0098c8a483014a0629c162c74ce281e2b2be83aa5a80870be2105360fcb54b660951baed5cb91e8027707905c5d2950b305c39f382115ab650706d1a63380c023a1f3926e7995b9719bf3da57dcfb333b3c66726d39a52f86e8bf79cd7c10161ef08c68fc74d29ec01a97dcd860547c7dcac75cc4d4d33f9e59d5b63ad48c601dda06eb6755cd35fa388dfaf89f446c0f0670854e94e9c12d6394dd7653273a8ecc8eb71ecd0adb1c8493d0bc62734f279dc7baec05d6655796adbd33cba2173691dfa17d3a81cadf2be7c666dd4980be61ed1be0cfe92b74db41a602d47576314c36d65c665eac85d90538288bc190dc9ab7138ac6f8a24e0fdb6aa60ca24ce705535a47492bfbe5464423708b4eb7141277602916a2a4319660a8018a0d59c90db8319b5fe7a502b48367e7cef139df7114585996326821dded3c5dd1cffe0331f65bd7f229f922e452b639aa38b4f9040296fe34c3891ec983371f1f57aa3555e347c4ac6072daa6f069ecd281a448b0c141b755c50926a05305dae4553428407ee386b5ed80377a60ff18a1c1b7307215a0132c7894114035323d273b77f5e565578dede0d17de1cd44a16bf5d1cb648027bc86c1d1fe98f24c136164275ff793bca01b8dcc0d0c24287c9d4500d19f401e49741f2a0333c9801440c0eae58605e029c00b242b6ec8742983b127f9a9746d8fa5281199666362d2d611ba21b062ce34656181e3285787d9b90d9bf5eb78df1be7cbeecdef64e722a356bba552ca0d6d80474764004f5e4b280bcd50962a73fe042bb0f0ea9129f77994b2edbac07fb6a775c69309c6549474e2e3216c61e416fc9491740738944f195036235434f88146a79617bd567dc20fb215a3b815c0b8360849b2796f2febb28b7a5e764bdf327508bc1ad3accad033325a5bb8473f5109bf759d75223efca7387f273504276bb2ce92c2d1395a2070301d79880f5e94064b786e50c91b57b179ae9259919233f25b6e336ee5a34d1b18bed16da210583021754741d2585d696d15851fda764cf4a73e6cc287fe7f66042de190c6115e4d1a59ce1d72c8eb029a2b6da9717a81228182f48d3b3c9346069f34a4c467f99ddf72a30d78cda7bd494e87aa0992131b2cdeba59777ccabfe45a1593ad3eb6cbada264282fdcd0788af3cc0add058387a26b2ee9410558992568e611f9b1a3e9a13be63c644f193ffa70d1c20ce65660b05a15b73f4d7f0216a221ba93eb5df8d601088ae54244011ff79a2c100383646b2d78647524596225e638c116eb62018d145603e48870abd223bc41435ef10dbf24ba22da69c940e22a0b964e073f784148c5480107b25616a7710e91f2783800383d74619e96db76a6e2cf1854c02a84a9f01a6306751cfcf513cc35dd927a07b05e0371ed086eec6e09eb1d1e1d828e9c86b49f9ef742ecc64d5418f28b28ce73d27e47efce9367c95eb90d41c257bcf4356f381ab4491139f11ff8e8dffcfa8ef6b35d9d26dfb32ebdfa72e6e59797edb367f5653a4fc876401efcf35675a9a3b569c34fe71f687cb0ca85d58c6f01c0265f8de9e28cda4e8b33ac91690d2f479a89b2c64865491187b34b25d35991a98121df8e556e639aad10c337c5e5a26a19e499db7409638fa2e491240c9d146fa7f4dd08139c74e7350fd2a716de29f0e677fadd0a9ec24bd85232fad6550e699927eff04627462aeee333ce3f251f3af208cdc3898d0b721a205c174478026d1ea6375ec9ab3c0ac1f21521db288507bd7da834a0d60538109767d40b4448c62918aa4737331cba18f994cb86aee21f9bd12b71b6f2cfbc7c65d9bd707139d0f77c1473eb28140967a38748e9e63868ab1f111b3b361507e61741d1d97f60366e7804c24806ad05025a8d13e7a4616270f2c07228d8502048ce0aacf0c4b3ca3d79289456402c8e574e17003082d38ee922d0fcf0da18a8a7c0cd06db31e80acf8470aac49f716bfc14d6f3045f067f1eb335e81cd2e18e9bba07f5e31b019aaf7597b3ba31418def868f9d0eeff671d3340f68fe7868ddcada2e1ceb25ca4ae37f978fd38a977fdd8309861db109422b8961a6efb5252826372e89e0465749b02ad08655782aeed0f99d86fd6995bff3cdac121178cc112bbdfab27be9f2b2e8d59745eb3274ce5bd2118b6ce49ab772fd542c8a3563e74307382646e18166b6ab6ef09a759d463224c1974b988c24107134613b342810b464170bb4d045193241541030ce9f9b4fe505c3d7ce7501376de557db1cd8ad93790f2811f1cdff6c8ee0566203f3ff2431e49d205ae16d0f1ee88a8854a5cbf1f8c585000f120ad946df1ca59ca10bbc0973a18dc2757ef28c54781d9fefab8e116bbe126ff840441615c4375d567b12eb74f06632e7541d010e5fc362a279d976f18fccf08a6f126f7ea30e947f1a2e4218db00f338911f58ec83664be72977348a9dbb7a7579fcd1fbda653c18acbe2c027b8a2b176952aab9ccf05711361af00eb66f7df2dd608d64e088c295daf766344c354a780901ee408acec9d244ee1b8c3198d010371037d04211fdec283bdb045038d3220a7bdcccaf64531b9c6e4f08abe53e471cc4c1e7df12992b7a3b378e859b05b831c4393a2be0c3a84a09a9f1a3aa462b4578fb771db5daa84dbfb49eb10b19e08567c70d90fcf83329f228c1f66bcaaf31a147e374885dd219692a4ffa599af18a674407c12030627761b852ceb2200986ae4404186cd06e4606772a4b98f1922701f8facc0a15aab18e7bc3c086af68a1d111ab3357b4957f4e5bf9fa06ab9667552cbbb5814e6123099f80875ec80be8f96b23574ecbdaf599c432bed8354aee08517e33a6d086c0c264bab07964aa33d6fb5892995f049ee4157b4a6012f879d8ed62c79052269504471432aa42408888c4ffcdd79589c52a89ac888bcc8ee74e7e2a45f0c0919614360f48a767540d6bd5a06fdda125de773ba0cc6a39a12ba01389736d6fe0df35284e12befa4b13b2769613bc50b70b07f5000412becd213ccb292410a160614ae56e88c9a32769b2361e221b28709c215a05f96ce55d08bab5af1a1299d11fd848b3957f49273fb4d3b875fb0ba3a3867deb542eed6be8aca62eed6b602be7a28d80890348ab5fa78cdac23757d88419774076a30835912908398a2b93bc8c4ec0c21c1afbc710e114d93a8005b05e854f620e853c1aa5f28c1f8592b00e2a6a9024c42caa606656648002e91cba805c9873c646bc15a3b227a3bff3afe9fa4e2ef14ed398fac12f0ab7bc993d303ea1c38cc467dcc0ccfd8411f6c8c40e1f08860e65add36d827da08b64109c67bd4c62d8ea3ee536fbc21f0889f8ea0bf910f2a9803d1b6405b75dce8b9d224211fe73311b60000db56f8a5ca0c08c7e223f3257ffc56fa5d39ea68c972f2d6774fae3f1679fe8547e6de58b470e5f8017dc2ebfe886978a47639cb0b375cb1dbbdb364c58e35e93a13ef4a331286e93e69ade828a33e23b9b26563eabdca22f1d5dd185627c848385000b31a7411798d94dd914bcf050d2344dab3b20c37401a5f38be3c934600ec4ce8566b6909bd7cac03ed1a59def573f465ac852063ef4c67d3c893830d2e47705402e614e271eb895dfc86ffec8c7c6b5d0a0481500873c5d409a8253a603887165570ac1fc8c5e85631e45dff248b6ed452dc81a2bf4e76ea595f5424f978b989ab2ef45e54e013dc92d258684b279d8ae74e7cd7e6c05c0db0828e27599de2fd356fe9646b52dfd880468b6cbbe20be52254fe909387ca04cc7b1a164411aa99be5d33470aef7c962df90558e4f05462338c12a2cc314f2849369438b6ce088d4060028562da671d6bb9c69fa6824f54364a714b10b4560196c7f94d1f0c0c07602e99361cd8357023cfc216986695cf0cac71946867e9dfea551c5286c9c793eaf472a1dce30ab886abf4eb48d6055408e2b4c040c395547ac1f19a982f11d8d2ab4215df5122fb3c3e0e8403ab4981ee0491f02358f665aa4020e1ab1715879595480513a2215c7545d11ad3b7f09409c279c2fd3a550477ea7b943cb32cee20ac011ab33dafcd83e7b6e39d28f4e58560c32223493a6e601640ead0b30fb214a5affc022b3698c2f1c58ab91a50a3b8566f478cab40e456190e203b739cd77915979e325636e136b2123032464108f22332bd71e2b68eb8d9f4247072545a66cb309abc22b26abac0dae23017d02df9ab45692d591143072d248b296eac6846ce92c9191b329bb39735ff54885711a420272f5072cf6cc7c569dc42536874ad7c8368bca63da8349f145f42611dea9d02b93d80676cb06175de660ed261e230ffdcb81c35f951643f36cdc6815bb2c6b64acb69a877d107dd0243606a7cb00d2c6457edbd62cb903833ef7f80df8f199bd654fcfcbce5ebea29f46ba23805ae1c035e1266f9c95fff19d1fe109b70a6db3929dc7bd9f9d19068e60bb59a60bd33e8e61b069a418d6d9c9372d4815ec16d35bb75c2aed460b73fe21b7712be12ab165749e9cd5960a29782936278ad0385228e228e4a2d7ad0bc8360cb65dc900249ee776704801617b6f52d80fcae1cea77935fc3419c98bcd511cdeb36249f631b0e6256805f418d111c1566ba90bd94ecb767049532e09d195f8e03df28092c0968a9bbed3c014b057b7b617d0c938bc8d43e650980421be053c44af0a067f4ac3c74104b39c0673475ee7711fb629afe18dbfa10e6b64b6f2af5d5b1e7dfcc1ba2e33f2cad33a940c7f6fbee2ad575c352c6931e15034d60182c92ee2bbf3683328c5cb4afa6277ad9e828919714f041e8827f57c589de1fc0d3d0519445d60412045168a9b771962c70a603b8440ee286cc3438f1ee0641de51497695b1df525439fb541ad304cb30c53ad17cbb746818147c8dd1a2bd515ac0b3072523106326409a85c7c1af4dcbd462b105bb7968d9e89c7573d4ac223fa36b7ca2f61d1563afa0ca1917541992814ec94031c9cd34e598592e5403e4e8b4c71d77f3815adb25b9f94ab70fde78c4dfef022b491ca8dc8f669f13e458f26735625b634653cab2923df627574a0af24b0b2dc6c1558014154d014497b29f986750108137bf2bdf97048688f9588acc9cc97fcf0f33d152206c108652ca029c11f842105e4201c46ad16062cf4e203bc7878640b45782b6e19be347b71a974a10aa9f46a3ed2a34723f4eee0829c1e0e668322d3bedea0683bbbf0a1ed38723b3fbce01dfeb34e8d93bc56169d565e56cb062b5f0e889aa964188daf80c11d0e91928865b9270bef99af3086be8e87d986dec009e6d33a4521a0a6771917cc7643207d23918482f29d06419ff6135903cf420a802c670ad7bc95808780b193dc7c870892cccf99f8c78a422d0cc5a774eb1a6a10e06949c62f1691d36cce6aca78e5cab277e1451db1d28ffef105abfa73cd2e7a44208a008f349ee8657d0cafba278486211f4ead81ebb8d361b69ef810066831c872362e36cc90995d746ae6ceae446e65a16505d275857bc7a1b35cddc1c25834c955f7a935c6b0f0a2036e437317177bca5c444fc8ce5f6f5498a770ba50d67be48666e5db65173cf84776f522437ee892e7ab0ba1b8610a818c226fccd88ab50d090ebe7118f88a541c3876b6ee417cfeda38f109f93028c6b37e02b7ffc1b14e864112a1e6a17852f05228c401035730240033bc68da9c95aca87473bd13dd33fdccd13376fff47746eb28caac790d3d56887d00bff6c36a67a4a11edced5bed326eebb59733fa1aeffd1bfa76611fb1221ffaba3b11b3062ff2c268c809f67a457ef0db3f125c7e5044dfbb584636c9ac30849d763eb85dc90458dd497c0da629c546866801851f1133f06da52c18b806ea9a88f1d74d0a2a7b3e460863f182e93afd8305f2f8644b3d3a98b53129801610e87038accc2f72a237bc82d7d7c02b6556e2e9dea3612900d3418b49f261a74763b21e81bbc08cab4bd1b4bcbeb7dcbe3f57562d5ebe691c4073bc79599ef4ea3cdfa15b119c17bd042d7f936db98dc71dc3c8b72f7463abaf9cc6dbd830c5e7a0f0b5004ff51b624ff4ab970f7480f7a67e5fec0bfd70df8b7a78fc97dff98eefad037768067f031008bc84291ef1f83cbb0dd842768e585d58ce5ed19451a7f28f1ff7ab2f2bad3533af0936f1b6b0e9d27291613d2da8e2135e7d83b098a288152a0168db9e2a82b5420400bb7618d611b708099b454dedd23850478a552bee0318678289d2460c36d7c092e7b514dff7c71ffaeabf75705af87d9f7dd5b00ca511dff9b9479e5d00d3d2961bb4c8400fe2e625b861d604747431d0cc4d03be796df28b74f88467464a1b92aca832e4052811a5f0e08d5e136c864333e7b5eee6655563d397f256065a4bc971b75a25cf74937c09eb81dea3117434b667fa21741ad5fe93fde5e18387cb2dfd22e6a79f7eba7cfac927cb8d1b379687faed669e617ef35bdf5ade78e38de5a20ef7dabf381ec9e233cb22af20c3be5629746db7f0342a2efa76e15ded32eee87eb8ff5899fa872744f92ff6e5cb82b5d808478d4d5f0b2d0156fa733b81a7959bdf8c2e4ead5c2320fc8f077182996f186dce2b599266110528d006a210e904570427c054a433946d9d45e6effa93c3e26001d0bbd909afe50f3b8a37375053d94230c71bade9bab01aeefb24a7e1034fba269b2bba9726d800c805131d5a46f3e01e3e6df0f3824ed2cce98ef7bd1b11e98eb7acd8dc2904270eee49fae771552ac5b33918075a1980d6747c04e08c54cf9eed2f8f7466f09e7e66f6d6ad2f345add7083ba7efdc6f2851ad9a3870f8d07ddae3efcfed803fda6f33d7dfbef57bef2159fefc481c33f1292b8c53c77699ddb160c747f87727a18bda35192efff58eedc62d1e53a91b289fe83ce72e49cba379cfb4919b0ee803b5d777547aed073accade2967a77e900829d7cec2ba1696fcca99a4206018650d3b333c4dc145bcb81df9e58208b1f2785024c871efa32c38582e04685df94a299ab49981b8114c20c8aa779c14a48ecf4e236ed9e6c565a585aa1d680e6e4874122d2718602590171d9b5d648a62c39785de5442c60d091d3187066edc4ff242066186b7dc4128b68dd7b8e077e8389c4ee281031c6c1fda55821d4a8e9d79faa7df667ea8c6734bbfdbfc9946aa8f3ff978b9ad0675f7ee3d8d600f96271acd2c4a74fc3cd219557e1a19537af8edeb813153c7b7df7e47ed622f7620541291897993aace99ed231e1c638f51756b7767d97d51df62a586b6ff99d67bbc2d2d14706350cacb40409cda419010665f5ae07c11ce2c1ffc66095a363ec467302117dd141c6d064a3429a692170cddc00f00608279988379174be779be2c400ab22ab5187894524f1625719485463704b40ca2f64cf128e614743b383ab5b3218ca34abb416f11baa04bfba0ef02187d38b064025c7d810ee8139d88175974c93264c062737007df7260d38943ab39e85af4d0ad304e4b5b3f11acfc8154056d27aa278cdef86695d734cdd739ad183c856d0ac1325269fab74fa37aa0467477b971f3a6a67f9f2e9f7df6e972470f7e596f3d3bc8cfd14247e8739d69a08186a7ae92f1f8f1fef28546bcc78f1e698677de34f61bd9fa8bb6abce46a80bfab70d8094b49d60fb37cf3405e507291eaaf11e6afa0a33f2c2b7eb009481616cfb0298f520af19371e990af1103cdbdf6e648848e504c50106468ac2c0189d1c946585587728c0ca8c3b1f88e264a731911e99ce33a12e28ea1ecc0b62338ef2f0852622ca20f007e5e0195de08f1ec24136f7426ed1ad4317c09c6eae2d109eb6490c6d0b0cc3ddd7d0b632ce747ecb6ad9e66b651c4b52489d3febe0b858c235f0266c392b3c398137af96d0e9e6dd703857b1460f7b29b9e0a23bb4a6434005600d67bd74a0dd3fd654f735a56374fae28b5bcbf5ebd797cfaf7fae91ebd6725f23d5c133dedb3af6d48f47258c56841449f440cea1f8c19bc6e6d75282a5c67bace9a2a68cf7ef2f2f69db1d7aaba44b553bd3811ef8a430308c71e0beca632bc4afbe70c48a1f0ad4884985593ddc3a423c43493f1f90a3858be5498aff80b5742888e7f7c948581f5df817ff88800062f253398ca734046d70188716064e83a8ffd5b1e252d30173245f995d8866094105c32b6ea8c84d22d878adc68a05097c8795c59aaeac00d6eb068d8c318b321e59094d2c003092fa700b4ed10579e31a9f016a1e1bd9f6133ae065fef00b772b62f9f10f54f04a102f470bcfc0ce23b1d2047fa235492a05856759904ee4ad0f30372a6dab3fd5f7c933aa3032b141e146f5398d4a6b2ac1f7f7f7352a68ea255bbcb6d2b42cd3bf340efff09ff4673ac90c6c5bbfe9bca3b796cfe8eb01cee9e130a63dd01a2c8d33fad2586fddbab9bcfebad665da0d24b41751d77a964f3adef7f655570913eb62333582edf16b9c3a01f2ecf6ade1577883df6e86d748980118e6306848816f5ce792f0bf52e02b08a19e9301d04738a963a45d04beeba26c0a45414430b62121312c5934c4e4e561afda399d9880fce59f4a009ff0b3824e934da442b48f1655f1c906dcc128244c96ca43f6061fe5b56c505300e810594e9b6798a7e2992838ca03b7e546ad35bdc1c71e8277f44c1efe8a2f91d8f8e812bd75477e14d73df438ce74a28566a6b3cea53f6ca2d3c4a2688c0782027178cc30f345895298e9df811a158de6716d56dcfce286a67e9f2d9f7fae8d8a2f6e7a5a089e06a1c8156fd6527b6a30ebf42f361f1e66a4627ac8daeaecb9b3cb45fde2ca15bd7672f59557966baf5c5b2ebc7041bc3f5bfee5c73ff5f63d0d92cd753644beb879cb53d133fae108ab8821e5a8b6039f9e0cb3afcab442915e5a8eec4a87b37a91735fafbe1cf50f529c64a2b48b05bf398f54c2cc1f27e8fff9508a9135d6641b584280103c9030a88d02b01157be0b4b95825eca65566ae1990d85940d47d42e1d0cb110c56679edd4d6c3884e080ba5743397ba5b47382313e4811389aebce4d0b88ba97503af15084700c5a39818920b34d0cf76256d46e214fb5612b401dfb70287efea05c5acb4b2c7bc3ca89153bc75233dcb06abd3cd0298fd819d15fc037cb16aca3b56a33a7445beafa9d9ddbb773c05bca14d87cf19a9d4d3b32bf85453c48c46e517e9c048655d9abfef5d4fd4e8f43b612f6817efc54b2f2e57aebcb45cd5b9c1abfab2d19735fdbb74e9a5e5c2455574359ec3a343ed2a6e2def6aad744b534f9fb2d033aefd274fbd514263bfa48d8a35c44a64bb1cd78c8d747cd2fa80b496d9d685179673afbeb23cf8fdd934b28db22986f88e676db8d022e3cb7669fb9c4adf4536eb33b3d48f00460173abd1c5c8ca20cbb272a9c2147915b42b8915d82c7865b766e221e3aa70a371984665239611932cc88b66186310f8a26c078fe42c1fa03ece8bf4c45b7a24c3ae430a8c147ca2c79a477aa53935de34f63682ada27d173e2024365f4fca2a6da1f6277e84223c61827c52cd2eba4f15a87c63bc62109ab552927eaa1d401efc7eac67543cabfafcb3cfbd71c1ae2023190dcb155e95688777b2f89e7cfd41db0f7a399a649f09eee99fa6758c4c34aa573452bdf6daabcbcb7a8feb457d27fd05460fe5ef6a2ad947da6ce5e196f22e2e975fd2dbcb1af17896065b4e7edcbd7377a1f1c38bbcd8843fd632c10fe633db3ae2b3a7a0015983815e7939a3cd8fbd8b979603d96b7b365111e150aca4f3ea3f32daa7611adcbeae7536e555d3c5249883331570cfa5a17e47bd113d527a2da67ef45ee9c1c23045dd8d284eb0c9b19c0a21e5a87b92e08ad1b670c73b86860d89d0e906af0eabd2404a5a5b2f4850bba2457e3b613867e21d414db7de9b65ee913f8e51594ecb808638fa2418db3274c97f154ce10acdd983089f8b419c33f889ab2b0d5c577f4606d7614f81663f01ea74fbacd37d473cf1bb5a5ffdf8c73f5e7ef2d39f6a0770fd0d2f7880b3b7a7aaa188f9608f689a07157e57f5e29ca67fe7f5f3448c34d7b499f0ea6baf2dafbef2eaf2927ee9925d41d65ce0c2a3f5813f818aed262a5917b5ad7e4d0de98cce17f208e0785b020f0f7c0a84d195064743ef109d529fc24b5e6373c565228615c01ba76b1aa83bdf62b5c3afbee8b37fe3f3e598dd4fc19b3274f00762e32bb7d24a111a23947048987d854edef80078a461fb99e6a76cc53245d084403b3b573da4e3a8763855a01dd64a444172ac922eabc00183ae94f0bd12867686e9275a4a7b0e544ac204266a8ace334234231a55e4ec7216ba477fa8f8accc5c89940ca4f3fa0eb7cdd0f81b6c263de26c74100f35263aa8112c67ad28489d254113f381125acf4e17d4156913969c359f9875f05d9c44c31a8a75121598291b9d2912c80b6e7941ac89ed68b3824d8a4b1a9568442f69f380e9dfd5abd7345a5d592e722ef0ac7eb2487874cad13fbc88d3811356ff53f23aa42bbf9cd5c87255231efc1fdc7f0096728ef410fbe172473b98d44b4641d37311bfb618dd7c6a03b842cb4d9cab3188544cb4ac6bd409b001c2dbd2c71a3dc7ab2d71baf1c5adeeb9d92f2e4bd1970ea9d5e209bc02f1d6037b77c9229f0fa3d8136dcf3e5643dbd24fcebcf8e24bd62c0fe588a2f049d1056d214131ae5db1ca2e5a1a40f18146d1912ea3506c84a61fb02a382140976cae96a69ba003375c2ae739f89051a3897581b66d1908805a9109a8689cd930f4e64303e9ca1addaca8e4d8e4c9bc3240f843cb21de22d1cd96ea5a76b579ad53e0c16bf943a346462b318cbe5bcb3955322a36d33ba6641d90e9998b460e1a0deb2a1a160deab5573452bdfaea7259df9971fe05fdd09e1a07235e8f56f0eed910fcfaac62f48b5f805b6f25ed0695173fd9c4687859230b8f045a4746581e13307dbd78b18f58e15d33c121edbed008dc7ee78ee9d8c3875037c5348430d26af4ddd3faec4875bea7c0e0b93e5a395f00ad61f267e7da9ec220de69ee6c0679ba88703ed0efb09053842d59769afcae110ccc3c6c9b09a95571702680e276ae10c0b7f1c2360cb482d920709ad33412a414cca858479a69661e936ed653bc7db73ad0ac69252aa02c4179956d1d928ca39d9fc6d27a1b5417c46034375f8b65e085441efa39af6572a7303a5d05a364f384c471771af0e2e1af9e53691a45d9c093e958a6f2e66611830e8a138a581a7cf5b7a78acd41dc175fbce8675b48c0674ccb18a9bef6e69bcbeb5ffdaa1ad52b5a6369a45263441ed3446fa2204801193d4a9146af7c94a72d7bb6e599023eae1d4bead48bdae480a73b6fe9c24362ce29b2dbf8fefb1fb8de3193627dc66383fb1add1831ed478428947451a736d8d6d22918cf5fe30f51ea7f5b9b2e7b7c559c1aefd3dbfc803bf8196d574a782b437ca16df9236e9a153bfce3830115ad1a19c47c94a9619e052c3d1343341f0a76cf051db3205e634aa48605d8718395d03f6ab67233a5d776e041e300577093b2214a9b6558393fb9c5a990e1b1ca58552206bbac7da08cb093f8c08d39e408d57a449999375c12f059290b5fe22e88ce8d6cf81417db3a689005befeac95e8c12b2ae332c53c92ffe9c97930cb46c09dbbb7b55679e86df06fe9202d2340ec11b155805b24a2c9a81024085635bb8317b5c3f7b246a8cf3fd7ab1f48d67496b3835f79ed2bcbfff5efffbd0fe932a504064f78f58786852dfed031573eb3a1671c02d601dc077ed675dbcfd578b6c6460b75eceffef66f97bfffd18f3205d4da0c1ee7355564b4e415977e5e86ae3c2fe3d1c19b6af46735b2dad698615b3015dd7dc79fa513a612940c0d118250ed637d8bd5ae1af69e763ab7773ed5260fef97b1aecb7b6641e69a7219fc4706b683be364c649f1676ad143942a017dbdda5b76280d3711837328d66528cd03c9c9cf9755c776637e4c3d7c62b4ec108e27f2f4495ea7493929d9088e9cb69c0db00f32a4582696d8ab6e37db794ba148a99adf9818a934795a144154cd92224e4da57a0da4280450dac46258394b6f5744e36acf92a175f54d266805bb470036677c15e91fbf7ef2dbffef56f965ffef257cb4d1d597aacca8b8cbff8e6b7966bda16672dc46866a6cd7ef0af081a434428df81cfe60423ca9e2af613ed3682e446a272c756462eee3c44663a957284451a2969d6f27eb6a6e3539cac677a775ddbffec58de54e3407f3a058e5ec1ff059e8b7df5ba9fc3d168e20b8dacd2e1b2be838369e1a347fb52148575c4ead163375276438d8f2d820f739cea744131bba2d88dbe03a038d81e502ebe30bec54a3d8370082b6ec8222f79b982c1c7c1489896fad1f7ce26ede92224da33f41c9a85eb1ebf4eb83c5223d3a2585393956391a2b9801eee250d460eba550c7f1a2769e15aaba19af2c859d3cda374867823247f35844c9c871c3b0d3d4c119c418c082381a08f7a016de21a39f83002a7f91594de82c04d7252508614cc6032eb63c4f0953cf0c921f47c7fdd99adb58b379b349d5205654a45e7c65637a3077cf97ba6e916e701fff0de1f5c3169506c42b039c58746012cfe46277cc2f3afbc00c9d9413ee030eaa5e144f7b31a3dd8c0a0b171548a3e5cad66b92bbe34684ec1b3e1d065135bf472a52a24e70bd9eef779458d529ff018408d8bd196753d2f60a21bb4e8847c3e0468eea9f15dd233341fa9126c67676fb9a435d9cb5a2731e279a4c47ef9859dd0873a5942034c2157196374b9dd8c75b13c3c577ee09e8037aba3108cf1675bdf2a9c6fb13aabaff0d60fb83b88b7fe0834afb0e79a308b9bfdd2f9abbc86f4c368a5d9a1e9e9427e3b387362e6c53e78cb8e4c07e3c620834a87564e8c646c1b2c0c9372691e10c880a2338f718961b66e031f049876450a81443914556a0f10782b2f22125937178007a19dd2f45d3866aa86e67c2b8a206185a1b38923ff34671b4117e8b7996e09974ac7629ed1885d34d61b9cf7a3423f50857de38daf2e7ff3d77fad69d355f3c5001a026b98b33a6bf7e4e9335554f45017a887b49c6aa771d228a35d3d5f5225f6b32f9d27a4c2b2a6e159d3dfffdddf49c6eb6865fe3438361b68d8b76fdfb14f8ea42fd3537483378d8c72c53fe87f5b0fa8df7befbde5830f3ef434f316a7403452f9b10f46c25d3ce88069548e170ca18c7c6cb4b06bf83aafb2d0a1c8919c4fbca0cd144656ea1f1d0f4e3b3a06ffde725b7ebaa675d92e3bdd159006ff6ec891deb9ebbdcba77dd4fde796d697672ebfe475d933d99bce30b6a213c13453a1771d6fd9e0347fe3a7784cdef08c6495a16e45ced14826e13c34a417a5276923c244c892d072ad849dab58199c4c464653184e2ca15d41a6110c16a9425775836057c50b0bcc0b7ecb0e565dd1a9018a406bbda1727ce5eebe4ac0990ffc4f8676922557c940831c633f47528db21841cf4605959629d31df5f21c21e2843a8d8b91e8e1c3c719695479e0fdad6f7ed36b1324a01295fcb27e9584350b3dba666e826fab011cb8a151460e3652dbde8f1e2cbffad52f97ffa6676037d5c09ed130c59b75cebd6ff18ed66b5a77678b9d0acb74d3238a2ab9470f2931460f357c1aa01b8b84d0406ee8d5fd9ffce427cb1ffef09e474bbcc12e221dc9491f9e4ca3270fb839ef785be71ee970d8487190fe6799bebe9c9195d76108f8844e823393dff8c65bcb9e763bf1abcb4034dc7194cbca3ea02334949ccdb821557ee072c652eb527e5e69ff537dbbf0c1babe32ad2eae776217fed052c682af228a6b6e889ee50375236b6770c7a13bea5dd87dc2091c0ee5b434888da7582a19268a6616484ecbefcabc59174915c6146d5878852f0aa2344639ee8bcd4485618cf5322f240e4ea52f4e17d019a9b8289c7200b819dc9bcd602b802cec2abd2dbc90741bfa399b0b98991272eeef5f7efa2fcbbbeffed68b78d6164ce558e7980efff1273e8f752afcc1c3fb1a31e46f37041590cae1b276fb1889a89c7e56a5691f9b135fd5ee1feb94944b74a301f2dc0bdc1ef9182568e83df29ddfcdeb23db5a835fbc7051479d5ef6563cd34a746754baa387c07402acfb32cd43479dfbab0e18996cdf47767ce1ca15c76e3a15aec0f5e16b00589f71840a1d794480ed7c3866c509914b7a44c0460f34f889a355744a1c527e410d11dfe2b3b83b76c300fc6852f22417fd0c5746df510e79eeb0c4efbc6c7ca0519d575fa007afc3a059419ed9753ef7c6a7eee0a30ea1ad2dfc06726798df510bdfe58b4c1e6fa9075423d357681d7b9d16e55050b118e9ab55134c9a90b9da8d06d953808480238adeb7400193e9540a8e54f113411b52e8e6d3f16c3a84af0b2002ca61c21a82ccb1c4a462b7239a579053586333c36beb88989b00004000494441543972a029bbcbc6d0010bc057c9f3668670a9b89f69adf2d1c71fbb00f24c4995d5cff7412ce5e400a6514cdb68844c01d16d4785c6aedb0f7ff837cb3bdf7c677989758b1a05eb2b1a5c1ac0ea8e331af958673142f1ba085a334265974e15556ba9bca34587bab5bca0918a75108770d3c8a87c475e6f314d638a3874d15a9075d115c93fb3f7913b60f83b50548ae0079795ece2cffe05013b5dc86ceb732ef18ea7cc991a5331b54120a7bca0e75617f558811d4d464e68dce8358ab35b99ad7f8dc4fab364f1c5e796899c3f12d200d0af5481c1193debbb724d3f48a11f0aac75d9a87f42c406748b30a2c427bb040a7ef3459b4d7dbcbbd80a828c22bc8a408f454fc7fb434c495e7821c448b3bfcca984d97f5c2c1f1455b2c647a23ea52b6441c039c1b15c23849f9d001682045f59914ea001580f65f62388352fb01e7d52e151ae89a18dacf0073f6aaeaa96762587ad740a9d4a47607328151cbe10972dce8d6dc8a04233dda31213401df7905817461bdcf154d3bbc8802f085bcb8baad8dff9ce774cc7c8c6e6148172a301f1413e3b6667a4d74b3ae5cee8f7f1c79fb821219b11800d13d6d7aef85204fd988ed260c9a7910b6a5e3446a6b7ec3a824370a3d4c84345673dc70608e6a065bcd5d70256f9c60ac1248f38f63152315a7ef580d1389b2bd43d1e8ed391d01981878e6cbeddd3faf38ed69a5f79fd75cbb6bbb141fc08e0753c90f54a5ed7f132cf99c055d1976dca47363dbb7d538f4c32fd5e79a171f8db6f1383b6abf9a70a5097e25bf0f9acd34551984808bb6a5c4c17b777b4bba3211467528ebde80eb1257341875c898a29aeb40140aba2cc489633c983a24374801f0aea5e598e5b4361da8850243b85bb6ec39b8b10e08c0ee0ea222623878860761c594ee19cc49147e5a577a707bd779751e69677cf6838ef7cf39b3e00ebb588689b1e7ea4e2a3638f36bc78484562b3c22700d46353493975c19ae7b22a39af7f3035fbdad7f23c484ca41d5ed05d95efacf03b8c86e5bc4cf1c94326a31023dc57b58942e5646383531aafbdfa9af8bfe21d3c9564b3f246c2256dac70348a750f8175139b339cc267578f46ee20e79cd7ef307352841107bf10cafb89a330be56882fd0bffd1c38fcd9e8610a48a792461cdf337de4c43ef632c5ed919aa9259b31ec787bf7d5ac901c9e9647aa0a10d9a9835645f1e8e34d3c121d34626e6be43cab4d95871fbcefaf2448ae0d1116a994a74994749dea1cc9495987ff6c6bcb4f97686a2e614c8fe8b9b77a3e9cc042f8f0e840bd0b051da121c110414c564a29ed1144c9d8023ea1ef49812dfd1c669bfd4e124629070a5335821d1763c21c217c84a81bfcaccf244ba091226e3c60a2c109e93822cf428d040ddbe74f97f7b593f6b39ffd7cf9e8a38fddfb5271dfd45a88d312d7aee9ebc5d4e342671586414a33ed16f405ed9881c79aeaae5e4ca4423312d0a0807b0d225e34361a9e0fc28a191aa15f7cabd10ade8caae2ebf251470888f5323b71ac5bf85a352a22cfaa2ee8e0ee3ffec33f782a48a3dbd3f3cfb993b42bd05b32d8f8409fdf68dd480366238372674dc67b5d54a2aeec5470d64c740c6cd9774382dfc9804f085d09b775f0d75336c9a501a127dbfd9c3ae9c00c818e81692c2317765327d808019f1d4fafe3e27193d9f723164e5dc19b2f77fc2556c4449d0e0c36db3ae8cc3946be2aeee8c9e3ca13f208c22e63429e86d532da078d33c82ab236325123d8052b6cafcbd4e3d293b3583fd43ae1b87782662ed61cb51588a335a16e2c6702078895dcb8acb2c8e910d3ac89b03b955c5231a8980f21704e834f4cdc25c3d4d4522aa745860e350f399ea4e90885b7af0aca08c00b853c2c453ab59b0d089ee9bcffde07eae56f2e5b1ad9d979c5273c2362ad40c3882de8680dcd8bf7b4e875c1a362fee0fbdff7c8f5b276cf38fbc79a89cacfd48f8a87dfb1a1dfdb6ad35c90ca43737a61a6ad0787f96a351a166bb8cfb5b9f2a976c718896864e07cef7bdf5bbeab0f5347fcefa2817f2292d730ceca660ac8ec655fb611686cf7efe77b3bd848a13381963b6ba62b57af2cdbbfd3264b4d9fa1e9ba43dcbec01d563c693a5ff8620d3e6424bb7fffbe3b1a771ed289cd14a6afac2b790ce1131592c93a9546863fc93347f8e373176e351c9240879da4ec02df5b21672bcb5f7ba0c6c50ee319c93db87f57b4d965b4ef43adabcaa778222f5c4bf448ad33049767e1bb9141bc3214a14632e6efccffc559239976c4b42e3bd6d4dcf6b46033411cdada62476db7717431b8542a14b20c696561daf4213075745a89922e63c12fb64656c24e00acde3e8ee00e6bbe9be2c0bb6d4fd4837aea71ef8e9f4ff11e156fe0d29bfeeddffd70f9f65f7cc70763699854ba0b6a0c340876e6904f8fceaeebdd3bf7dc1b5fd0b4e948158d06456744c567bdc1f329befe8c03af6c46fce0fb3f58bea92926af87a84d990ffcd83da3a26225e630da64c4c11e1a547853f9791ec586018de953352ceea9788fd509eafdaf5a6fd141d2d818d96864c8c1c5ad7f2a8b1a5949a513b82c3c1e06efebd91a6541c3471edf1ac59a9c9d3f1a398c78ffeb154d3dd9d97cf8f0916d8167f81ac54562fe22011e1d5246c0b764172325fabff9e69bcb9e1e1dc19ee937cf0519f97ff3eb5fdbaf12b01c686465db9f23563c8660c46b793123d6e0c596977ab15ed141da04a0064d532175cc216735305e7dd9faf4637d573e6bc1c2541940074ff38580cce255edd138335e84e4ba8e6405450d845399fca29eee54203640244599f4ba4415b7c0c4134599d0c7d0e0d81ceb05f72908993e63030a23318e4382dbee4b4a57082cdfc5150339b3521d892ba72a1d8de191b6c5efabb2ddbd735b8dea0b8f483c40bdadf443e51de8d908f6beaa42bda383a23d3a219f8e862dee0b5a93d8c9d402057ad5077a53e1961a120dcba71e541979a8cc770bf2dd172cd499dae0c7afe87b2ade79e71daf71fb1915fce8d531663c63127b7843c7d93ff4e6a1adbfac460deac6f51bfe42d047da05e3f91753c52e04a678348c1d8db6c8a42341af37dfe471402a24f2284b683ccd2dc7b3c14527f3b25eb6e43958c2b175a01130b5bbc0f329ca4599d95cc90e268d0caface595d21a652a3b89e3e339804f87444781bd8c60a9333c80d756be46caf392f9543630a3809ca92b0d2dbbaf59a3ae725729fdea4bf33b29d7f0d2077a0615be8b71570fa6b7346a6ea9eec4a85082dfc1b656dafe107de7f75d208595c68d6c55547908d587826224e38e51ac4ffcbcacd602166a6ef406d528c49754070b851f6ccb288447c7c2830705615ead5a1a2b29a635b00c8d70f3af7b1a3b73762acfa1a650f4b84c43ee3fc8b1a04f3ffdcce7de580bd12b3fd439381a05238f6d64d12b7b62e3a1f3530911c93aa44f875f5d3ef9386fee5ebe9cd3e2aca77ef7fbdf2f1f7ef4a11b159b003cff8177427ce8f58de0f7d56b53a9fc5cab8cc027f8f4408f48fcb56a8c1c376fe888d227d69ba920d3a947d29b1198510d1ab6b719add9a0c23704aaf2ec63687a3a4667f1407ce0879e9cb2fffa37beeee92a65042f7617fdcd50c26569804ff06b367dee6a6326af3d81df5346a66d4ca33b90873a943731f3264d1dd0c779158786b2fa421d1f1b42639d251e3c24e76d6abeba808e023eaccb78664b07c679c6733a01b319528f2428f245d38168d7af382c75d6f99ab110fa575fb6f4fa8e1413a0e9cb1a31410f87baad3cc5b5f2251e6ea8314246b2262af780802373f28387d29a0aa907f4738b3eee681693b2c5d2ce14bf1826a0e2f08b8864b4225d08286503504cc6416b7a780ad9e010099e29140d3f23957a7c55604e375cd7698a4f99fe6934a192b1ee620a454344aff4f4eb16b8a795169113143c576204a0422195170ad914f8c10fbee787bf172fe6d57a76d6d875fbc98f7fb2bcf7defb3ef7a72665199e62a3f1e400beaf82ed707462516f5364236b1da663bffad77f5d7efbdbdf2dd7358a50a9d9c840ef04f44617769be910d84e817d3a97b68d46019cc0b48e976f7fade916dbe434543a1c7648d9c9fbe1dffe707945afb0b026ec704ebb86ec30d2d8f0834759a9002d535f4fe97876a7b9073d3f5346364bde97fd8c44ae7018d60527daf83c775d93877ec2e1c326126b2c3a00762cb10138758f2923cfd0f87254d6b7941df8741c4c852f5346435eea083e1a5e539ef36de0d44094864cfd87f389bb8eca2f7bb2674f7a1c6806a16d46eb4236ba379e8061c05d017d3bc4de35ddf96964e11405a5a9ff6430d3450a966726de61544fda3260e8389c4ad1164643299656ce62adcc6a6c140a0f414d4ae78131e4b1ac2290e37589a647bc93f4400f48e9dd7036d33f7ac29b6a54c0d81123989e4ae78f1eac9b9f194b671d12c5c3e21c5bb0765de873a6900a46ef0f025bc95ffffad7f4f9bacfd751d8d0f25c8b518dca895c2a888314867fec03c281da67aaac0f3cdd81f7964622e7883fdf07fff39fff428decb7d60d3e3ea604bf1aad15313e370ad50d4b727635c5e26ce33955102a2215968e10ef3f56437df777bf5b7efdeebb594b8903a301f6df93af38e6e50d04588b171b38a4a9dc34321a2a5f0340a7c3949151c75be7d6458f262493ce08f974c0d88b6edc61983fd4169c8e4ebbd30478d0d0d94de579d8d7bef635aff7c8133985e78e842fda7945ef979d3b7b7e7970c00e676ca7f3a0ecdfd00e6fb6fea18cc4e890745fe746d03074f20c093a646293ea395f49705e8f3a9ee87b4f8e9ef2203c7c8d81597c2ac4ce4e59edce99fc10501ad9c8165331e203477a4d5736a5982e327a6c06344413087070e5a23909c16304c9c4834305ef02a1f04347c1d0811f494e46aa3ca3c2a9bcf3c46974d63c9942e9d850f5f8ed4816fcf482e911c5d36aa5f0c777354a183691c548827ed053f91f697dc1c9084e9073decf410ad3a0ec5474732566178c57332e7b47f2aea661ca320d9d12b86c66c013dec43ded52e5a537663a041c3da9d4573482f0750034001a183acd015c78a2330b7e76dfae5dbbea9e9e171dcf698af3c1071f2cfff37ffecc8d82f6090b6f5428821c4f31e1a10fd3562a2b7ae1331cc5231bde80667b9e512fb3169db66093478d92064cc320a0096f4d5fd2b413182311f2e01d5f52be7464aa43b295d75858d3bdc417ec68b4785515998d0d1e98f797ecd86e717155101f6ca2d1f30804d9a91b9ccd7ce48e89b2a791c53712451d34b1e215c8ebd0bacd77f2e09b7fe9aa06cdb70b6fcb36de961eec6c579de037d1cab7f9afa2e6bcc4ddc81841d2b0428213d9e1f29451958942a291b1d8c679e9cdc14da1c5a149e3a6b61783465094c6c436ae31bb40bc255d6fcfaaf0a98c4c07983ad1b0d84c60670fa7da69e691c2408faed4f0c424178647128d08faa312b10e62c4618a7349df8af4a27a49cec1bda7b770995a622f958aef60e79b7199aeb19b86fae4e1401a8c25204419f0a51270aa9d531154164e89d3bbef6be4ffad46118e53612db4d8d55f0a833ef0a262795aa4290a0d996fce3de933216a62400169f4d4cc822ffbfc915e78e42031cf9268ec8c94e8ca94934a681a74542749e7426363f380914c6f7f49977cfd1b744c6ff119f2193dd01fd8537a72690f0e27e069684c0fa91304377675102f5ebcb8683fd28f0dd8c0819632e1a1355ffd4663fa8abe60e7357d38fc4ba3e4040a78f0a20c2957976dccb42d3db2d291f9d517d53b8ac0cfd7a40f0d0fdfc55f22c44ecac6e5459a924be8c676b281e133996efb3d3b3823ddb5cbbaa7b23c54797547a11a607ee6262183afe0ab940d94b229f26bba2832346c6add51a81b194ecbc98f54f4b922b4616117a561630350ce20a922279162ad4183c5598c1c14de175acc7afaa75d3f16b68c54f4b607ec2ab94714a118d2d83d952a01eda48c2eadf3ae4eac9ff7b63beb291efc32dfa717e5a5c04b9ae261cfc73a4bd8a70e52c03923c70f22303da600e30f694dc7a08fe511b53adb7ebec3eb23dffdcbbff41a81e353f4ae9febfd2ab6f1e92468605b9a76e598526ffb3322b8e8dc01a0270d8f351bbec5bcf69ca2d53b11d108c2b129553cd62bbb6a603420fc92674b97bc3b4865d4d86cd93082270d8c005f74614ac85a8a0d0497a1f259a331bab0bb4719d1f8f02d534bf0d910e946b6ab064583611dc71d1ace34f2703d5fb043fcaa7ccea97abe0b440fc3d9b0b116dd6971ef2508fe5d3f32cd1d177621f3e840768a9aed75ea0cdbff34e074b2619aba78a2a352167ce78616ec15e634e52a393bda65dd93ce4ff428e3d8f52fd87d9d4b2771d14970cbe08e975b1e746e6438b3ad6f2614869f97f10c8146e6e9229b1fd3b0292268610b6f46a93ce44408ec6320ec11c0f48ce9deefd5cb7ff0e187de8e65947aa0c6f6443de681b64ea3b01a93863da6555b5beca0a15f2aa595b730ea9e7a4e5e23d75487dd291ef252c06cc773e89534a3178d8e290b5317f8b203481e05c8c36076ad7818cdda8a918c5ef2f048dfff20bb6d47d92289b283447a301e5cbffdf63b524d955815c8531e6573aa831d3c8f08350233225051a9b054c414069b199aa6490ff4a1f2e3a7c8c8153cfef033be8707bb845907d6036235324e607070d80fb3858f6c1a3c0d906784349096c9ac80d902a31e233032910a3e23078d822960b9d91b3bfc94110fee79e6071c9a4bea54bea787ec9c27c406a6bd744ef637f546be453e3e6cfd1599422a277a51ae7c78d0ce8c893712789c808e7424ccb650923a4403e3910c4b8a1dc94921b5154852406814b5bc96d13ed84489fd1ace976d465a1ee0cb7fc7aa17f64118da47a13340797644dd03b3305d22cfd8bd5528642b1567445d0916131c45453c3eaacd0ff5244c51c284ca16e673038375e7136fae14f475f5eefffd7ffc0fbde9fbbe17f03db7a430bc5692ccde5c88e399a6a151763c9946d0e33257670d4185e5c128bd1a151b38fa51c9bcf1a1866847c18082a493d01f158a0ac1da820680f9ec465289e9290f9ebd2e1ebd2e8b7c37709451601ac6e8c07753a21c793402dc818e5d5979e3181fe10f2a3795063c4f95c487691a15945effb3cff48c4a7cbceb293eed43f4254e1e1dc0179a2ed1313082d03970c2831189338a6c24d0f89956bea687b6c8fae52f7fb5fc4a1fe2f0e631063c7803fa157d4f624627be5c87067fd9079a29a7b25a159e9325795ee62350f8525621ffdbdffeb6f8b22348f9a953943d71b5b4960d6cc8e014456d031d51ece2b14b0e09e44b76345557c3666ac8490f7615f9fa02b6eb0f6b36c3948e453beb491e4a63071b2f04fb870882b85847e26b687f366eb97445956edbe2c7179feee8ade943d98d1194ad69c577e3e0bb58cf7522fc5779c480ada7f0ed1983958392d5c8f4247e57671641c6280a17c350304111d3a20c1127867094c037802900a6159ee608d6cfa8e04da01258696d9f228042e3cc1d15889e9ea9df15352aa6287cfc32a3b69c6930342a1a2ac1f2905b3cb167d5971130a7cf2f6b9a46a3e421a7a44b3ecfca5897691b5d769e3bcfe973b810304071f1b53395760ec20c76ca783472be45977508d3a4bc9e724515ff0d77046d2f3cd1925198ce82ddc2438d3a5bc734eef21b31c9a031219b87eb3c48a737a7f37ba807d3bccc49e524fce33ffe83e5724a855187e92f0fc67ff9cb7f358f1da9a97aea35220d079ff3209886c2687cc90fa5fb281630cd6234b2f01098519e692ab3014a8c06bbadaf0d88fd16ef066f7f5317e4670e99ebea4c9fb3d468c8333f1eb43322335ad1a0bfd08f4bdcd6e918a6d94c63e189103a5cd696ed337cc19a994e82e93523b15185ebba6332b40b8b2e2fa7292be0ba37bf21082ec85307b9c3ba547c9fe94b8b64106684376c9398e8cd72a4c37b5386bea67b151cf4ba225095961e8ac2a7005897f12e103434a8a12c69e46f30409f4d088d86c2a7e7264e01d3efe1487077d4a0999ab23dec13089afa5d53af72f5da7a909663493d52b951212226781ae828ba57c15221cac2e8ab1cca1f1e8c649c57e43100012b9ee9c1305faac933b8f68df3f093cdc16e743689650feb2d37bcbfaa9f63fd8fffe1ff566f9d93f0d8cde60ba31ca3577b8b2ac8260b534cec7ea046468fcda2db7e2e4c6cddd3a8eb759f747ff7dddfb8f17ca667837ecb5815f7ed77ded1c992b73d3a517604de18a073a23361c3c3be107f3662580753a674529847e7e3738ceac0e8b418e1c9e8af00e0991bf6f02a94772ef125a3a3e8f8c3a67ef64843a3e11ee80d6e1a0d23388d9a06c58f02ded0ba95919493334ff5b03ef65a657796d48f7632baa178fbe4c9919e3b7aed9e7519b86b5955799b28fcfa8a9e8dc77d942108c890099ccff5112bf9ecc9a71f697a5aafbe14b277a937089b7beea9f37323d648d628c94caa15b1db285c3532d6473898d309f4f8eecd4020e8de51947d8e174a199e42a742f1eb8a3e8caa2c2a1053c0d735b77febadb7b213a591807381544a0a1c4782572e7445a49176e8690882a8a47d0284d197e76c7cd5186b8d4c7d792cc241d4173d3d826f1e78b2f9a12d6b153ea3dba17470615300928c8d3428af3b318850b6195e5e801feb133f87120aa3013e01d53a9b344d131237788faa2f68aa7a4fe98cb4f8842919d3605e53e18b6dd8c16454fbd9cf7eb6fcd33ffdb3379062fba2d1ec96b6b7ef08ef0dfb0c7dd11f3df8fe44a69a04ca974e849184cacfbad565260264d22879cb8063635d1798ca71048ae7a57bda85c3041a657790e0513ff09f37b5981168dacd6317a67e6c34d199b1ae635d851fa0895fd425aa83c53f5d7794e5068ca0c6e32c29b3a80b175ff0d77ae3674a247e5514faa95cb07f0e6dcb0c339168521ae92476c4ffac66160f350b91c1e60fdb2ab126d964a354eb89eec8d6cdd7d1c89c3e71f10965194225a7521e1ce45b9558bb64fd140233c5d2721ad016987884e240d640ac57684034b2368fc27de7edb797bff9e15fbbb77683920fed181bd815544ed5e884e588d415b1296015a05fe1d7e602af493085e224c8c1b343f7f0dffdee5f6a24ad673de2c1daa5d3d183caa7b3883a9dc1f3322a0276db633615efe1be80e2f4c0326e081267084df3fbddf84418f60dd33407abcf4649783153a0217c5deba93c4fbaa2d1885761f44b282a6c2a3cfec928b8e351e825ed803102f11c8b86846f9986b1dd4ec74243206c1d65edc47494b709d00fdf629b7fc8efd61dc9600a180be0e535a21ada2d358ab687635f8c98ac9fd8ea670a070f463bde3d332f8d54376fdef0ee2af7dbf23d8d93e36ef0c135f047169d365e347f6b5ae5ab3878f806bfb05460c641a7853f587b737747a0a92d332d7874e314b5e9ed5adcdb3e2f198dd7728d221cea917175e3f7d0ce4a1eebb32dcd6a60e8cd97b8489c56c6cdafd85b17f8305b6a45d63559634d77f060826398c61defe7dd25767534c0380f1bc6f706dab28981a3565fb148a67172b29dc39fea76ad14d3001e003f53e3c50064d2dbf9f4b0e8bab7b441ca031f1dd848a14766dd9107d69fe7171f99db6b5dc57a85efb960cac434f3adb7def274d56b06f1a1e252808c243936c66f631dfa199a473216ed79a45546c516b9bcd231cb3e278a89fac4bf440a0f9d4d434e2a13330276087b1ac554ea1b6fbdbdfce8dffd3b3fabe26bb07b14c7ee54d21436158be9345f0540e5768591601a0295fac953358445fe2d5f62271d1b0d0f991d7884c28ba8cf9eade7186900940f53f4f7f8b21ca687251fdeec0e33b3a133e3b40a87963fd51703f1b88293241c18e61d31cacfe68b969196ef2c697dda169ce5cd0cca589d1e0fe4e938783b9a5de2d7b491734d23f0558dde3c7aa153e42135e5859ecdc7f68b3b777ca57f07a7ab701a27399d9fbbfd4bf9b8c844ac29a37f285075e340ebdf63f60808f492ca6e398a043eae95b602c185e95893ad7852948410cd4cc6e3201c0b2d9583ad766b1489216d7920f93ff4c9443128347f17806f5ef28150f146063d22bd6f8efb3cf0034e8f56ac4b6020230f84c3c8c74e17dbcb34aa1bd7f53b5a7a9ec1348646c543611a1feb842e50d626ec62ddd7e8c64154f4f008a2ae26a7c9397570d11b1e8842cbc7aa24f7ef6bad207e4c3113a4ab22e8efc66e7b0218dd88f44d2153d0ac33695afab3f150664ac5b3c0dfe931c6effff07bebce6e2615e8473ffa7b8f608c2496018568998621bc79efee69ab5d23df353504462e2a1d2320a73fd82ddcf5668435b582545c7a7ed685c88e6f3807cb97ebdcf5684443b49eb290f38b8c7cac216958e0d3b1ddd0e8f4fffdd33f7914e2351e660994093ea70c590777fd62064043c0379407eb337810b0c3a399ea141d20fee73926bba33c0ee017625852d011f30641463fec49c09dc8e39e907ab4a612737e91b53f1b67be9b0d97c6953f7dc44abe7ca2c3da877a306fbb8c504257e1e5b795237e7433103fd0c6741125ece4be5b262ed29f9cc5f328d6614c0f78ed250ecb346536c6a2c498ca6549cab45a186010d3343dd35245628ecda29866cd733876cc383b78f88a8e1ef17c0c1239f3fefd877aaef6813f376ee881b57a501a243d312320c1052add1183be716a0a134b1931c0b7de7ef6c6d485699a762cf57c899e183c7cc08840e5f3ee95d64ae935d3702909a1e015cbc5996d17d36a0295eea9461336153ce55125071f3ad6b3740a3ff9e94f7d5e11627a7aa6b41c2246f6c58b782c05e5ca68be9c4ad1482bde4f8403cd5bdf78cb8f2f38a5410363c795a9251b2909f0c908c1ba8ec6eb464667a03f7cce48c85a8db55f074650463eca298d0cd71c7be302dd79ce882dd487ac37b341e6ed6dca00a708812364945f2a5d960aacff98ead2a8d8a964adc8cf26b13ea6a3a143a05ce04d400e65967bfc62ef4b06233a01bfcc21560bcb8533e73c1f4fbd6fb828511def70c44aeb607ecbecd0cb1a97a0115de428f45c50198f3541f870fd92e9a2d90c16f4caf426191534aaa891611830374c3453c8356456498ab42ae8443e9586c2e3dc1b5bccf484140ecf7a986ab0dbc40edfee5e5e65809e2d6b16fa3fff5fffcb8d124e2d8b8a9d9304e924ec70324b3037a624341876d4a8f8bcd345a0203916c4763b8f13faf4015bd67775629dd18cd3ea527904ec70900ceb20003e78a60ac5712a64707099ef566403e01b6fbdb57ce7db7fe1ca030195d223399b307e0ea78aaacac03a97868d0f5eb946e79046e54e4d0d0bdee4f38229df2bcf691536427ef083ef7b3dc76846796038a3c61af20c8b46c463107e50041ff1de145341bec99786c43aa7cb934ace0f40f0aa098d0afbecc7aacc1cefca668bca017ee42bcf75a170910fce194d7b792cc0e38cd77504ed158d563c3467f4e23b4e58a35b77f91f8f5a9678f81d32411475a031a4b3cbccc7cb05f98cfc9e3eae8d0aa22aa0a2b76e40abe1a5715176cd7f5419ebb0a58db23dad55bd95ff402fec82c8ffc077e91b170ef087a74c96e4926dd6b5bbd802232e48e605ae8da3d760872f67189f3ecb14a17b6ec4d939ba87179cc401a128a63fe0702645e3622b9ab9372357f23897a6b78a359ad19b337d21e058a62d2c80596c53706ef0e2d70582284d48a65e04c54d2e9cf4749c558437a3005be504e4d273f24531ec3e3e7aa4675462d6159e9d3ee28ce2fe3a0398997564b70c9eff7caa93dbbcfaf29bdfe8fb15e9283442b37e6086c42fa4f8a1a9e8b18143b879de946750da9b70c5e7951d4e32dcd128422562b466778e87b3eccedd549c864b6f49a36126b0fd1dbdc9cdc825477b5a29bbf0b74702fb9e22d7e6873a367e4a960ae90d27e9820c1a2e1fe269a41ae1d5f839dfc914930605df74b46b45c77febba5912e44bd654941ba3120d89a91f3bc6d7b4be62ed4b1e233b75a9cb1c97a22f9daca20ee4019f031d03b6d32170fa848e8cef18e124cf5ffdd55f2d6fe819648f6ca6a59c261eabbc09380b203e6571c46a5be57456ebc1fdeb3a30ad511f84e6d30a9a440207dc1524952f0d4fd34522274320384ee6cb60be909206c58980fd637e88225f5b1626ae752e48f8343f78200a07b63020c098aff37c8a1d4694a3e745168d8b91209520238dd05d389ca9a3e79b17eee4115a0f7a55e4255443202d1bd19946c68896354fe828242a0595965184ed7176e4f245a3f74c4743c4122969fd89b7535b1ad3d62f348dfd54cfad08d8c8ae1a274898aab27b482f47a7c1c602273c68ec542e64339af25afdcd9b377cecccaff188df6dedfeb961798a97d181911b9eac89eec8a62baac49c25540f64bda4a6d5f5a6021540597454d8cd5410ff6208b6b2c6e58815d376f2088cace844a3a4f3d9df67e19fd1c53305c569c4e0bf709e470cfa4e0ee1f20b31fc842da3624f37c1e1f9273a500e08a6bc88765d1110ef8e30c31b4807fcdb77dff534fbe38f3e591eaa736656f29a46737edd930f5ff494baa0e50202ec884d39945bf31f6568bd22895d41374ef993b7a5d900d956bd3f927f9adf4c87abb1690ecd1f18f1b1269b91a8f0588de1907b4d4623532f04a0cf31364d988299ca47ba15b1781b4b05809ceddb1c2ea52079b7ea48eb112a0467ec1ef1b5d56a6ce141c9e498128b7636217a8d001cfd3a4407a5cab1c0e3d060d053731cc7e712d533bbd7969ddcd992bea8cf75a646f5c70e1d1b2c547064c3d76ea902897db6ce0d87af2960b380a92053476fae88d723c944671ee8d278e924d87040263d302f80b2e0a762d2d373aef39ffff9ff972e37cd1739d974ea062e7b6438959d9196d1ce5335f9111b197558333102715a068ff3c7260f159f1185a9b0b0ac23f6ddd1e849637379d86f9c63d48ea41a3e1d0c6f95d3d1f2109d87da79eea68aad29208f1ab09b236e79d8ae236d1ea9dcaa50d58d5967a3148b368e482702feb62f476126829b5394c1c35efcf8d1071f79b387ce8a0ffa6787b4bf5a0e190a27ea01a00ea9172da7e4d3d10f9dd4d18a9eadfc3d751ebb9a711deef376836842d6b7b08c8a8ebb1ea2bb3aa1ce1c8dac058fca5a0251d60255193d1717315facc3c81047c0ac2437db3270861a24809d5a8545e5a5400efd3d1b1486463355401c8753d504ac05d322d64e8c7e37b76eba01324573434466d9634d66a164d1852a037e8c922cf41975b0057c8f2c7222bdfc9e7439d0c684262f9eee711c8946492502cfa16c4bc206f9d40a0d84293095f9c9137d1fa1fc85efb087d30d1cd162a4bcaef389ecd23133f8fef7bfbfbcfdf63734e561b19fe74e7ca7871f04ebfb46a8d4dec491306ca531c52fd2fb585fe8233ff1c098af02672794d7766eded0035f75526fbff3cef2ddef7ed78d848d033f8b5385619afab1bedeceb307351c7664d9a9e5db8269e8e88c631881ae6aaaf4f5af7d43bee7d75618a5b4ae7afd356fefb3817156a31c6737d19d697c87e89aeecaecd43ba5acf038f1dcc177c5961bbbfe59b870069e68c161ed86ee8cf8747e70b07f35c25dd763048e9b7527e1cc13f520e8a68a2e558ec8756016808f83e8fb961ad919f9e08ca6becfeede52e7d587acc12a3a62156d1bcd258c94396d7c8010fe9bc369e052404ea462b0a660986634a3d0e55dd3c529151db7e2a9742b001e159635168dec8c46471ea812e0e7cd0f35061a31533911a621a8c7071f5a7aec8418eb82428d889bcc0716209593a922bd211b1b340abc43e5c836b29e9749dea1a746d528d5c0ee6b5a82bc9e4ab9508a672921b14c9d725ef1554d972e68514f65a071727fa45ef0bffc97ff47df13f289f8e9ed654d11bff6f537f5ed55ef882ecf7da8f4b892118e11676beb4337a8aebaeee8e4ff735a53bd7849affe6bbac9a60c53befffa5fff5fffca0ad3e167f225be6523e71d35b49eea6e6b4715fff15017417e8dc3ee39d6e607eff0e93b0ee5a3acb333c2bfa1f5d47ffacfff59d66df96be018c578258732c0abdea955047740eb027039505fa05aebc6a8cc2e0e2e9da7a842d73ff3501a7cbb19fefaa333a623649df8e1873aeea4d11a2e3c5bbdade92e9d21a761e8943aa4f2470efc57199137743241eb6305a3bb64f2e53a34b4471fbdaf1fa4a0de51dac1812c32cc203a3b3fbc5afe18c982d6c8ada8180ad37f4c17f4fc854278aa699dbf235f8ed56acdcac3304aaf0a6cf2546aaa9c54680a9d0a41c523e04cd642ac63387e43a19a9b989fd728063ebd26953eb2a4e770deea4c78911f71d8009abe0d583d3c85c1a98e178f9902062fa390a63beab95817f2b85e75de5328366698c6d2c8d08f29e30888745a27d895cfd714c08b8266278f06c393227e6ce2673ffb850fb5b2e867f38143ae1c6be20405a3aa7a1833e33511b6b819bdfba4040630cd7bfbedb796b7de62dbfeaad740c8e28de89ffffc171e9d281b1f7f53b9786dab1d49e8d4aad430f2e88486ef29a0fc80f28c2af884d10cbfa29b1bbcf01919783114ff753da0a27a34b5df712199c91743fb68d349ff9bb23b7fbbac28f245ff52149338d08202524a15a302ceb6daf674cff39cf39c9fcfbd7f6d9f73baafda628bd84e3880400156c920a808084551c3fd7ebeb172ef5d257aefcdaafdaeb57265464646464446460e4b6ce293be69e73119661a61da286f02632704b947577105ee6f94215c4c4e5b644ec8aa90623d9c3c75b28a121e0d5b1b815dbfc2f63c3ce1612b604bbef278d3b75370f697856e51ee37de968f6e646ef29ab4953069f6f82ebc9988fee193a69bcaedc7647b0406cc241eac65ae574f23a611ae092332b978de2c8958c428e8fc59cf07941a0222745e420a4374694cb4537710275ee33115eb61cc18e128036a7818dfe86dd686c26e1f997ad4f66d5dfc49ba433a572894172cc1c6d83c5356735c8a46d72be77505c3bc0d21b66e511cdcdfaedb3c1b48535f2b302656130dccfeadd4cdaa985bd3181c1a257660a8abb9a2b50adf172831b5faacc9774ae5e69b033b71aa8081ba113226aca98b96965e48fd4f9dbae7e8f39fff5cc771ed4d9241cf645c74f6ecd9e24d09a9b77a58d06bfe8c80716cf8e9e12c02e638697d421738d856224e398d1f726ef78471138ac684027a8c68a23258d3221acaa08a88ed5d639a69e2dd2669f3358b07ed33cab0b4cf9f8e69124f98281b968d71a3f9d52f7de98b1dc7daca636b0ee16befbd4c5640421be05b9802129630b8ef30628bf7bc25deeb8766090c530c514cd7862e17de7a63ea30e000693670b5b55f23aa2f53ff3efe15177e0bdeca862e0098922616b8a8998cd5dc8027f4efdcb6422d38f1ab7245627bc624bc7ae64b64d1885cc29637d1ac7a94ae76cf5609b460a66056e3322e6799a68a32efc767ab8cc46e6198177c662e7b1e7c7362d7665c8310c62b4c1163a16319df2c7cc7299045b4496f3217b312844178c0c3a14c9547c2734de6e02401435af860022b346cd0c4e89752683d8fc1458fa37719f773a63732a6c3384c4293ee48eba7f78537b868872911e6031118ebf9b4030f1c65427b3b70c8c25c0e1d8e164e1d5300c663a633169dc033116eac6a0a012efb774a183396708a376ea6388c5d5b5fc286068987a7e092c72be0f4c5557f9a46fa6454ffc24fbd6cdc44fb39ccd51ad4df677ef0b52a48caee739ffb5ccced4fd5ba185c072fc30dcfc5452b6cf787c50e9ed33687f1733f7598fca945405c8ad2ba367c7a7d8e5238ffbb38c6526f419a150607915b8c6bcadec8b1efc95606d7c53473bfbd4926ae633d90462664b4b1c676a0665eb7e0a9c43c17d256304486f813213d6f6157e4c7d4aa232544b2a2a15ec0ac3be4913b76cd7c45924d6eb06d227b35ceaa55610398d0cae75669bbcaab7d0404033a7b1163d7348ac92a9dfa308d08bd3cd245268b937d4f75ed47a376a538ea045cebd2fbad82caf68f164b98fa629c6cdd8900502684d95a4b34630918eff919a3e981403547f5c10fc1253babf3c1704e18b00827e5623da6f7049e86009b0280bfe3c6a7ed2fb7177bfc078f1ffdec173f4b1904282678ca1e8711a534bdfbd068364212eabb4e9c28f3a281809e96b459f5d0c5d31144a6a576f265d04ed607bfa1c2b4014accff895d0c599a6cede41ead0aff62f04a6fc542315430d9fe9bf4ccd644f653b6711871f2981fbcfb5426f71ffc74153e7a99b42eaca978716ee5c09fe2274e5db63bf19347c4c442abe969ddc41df21157fe4df1001b975d8a221bb80bda005d75dc9e72d9843d8077f3640bd17de1937cb9f37115130113301dad5fd4f07a88b8d2062959607b18d6f3e07ef8a6e398994fb929036f1a3e5a329ac2aa0fc43e9ff31ecdcd2da2d0ca16c596bf52eeeaf209caaa72f14f2918de7defd2385af442ae7a326e6fb8d38ad211b2717ee47c87944753b31b301a812468f03a1e81176a21ba51a846f13f654d55f31084a72124dae6f9d20beb75fef4d6b882c166a6e93d30bf5e0e1cc24699381b83eb9ef70f23516acc68c262f1ac3a633c66212550a10b02d3d8f16abef36e3f92c131b296faa8a73644b7524c7a080659c722e8e9089271d9444719c44964f5ca934f3e79f4fcf3cf770298596937fa3ffef33fd55ced17674a0b24999b5e738b2e94f3d06426cc295475b6eb599dccf739cfdf84fbabafda0d9d6d46198a54d037185586a18dfabe9cd3b43e79223bc0a35884906dccd75c4738d40b1e724d58f1c3135b642ec35b731d4887b9129f7fc7622a5e9ff9b87ec32c4206686ad5b25a4a9fa7ecc2dfb5c3c47559d5bec87527eb410004b10899ee3326114dccf9a147d0bb2976aa75983719f3b8f161dfefa0069e6545f60661764e80a46e78f7dcf9084316fca6c13f90de4e5076d73cc6a1c009a19102a2445a25caef1e41990e133f84e61058733ce6737685019e60bd1fc6366fc51941e0197e1584985e18a39eba9da60bdc483b37bb864087f5cd80e331f98cade0836e84853231a6c4507d11f8ccd0d9d66f8ecaaaf9a9a7d519bc927a3e0a8750e8c98d29ad72a0e0089bb1ac35975cf84ec2121613b5eeda2cffa20327bef40ab67959dc0ed2ab1f81674aea155750af97c2d48f6735cb4b313fcbeca9f31fafb7eac20a9477d21e719680b565822f45b8141565a99765a110121b37e1de3333b372c3d6a259749e3c104f5ee6a385ce680c5f8ac22bcbcbcc8b69234bbf08a22106fcdd6ba3a524a6a283d5a24b40ec02fe398c2fdfa494c5c92d30a92f87bfbb5b3ac389775f4ffb45e14a3b905d27c70ef0ba8177fe5de15d3c2c50ba36c6402a20c43326abd63d72445cbaf934f8e57c89628730c0101001465169697dde3545a2088e06350736639249626de49fd27bd074b744a8162cc2c5a4337027640b5640ed82b28d158ea5e18d6f78d7ac5cb8ed368b513fdef10b0785255dad5f72caa35e1d97253d41a868063041676ef14c9a709e90ba250fe6a1d569636306cc4ef3df7bdfbd47f767bda2e563c527e3990fa70736a654978ea79c601506e5885907e8b4370ac5d493096845bd01bdf92147cf7188bc9eb9bbc77ff08330b853929de59f3322834e7ba98de683e39ee670f06b53e4a5965934733f38e5901e66a53162ca034fc0b068ce5bca4c9476d58150ce0a1ae3d5715e60f4cb31ad31be33fb396f5ec9dc20017d2de342b4b25e12ee977a049e52465951e075a814757f4621103096049c5920a61b5e7bcdf86826a83bde0c6cb83ef2c8235dce557e6a33ad9a2a67ea3a77f3575d4a9b83772c15b990ac77a99c03766e081f9dfbcd99e2122278b90b8b2e0b1600044cfc1542b6cb919b16de6448b0cfc0ddccc328969b9d0601b08522ca96379784113169a7913d6ba4794b683520e6211076d5229c8f9933d3349231444f8d0aace5a0e0fcd00378079486e17eff40bc744c2dcc6c6577852a0245788c5db8c599bbf55cc1ae541c642c9c25ec9da35af826cd720a703a94f9da28d6ed5da8fbfc7b8f7defe8d9a79fade70b83d5fc0d9cbb4fde1d6199359255263175f564d3a834619c1f11108cfd46be30f3d69f6ea989a4775b2b30befad5af76113006b71f0e5d9ef8e9cfdacb1b9fe9458fe7376da53ee8da3f6eda264d937a1bffc28d47574fce2c3bf3eb33a95f164c87b9092b989658dd75d75ded9dc0ba2ef4e2d1b379f4cc99a48f296d88a0ad98d126ec3ff4c10ff7594ff546056014ce6fb29e93f2e1151cdc369a87e4f5566707381e18fe304e0cff444059112b503ca67b3e74e338c9ec10d7b6a74f9f3efab77ffbbf6b66ea4de17ac71d77a6cde750a5e3d746c1a185e6ddc0a113e69b161f5c7642b115a8e879efc6dde439166beb7aca277ce4f3ceab424ddf247b619db881a2ec0ad99505a9781224a5ea6ff815859a473be74734721a1dd111a58ee35ce56da32b690b6ea748ef440e42d259be73f3cd364ec6048cd03231682d7bbf0cf235fe98a33341eae0945bd32b313bac94b0994f4fe5645aee6a4cc499c2b4b44a8099a52161d3f1639109f9174e1b2d1c2d66d5b9033897392a0d5ceaa088d05300e1ecd601ee180d13319bf484cc421a5c6fc0ed6ffc3861beb8e96b9a4c418c5c633475a52c7ef18b5f76fca4a7a0ed31bb0383fef11fffe1e8c4271d473d6bf22cc9523fcb9aecfa86836035bd3624cc33fe51d7d02bcf7af0fb1f78e0e8ee7c6082ab9fa0518c4e1bb612e5cdd0bc4c9dbae95dba8e31f5620236a40cfbba46f9e8ad2ed624a3b898d82fe6bc0ec2e7401c38d9dfa6a7610108685fbc36654068e0ed4729a9025cfdb8eba565f23105f5e6cce65b0956dad7589449af4d9e78e2898ce1ced6d4d48359aa472973de505e63da1785963577f35759ef1756fc7c1526699a2c08c29d191a21bb364afcbd2817028cfa83bf9b3d44710df2e7a142b61a4bd9bb4cde3767f5415f785e441b22f98672e6ca8ab47792ad12e679fa2e49e42ed6dbdf49cbd1402068abcb6f4d6a95a5fdde78c3c6c99c8e950a42056cbd1e17eea953a7620ae6bcc234841e88e3423abd1e65d0a17de018f8d784e9c07094002d0a4b355bef357019232812eab57ef2724c1abd1833c7140053197e7a4fe55a6d7ec38de6a83053ce4189d26146713fd3fecbfc910fa31210828508cc205bfa7ff8c71f1647e5f334c25f3e6b13f534eaa40e890af3e96d3fdc670dd9ba86c1d051a09404b42264cafccc673e7374fffdf755796072eef15b43476361f5521f3f3d82f18ef11365304d19218bc9ca93f8e978f54cba9b9fd263d8dce9fcccc7bef7bd0a1833d84f2ba3a11e92d004f45686de6aeed14bf00c579646cde2bb3ed11387294cb462997847f1b1424c23516e567758c676eeed7cf236de6d43046d669ca71e1d57aa400bcc25ffca9b9e13dcabf32ee4793877c5cdf33604b7ce6c4e178e2bdf37dc7a586ff24f9e40b90ad67af63ede45054a33485c5170c9b5104aea24c4a09c086cde7eeda5deaf6d2d2040098591fb85ee2aa32f9b60eeac15a385b8b7798a2c8f9109f39927d323b862e61228d92c447d284c23b411d9f1296b119056b664c840bc75226e840e3eea19663596e2b5eb5ccc669ed937e543e667cf9847627e4c5a70de49431a9cf3d8dd94f3f80435a51c300206a73d95e74a38f446172fe5409b94adfe98bba671bebb659586b470bd94759bf280a70e4ce23252944bcdc6300c27ca8439d6fb1361c4db33df85f1eeb8e3e311804fd41c7cf699d3473ffce18f536e7afff4ace8cf41609e8c02803525a00d091f85e5acc7b65770391787934d9d4c58f365c1bac5322f3ffd994f1f9dcc3a4bc2b38ed9e6ac60d6c35b5d944111b42d9a93d08f79e951fcfa85ba93026dc24b3ee8f18ffff48ffda4939e1bfe33373738484f80f5dad89f09cb7a71fcc1c0baa665a119c5615180366f2f1de216a7e0989b96db3a0f061b1e838f2485979ba6ccb3b45d209ddef5c628973f3dff6ca634324cda6049df7c037a2c9eadaef26edec511b055e622d2212244068cce9545308c6f30a91dc088cd74a39df679609b1cb9c06534c92a2157c8e73d41b1f5e34399fb21b8343962321df5209d1f51c9aa14cc1a21dfb4f65021702238135250feeba7aa41734564da9d67ae671486f92daa35587640a65ec5094f9885a9d7b14299651a577e9ad3940273aa66d346407bc4ac14b939b8bf9e2f6fae006ff33b9c200450dd11417a7bd7ea9adf1c3718017d4a0df7f9273dad0c37789933132423d80f3ffc48058bb6860fafa98501cafb5936b6befd769c51fea5173765c19ce5a4a21cb5adde8839faf18fdd7ef4d4b1a79b4e6be9cd8db128944b9fa4009974a17d68ae5c9e60f88913280e5b5cc07a353dc8e29b75a52ca5bf318a49ef6b425edd9995f3bde9e10dbdb47f1c5ac6773ab9b13e4295cc95a60976b0bb4a2702a8de7ad4a79f79a6f56e9949b8685fd3337c2a7ee81b847737457ff767f1ecc21ba59b34f0d05c6db11f57fe0d190f76b7b47160db6d701b7a4f7a80b7576e97e363819a0c0a53f02ab448246e103657166ddb0aac951f59701b4c1063c2e487de82d1129a6087590427da2f0d6ffe4b0f457bbd6b509924cc504c6d699379a2d1c4035d031497242cfc30816bf1d453457b122cfba66835939b2f6610cefbc73cc3b83c7bd254d305acfc1a90701626260d93202e067dd34ee9085acfd048fd55a5bd70342a4fe51fb26ade7812f2948f71099317732e8aeb01ea8449cf30ded1a98fbfe09532f9e38ab9ea88c87eb2db3f7e477b100da927b1678bb90657422a039ceb1d0d2d99aa83fb98dd7f8cb0eacdf448e8eacc8a71460c1d99f280a09f718d7119daa89f36849c9a05a55cc7e40be553e6b1b45d162b67acf2f4e5086bdead7662e232ff4e9d3cd9435d79748d9f29bb1ffde847dd71ad97833707966fa7b126c6e9620c1a8cf48a5466d0839b76673e5386c6e4140b3c26c070be5863899885128e1ddcbdde526dc0d653afd3de5744e521705b6e2eab880c69aef3418a2882f7e215460da5ce9f29aa3c282e61dd975b9a3029dfaf3009a711c01aa6c68cb422b02aaa279b92401a68b929bca221aa385f8575b08f32acfd4fd3196caff92f04b5fd62c642391a3cda375e6f400b778d75682ce336ab056831e3219af2e57c0cf0c5b88d392608abd5da7a49f5d1304c48bd68baf222e7e21da153f68468f36c385ce31de5280fa3a3151ae84d3838c8d7083fa1cc06d1e0de6f8045d39759f29e1964ad2185f2c7d7e7a05125c9bb680fbb20529adb98a82ec3f0d91b86cb43df317da5e3f418e70166e414f059a2577e3bdb8102a6b880e163851fb8f9d5f68eaf6463e9995f9fade2199c37a51ae8a613385fcc0fce3ac68d91e059224d9be5b18112e1f163e6d6d39c58e908a8a317fee9fff8e7d6b90a214ae19dec17b45b9a62ad621aaeeafc1c210343b9e86c61b6674a8b62c40b70e3f0783113d77a7a4316a1744bb984181c96877165b0e9fba609413c69ff7dad9217a1c4ab5f42793e57b64c63fc49927e90224ad582e173bf7d29fc64fa2a30573ee973dffc0730e3f8c0b40a990272b32bd47ddfcf4d5300826033b09d951f0851cfdd018c55f020b9e04f6520dc90972a4c80b8e569facb9763e347b3d2c4e660b885119c60ab2937f67bc65421a6f92b6388d7b2a68c6dce2ce2a1239c3c717a402510a62554537062717ee0112ae666f1dfea66acc37ce50da48dad0dbc3333fec6428e225884b564aa265b16322f26434bce947e0925f8497b5d26557353a1e44a77604c4de3a4f31ed9d001ad90461439779c9d71a9ba62bc922d2ffd2354a944e1a0f5b5397f5bafc274eb969df444e0a9df99ec24fee3fffc9f55468e379875a11ca533ef5978497c3934e99aca3028a636dedc0d0152af55ef801dbc5382b663325292a60556a018580078053db58579315e580e2b9f55b200014c66a79e47fbf152ea75adcab16671e2a66df5b0f02248f2e92dd7d0c13385c1b4b727cfe4bc769bce60b05a3ca9be2bc8b7c2badfa5f3aa8de226b9727f0d9331f3add73e9b5ded7f1a577e9379b701dae71f9a1d57e07ab90a5bd755a8c62c5eaef9e945a682bec29915f921a60a12bcc1796016fdfc5168ef95a4b05dc53c8c27ea43b1b169b7d7d250cac5f4c6631a82e0804fbb3af7ef95f452560c68008374ab1410beaeecad3e70b40953d9c6592a5081daca2608f0c0da7ad01b33ae21540e7a99b996aca80ff37025f3a2d1cc845518ba0c73607e83708cb47a3aef7db75a43eb5dcfbf7bbc6618f7f2e9d3cf45cb8ea901a7d5200beec046a6304c18af9eca281a4c8c61e599b4b90b4dd49992b369d15a3f0ae6dad439fd5bd3f9b37a812a933c13f09a755b928583f7764c8381e60eea31b532ef47436f0db8a1619ac0369c116e42d63252fe7bf9d4916f757366114230ca6bf104727219cb9e39336378752694d62b7eeb9bdf6c5beb7ddf4acfe5db04944ddb2a65e9c5b583aba1467bf5d4a96c95127ce34d2ff6bbd75ecdb4c5dde1c9f466812f2827d912f6b4e9d3446eef373ca5c9ffc933e9db364cc628b2baf2dfcee65cd09abf8cdee755dea2eb584bf36af777012e6116827dbb894a80ceca0f3300717ef4609d0cf2b706916a52e666d547c4e01d8d00fb2d516e8dc53a9e08b30e15f232e5129ce79f7f2e638c59d06b2586710a172da12334ed01028f368493b02ad722304ecc2c717e14c1cccdcddc0f0f951dbf16bad689d11e95776b1b9f85b18b2e3af4ff5e3054adcbb1e298b0fccb78c6a4fa4d5922f5e1aca2274cfff99f599d11f3c5d2248cab775ea7620d71f6f0e0bb6854ad9cfa77de298a86f0ebcd696a2e763da5f93b4ae7a59c0d68ccd98f30641de38c61e3440aced00694d221a4a503592de572c97b6509ea0cff9613c74c9d1ea97c99ab29e64f4116d1e4174d06890000400049444154cb7b4bd1f4f868dbb2a3d49443b88d878db36eca7ce8a5f48670b839cad4e65667b6cc317d5180e9e938a4eceeae7778c3493123547aee9870f911640fa31406a77a2207b12ebde288a1e0ac98d14e0df20e41e63138c273c5b917f0d4619cd85d36ca360ae2ba8fde76f4ee1fb2c4ca50493e65a77e122e3860c9375ce9e920ac4403780a6ed11b521acc9c05a2824f83fbf10eed9051ea86f4609002e0b0e292af9549b4f2d8cf5cf51adae05c250d702d06e55ac798abd1a5d75851c80d83e11006c32c02691c381a372c9735d734f7aeedf4263a99584e9562eeb5a1da24d39382339a7970f46a1a405d525efef19611d0871e7ea86618f87a42cbb98c7d9cd7613cc4755ebc8313bc90017ce69878f7150af8c754568f887abdac18effa9ce6eb1838f340b6dbff3673357f88c2b1428479b5ea0cdef4787be1d15ef04ea22158ed9ea90bd3127dea6dfcb863c173406a049a60e8bd4b83e219081b9ec51da49c59e29fdedcbc166123f8de6b6b02c432b1de108dc58161f50ada2b774d971c5a19605298d2fa411dee68f25e7aee199a8479335ed6bedeab9ada49caa1c2f1c4d476a666e9218197603665ee13441f8645c75ddce17bf9b51f577ec6a1ef9c79bec31769bd92d40fcaab08f7ef2b644992c2472257a1931182d365fbb287f31d30a681bec5b18850330400a115532262297730dee1b0dd20a489e4e579a3a58b70f22fa1611acc51cf2a314cb918a03d5a608967fa31ddc06293dfeefbc4617ae7bd8b33012b8dc601b38d08558c1d6dbaab2fc51978cb1ce9fb9898505ee3b9dc168e2fb860528da967b3d2446dad4c306663ae5e73ddbe27916f95b3eae0aabce965e7f42a3d2c8d4f81fdef7ffbd73a73f462c69bc64ee839ca667addc222a4e89e00deaa9fe78975979087635194565238d0e781071f084366c955a6192c9b335e0b84ad0953ebc06c7eb0ab18bc1e7e80b3dddceafad69bd9719d78383856c2d8ea70ea43d1d76597b13199f6f87dc65d90299edab0146e546130eb032e29268db635ae45efdb6fbfa3c2fb4acc64738fe61b25667a5ade652ae2e29d99c34d1eb8830d4e174668c83f0bada1aab6ccf5bab1514cf080de35a1d1f5b7d9c8996f98e56889ddc958c9d832e4df32cbfb17856c35cebaee1a0e9894a671312ad3825bd5e0bcdada1985ba4d21e95a98c7decc7361e5596504afccbbb55709018db39696ac16ca7b79d64f7ada59f93c7f266bbbbe2ebd93f58a8e09b30487d7cf9ac6f6521d876c5d5ff20beda57a87f81bae9e5300e15e67edf36e71dfeb49686dde310ca5e9d591d97363b4f261a06d3111334aaf4fbb0a0bf7454fcac53c97896a9ec113274e74317015421a930279f2c9a7ca30a61f083d278e73edb543db2768c01773b92ed3505a6195252d927b46095734b44ccdb7d330ef6038ca2d505bc5d226790fe1edbdbab3c89959eb94ac45bb001f2744c664afc7dc35ed30c72c84e9a2a0d77ac8b3d64f36df28b954b834a5986abe863ecc4b4bac289c599ff8f13a65584e94ce4f7efce32e40ae900501b899efb36cecdd7befad73463ba97b5bb9fc39b451c1d230ef0424f393187dfcf3ec7ed1cf37cc8ea7873c1e3f820376427409e6bdeb402a10f77f51c89a6efba38015140a553d5617dca637e84468048de76f2d1e2e6249296b91de8a2eac89582093285a3542c3646442b48ec938c54e8912d1aed6d1196873575bb6c43c73ee2066be254c6d2e88b6ab4916488b50aeab5744acc39f72f454bb92726f8bc90b31d19e3d7dba269af114f7f6830f3e909509ff145c9d7d514c4b11c8b6acd67794d0cd3767b1b209d8f49c04d678c264beb1abd516d613f6f0cf30797b02a66bf067a615bf5008e3a82ff3f3f8f1df0e15536eb1a508f2db959b02ec59935fefa7b7832158d234b82692d98e1e1c0b2c873a8df28a59de7ac9988473daf2a4a548cdaff1ea724c98507eedd57140fd3ecbb19887e8b082323b399cde8c3b9e407bdbb60e6d66b57ff82730919275643c6bb994f624fca60798df7a2e4ad83408611d6756f6ea4508592a04cebebacef3a4100b0f8c07f5a2f2ed837642933d9edec175da7352a22f9ca42d7d9b287f44a6fce36993eb23f8e7ce66ec9b3612b40ab20dac46357f85ecea02bc5e71ae13a661dbf829681c0ddbb82cdb5e7ab04e842c399bfc706e6d22f237152bf2c6045bba416d084f80e6c0538dcddd1b410ec374cc1053e6f610d301a0263679db30b19e401a6979bac0f71f03b586ad7631281135b05e86b673141d1351cf80015c67ac95464aaff5e4534f1d3df6d8f7cbb0b4b872baf223efc018f7f6c0f6b70d0f87e0be045a2fca9970ee282bd1b7a456b69f8c80fddd37be51065136bc862aa941680eaf157ae270cc136732c2bb09830f2b42fd3111a1a5e56ffbd8ad9d57fad9133f8f0975a6b0569b81b718491929a54ea42bd72bc22221c812bc73e738586ce599a3e79c943cf350b3599407142de0df316df0caff968b9684f2f77110581860c5cbf0d3ac5524409410c17140ce898c03d5c17899d2e185648a52e69413dc8bb7f68b3280a35e1b7dac61f501929e6f1902719a71f9133279e0971bff8b5fabe82169bd5f74117f18e6ddca93f4cd92ba86571d7c1ac0cd3f40a70527ff94e57eb733fa10b0fb55e8aad808fe3031502a47a318f8139ed59b4d25201e2641ed72c4dcae7745748b579640487c5e96b964a06c6c634cc5443010afa68f5985d1872935668d9e809b0acdbcd846b094ddf1618842db3b93c2a4b9e54add3c9806e081b239d4d1670f3cf8601b75a185799984cac36c4c17389a46e09ae636bfe14613c4b04f7d2358ef651b3da6aaf64e1abb7d7ff5ab5fc594b604671a93f2b0d0d8b80abceb8fcf968c2594d3a8c350684f7130ad7c46e883d1f2d6d0e9d5687667cbcf87016fada0f1a4314dbb223e67e613327095532c17bd3444fea70fac40eaa579025905abdde5636e7dffb1c78e9ef8d9133157e763f005b4fda154cabcd98331d769ed31c35340f067cebd16e6b70a85308853366be3de7bef495dfe47ef294d4a96f9cf62b962bc1c7c177da61e534e9d3cd9406cd9d95d99c37c31eb39293567b57c24633edb8d2868a174cd75d5af91dbb377c2ba4ae3d7672cdcb009cdf67c2cb4be21cbd2b8f22fc6db5d0881a37abbbcc907467bb2ab0bdea01e5c9a539689cb23cdce03d4ae3b8c80e9fc981b7a3964907af7b71549fc605338036dba58111f8e8dfbf9cf7f3e1f687820664f5652d064196ff52cfa30175d0f69023cd719e71458fea8a012eba94a4f70eebdac028926ebf2a4cc591964bffa6a26acb3ecc62436c6a245bff4a52f1f9dc8716ebc63851038848c070c431b9369e4f3f999fbc2944e18eeee67550a4ee72248cf677bfe2f7ef18b9a51c621160afb7846f12a83077ad27214d1b2af6729d6adb77eac42a00d686b013c13b7e793f7dd3420e78195f05fffbbe9f96cfdc7b0f0a58ccae0c99f6ca5aff57f7a36c205ef46862e8b3e9e95874ec6717051af4b974eec041228ede92cfeb367ce365e7b2b4b0f1a70109d3606788017f7d5a2ae141ca5e4e42c6b0de104b6b93a5fd4f9487e848a15012425346d3bed3c05e5c5164aa7a495ce226f9ed5b7de7ab3dee3af7dfdabdd6bc621c2b132f4a1080ff00c1cf0377417d82d6ef84ba43482746e6137bc3c91d744f9fa1ae70d71a89d8fe91c8474dcf5e2ab083ca79c58491efe5258899a64ca94bff67c58a204af99158259f9cd01d21eac7824e5aacc86f02a6710f00428d407b85ea36bf2c290b4946a556092bf9bf994aec2b9ec7ab10daf29621e78b5ecc47d26e6553fa31a17b81ecb1e3513dc4cae324b1adca4baf117b30243aec6c60c56667006d0cc8e131fcfd64dedd5f45c838c7a641a23f357bf79f1ecd18f7efca308c59bc3f841565da72e535bde32aba32cdcb5621c2e68b8d614621cb81264630a13ef04c0b4c397bffce5a34fc6cc647e0edcc00c1aa3e5f54ba8c9c3bafff4912f8e8af55b8c83414bc370853c98d44e6b02871e2c02385bb8cd9260e629af02b2d51b8c66a6504bfced39f07641bbe5a73e94db7df7ddd7ba8ee0c36994b5f6afd9de98a9c5e09bbf93ac6ffc31d6b4bcca98d03446a732422b0afaeb5ffb5a77451bf3c2150c65b55df304325c87fffa2466f7bcf8bd91bb3fda6ff7909be4cfdf4b1403c7daed77c6957f26e34a13d31b2f07b4325638707cacc82b0b5f09177003e67159ce1804835c9be54517622ad1da1a28b2d2300521f47ade43eb5de21178874f505879f613b6ccde11b81227193b9eb2bc2a422d0e5395a8aa109806e8cf3df7dcd1771f7db4939bcd178672d5cb12664c042ed43099dee282150e8125b21eb0d8f9274f9eac93e3a3d9a2624b096dcced6d1cb4eaa52e607ef0e6d92cca913027781907a441921013953193d66e6638be1e67c1ab611482674780f92fce047383d6e5a99f86d7835cc8f8d1722827010b9e539d5da8d2d910d226988e4b9e1b7b05b0fc84454bf71c3a1d97a55730870569c27a73bca67a4d6633674743b37b3fa6bafa6943419e868332c431a1adce51e7b56d67399ae0a11e698ee6dfc0eff01c80292330098b0fd1fffbb7be7df4cb5ffeb21646346015e3a953a72a80044cbbced061f2c95bcc5ad6289be189c15b19eb79d1675dc5af2075e99cab2a5e13ba7ce4a18773b46016383ffdd4d1b9b4e1a50c05f650a7fcb87bd410187ff6af8bd856c02a704bd82c2843eb4f8f30e7005a5e451b1681c21c011b44c11ef8de2f584de6695526efbcae60a17c0281736ab1b11a8160fe751c110d69c9cebdf7de9b314ad6d9f91738ecf27a1963f275301e3c2b5470deca191c023bcc6f42d4c4256f5c8f0c0852f29963fb877ff887e0e37cc3b8e9d3ab753a2078a149e93255eae0dcb8c258c0042c73cf207c7af6e0948953bd22f3d3aa75fbc0d4f13ffee3d1ee8ac684be06ba9bb4ce3be623e501673dad49698c7afcf8ac7d4c92d27a590f53274a6b56c7df6aefda0b2f144f30bcdfd51f61534f34536fcbd74c53d8552e8d1f939907f7b65b6feb98cac734082f67c5f1d0472f629549e79dc03b08c525f0d1ce989690ead1ac5b546f615d8796636e15a9bc1b7c87b87bfe4bdd0298d50096786667566a759cfdea6baf6627f9dd9deb031f0c650cfcc01a0278d1386956d8973131f25e1d7638c9efb51ef3c45df16ae7dc9998a8affff427476f9efdf5d18578365b66eaee3faf45526f080cd5fb7c58c80eb8740916f0caa53169599a9346391f4db85f91df144983cc1a3ecfcd8e395b4f759dfb40937a88211947ca4cb8d6d1106dfc4a4ca7975e7ca90d6b0e443cdcefbfff81f62c1fc9225d837eb0396330b31f3308dc96bf95978c53160879772ee625f7af321b8adfac643079adf7f36f116e7a248926b97e564fa63ce78bb4e7494f7b43265db9df4d2adf91dfade901997db7fc4d3e031506561ffba14c1380094742b5d6167a1e1a6d5b564207f5c6fccc4bf8328d293bbdcd2c969d3c9c203d5035d307e7b319d3ee61411d044fd32699348f629cb1e6efea48c198e3c8b2ede89aae01fc54ccd43a59520782c2e3c829f2bb8c6d09f9e0ba683405284bef63186172d8b2a92e0f6392a64c738794b23aabd38ce5177683dfaeed120d2f736668793a8c6db5cbd48a12cac737b22d8812ea62ea940d36ab070ceda31cb56f1e84bd2a1c96a53e9e57dc7a3ecc723974bf782c70a3346fcc14c2c7320ebef999a78ffe1867d79fd2e35e082e61304236443fcc7c78bf0a11b70a829f7b95d6b0dd869202b94dadfc0073a5cddd8e105396cc5ba9a9c4d475044f1998c7eaeb67c27ccfc79140b8ec1d32969a31df9049d9566ebcf527878f66a57e7abbb067cbe69932b96b02d318a313c1c996e25af6dc286d88c80ddde3bbd35b587ea5b14be0269f41b63cfef9af6ebcab7017a737c4f0e2cdd97cf6b38fa4678d7919afa5c971eee5aea488425abd3ff8cc34837f6e6ace900a73600a4cc8f465bdb7b7ccfeb757f231ba679f7da60c632b0fb312a39dbaf79e2387eedc9a1e470062962e7db4a62e8feaeaede02810ea751aaeba3045ed6478f196dfb4d7ec7830a6abf1cf5d397ae02b7ffb95ce4da23be56a02fdf6f4c67a6793f41181c2552f82854eea207094587ffa62d658fa18879e88e79269fc56cea13c79f2e4d117bff8850a6f331cfc597c34b57220ee4d1d279aacefbc5cab83f7cef7a83963718e2a7461eda813e5fbd0430f754e12fe6db883320e6ff7e54deca2d7a11c1ca6c7df26aeb8f48f9dbce7e8c3b17e6ebafb547ab51f1fbd11813b1fcb6b1b93edb82fc9dd4f3804bc0a9fabf734269366e6ca206f4cb60ee36c5be68f9e22ffdbf01bd8d6b1714db4628761090b66fac10f1ecf8af5e7b7725ca2e55346cd3f70f3c328cc867af1cec7d44bcf519cd3e0e6d034845e96465b66b1f70b21ed83f9b8d3791a9964349f2d2c0d8b144958e1085c51355fd3a8162a338378e7782ef506cafd2fffe59fbb7a03a32f73153c650f7323c8f43e7a379e4393b5e2fa6be111747952478122206067cf9e2d43316f99968417e3c2fda31fdd945bd2738e70fdeb5d8def84c5305cefd766658ebc940a93d047f428b8fff5bffe674d4cab6e9891eaf3d1bfc9e2df2808bd410528ce7fd6824963c20617342e6d83b5723a46cdb8157c6343633b79ffed5fffb5e6315a514c5dc09b6a738ad8f1501cb5d101c788d356eb3466e7aa3059fb6d0154cacbf3190b9d397be6e8dd6f7e33e98e3a16d57bf23ece0aa0f90ace0d4eb12a7cf0405d014fadfb692b4fd21ea65b79d775bdab0f37cae4729c21c7efbdefe8b6e0f7c14f7eeae88df46a51ad5b8514e0b66334377f3d48ae8036581a802bff7c5ce67a1b8252e96d6506fe20bbc1ad413bf75b7d7785d1aa5648e81d3b3d4090d3b3ec423280b57eb6781010e3c10c9c9a0c5ecc8ff94cedf5ed315ab941a58d22e12214c1622e2e0f6374ef41711c161c0df647cd598234a46d252f65e1328d4fd09ca561d2f4739ffb5cb566e767823621b9946d1f5bcd03771404a1ed71041110c2e87835013d56c3153f75f52263bc3f65c363d77536224a27e531813a568db2510f4a457e160625631a8287152c8ad08ffbfffefbef3fbaefde7b6b6a1344f9ec78f8d9cf9ea8892d5d57f4e4da79c58c83fbad803cc3117c42d1e56597a7b7d0cbd81e7447be61663737cf2401e3a505dff8f0173fff457b972aade02e5094cccf8f5fb0f42a63d0c4a943feb72cb8ab1b256073ae5e5d7e4abebd71e97ca9cbb7fef0bb3f8c22481cbe3176340d62b78236ec27865beafc017be85da2964ede882bfd73bfd2ace7f5def53068a9a8e8a34b4cd38cd16e8a12bafe8eec41dc0d5ad56c17f60fab00af0e0bde278da0e9cd9842f957e70707c275647b63a83db8836c89542f42eebf964b14d3c29a430d5a1b3a1c8e98f9d3bcfe620042c4c3e79833eb149985420996441d2365bb3b66d7d3147853ac3f200d4c6bee086a7bc4280966e8120b63879773e2edd3bf7abafb9d98afdcc73c663433b4e0e77a297356c68b568598ff3281ad88f53e950ca3a4e74c19e38ace26c408a8dead00603428ed1a78618b46eae63cfedceda2792eebd28ee0df1d45839905b4233c3ca1bcaf4c79e5cb8a26f65a3d14cfd8acb0e18dbb5881b00ae3c5175fee3326a5343937d0f0ce78573b4716f8e80bb6f346eebc332b352c668ee7d5fc9c76f11e5fe8c9b51793da322a82675b4e5766a43ea647c61ad9be8f16f58cae172fce872699d414908d98b60cbd1873d08734ace384337aa956e993abdb6bb5471c22f067fd1873331bc1d2332fea1df276b2edc2a130892c4f6d6f970cac3869179cf269d2e19d0b41ec6294ceb11327324fd6cc1ba67d80e6845581ab0b2d961ba60848c0ea758b2c707c8cf36321b78707ea20e46603b011664a1c01b28691978b2665ca0921dbd17537641e2d668d5521c63ab7a6d18c7d301347028aafca6b608dfdc11cfe32e3914008d185364a08d127c448a35aeb665c66e0bc8ecc068b67ec6c5678fffbb7bfdd8da2e2fcd47bfd561cd8e058dd80d4b4f2f480b4ef300b973a217d2d9e300defc86a13b5350b379204fcae1ea55e85c3cb6950e5b44c377907b62908b8af150e60ac6f9da18d7794604044e08c59a6e70ba4d1fc017553bfff7c4b0594e363b5510f20cad88cc0111e6042818e012d0d1366bb905d1933361337d330701e65a45db5195e39778ec3634c62d688e3c65f7bcdf995d7b61d98b8afbe6aa7747645ff2ec726583cb0f5d6ea4e78083cfaa09dfa0a5dc4bcdd5340e8733942c6416381408fe993b884f57a7a3230ffdf833493f1afe543e7ce874a1d1e8edadb4a43b9dd7deeae2a7c3dbb4e48fa202617c2aa3053af4c959e6c9249bbba63b930a8eb04122f342a2f1664ab1808993183714100b7ac9b6ebcb966ce173248b68bd9b860ad922831036b60308de6c3793c6c9622d1a43df16ad02e7eab2ef0c0787fcc49be365ef2fe2da2eb8de0d195272118c16122cbbb7ead449ed55dafb20efa4417a6a50f36f8caa613b22c4f62961abbc209be15981206cc42db95bf2294854e43bf249a8cc5a18e9b985befbce3134ff99078ca156cb9e1747156637736040086573e4f1fe70005b5ca37be9aed2e3e86e8583c199c3fbf9dc71841b5f469052663a73ca44c523db2de65026c5116aef824e67134bbfd5d849fa365c167153cf9e493c589f774be9cf976cd60632ac38f42d0336df4575edb0ff8b6c5fe597d4a835c09207dfe762c1502ebe839ab7c501eccd586abbd0b73ab81cb61bc320fe3dc1fa6575730af4e33c70f880f261a789057fd7de24340855028f99324044bafe1045e15d38b6158f673b55e12c90fd91d1cf99a3d711a610b2b8ec9c34dac271acf9504ecef5c822707c74c9aaaa4c809f2af3bc261a5bc792b424bdbb77207e5cdf3e0c289a027639a305768d4404ffde653ae5cf32fc6e5aed1323ad8d725a9d4ad8d917a62e69ffce42747a76db24cc39ae4c5ccd62bb6b74acd57fa8e4b6a6ac27ca344c841f8fa25cab6aaf45386baeda885b1f2ac73e64060ce31536fbf5daf3165a02321c3d8bff9cd4bb22798b2f07de99c831185c084535f8caefe7aad19ef2469602be35ce611f5ba14084746eb0a527090b72109e9c215e0dca148e2618ad1f10373d21a4982543339ed773e9f107e29e3c6df9cb1d6126d0365ab033816542fc501a9a95fe8b495bff88b45c5d4a7608d9f3b21bdd58162b18976a640b2e0e02014bff686499c00fee2d5057bc5adbaafec57bf9f56596fe79a9e0cc03cf833656c170fa5d015051e664f758b90de44835acc6a6bc67b398ec011dea16a924b03bc42a602ae9eea73f1d0b0159e371d4fc4056f8ec918687033a792f9ac3001cd7af1621a5ba30ed8618c3c223ca6a389991f16ebb65192700832a5cddf621118ba775f5919278a319a86f5162ef5908531563d687ce3461a117cbb705760de9ec942d58b175ed8d1d33b0d6f93e4d063d5759f0443e805a4637a39ef449849f2991ed8e79da6a4a2aa1cc320c6937fc83c11a6c7ccaec60814956315302f3a6056bb0fe07cfaf4e92a0202ca6c75949a95278495521b864a0f9dbc1600f0089e38715760ccb869319cebc268d720c5fee029f465ed186bdb453d738304b4cc91eb6a9f194b1576c9243ea9105f2a8dd2b4782e5646da00cc6e794a1be989df493b72e0500c92cb6b1cccaaa00439841209d0c045c6a66c54f964eef6e5aee7433e3e14b0f57e776dc1f39431d946a02973abc4bcac4f227f0e01ef80ac9be4d3707a0e0cf96e5ce97a334c33c45984d9d56be0255f976815ce56f876af77348e300763107ba12b27a2b1730417a6b6e817435aceb50ae170b1620193385fd1e09ae7cfe1359c0cea7965182a68006f0cc0693b5fa824a4cc439e2b1da5c9653b023ef39987d288b7cc1eb0308a5ed496921ffce03f9b07a0d1ec331e5b8c11e82941298343e3951b02b5fcd4d76a0aa6de1d77ded179a95b6fcb81a1316fbefffdc75b8f9517acd51e2bceea92b733df44708a7b68274de4b5345cf3854cef859371a16567026f1dc788b1a9f78481b0af7298c7e69e8c69a4b92ebbbc579066a55b71ae87f1c307537ffbdd8ccb7cd3ac9f25a629872cbb9eb00e840ac1d06d6081ba125216d7749deb17bff8c5a393a74e763a81b79639f94ae60fb5fdefb21301be020716feb0b9949386223ac4bbc595b64dde3f8b562bc6f3cab3ae57a7112fae6937c1ad0b1fea25442aa15a1ab22b3544e6ff21f0be152fc85880567e8c90e14a0ccf3c1a87fae4dfa597559e5552e137aa384867f0ca1d4ce398cf39f7276bf6067142606995f10f82c28180d05adffbdef78e7efce39f54100900ed646e851220b80ad8304fb619f05f4cba1225e5be9701b21ec12a913733c8767622c632083758ffeffffdbf75f58606525fcc697e4caf6355ff223c9ca686296d2b10538895c64f7d8a57ae4ce32f7ce173d90df0a56ae435b9ff425cdea74f3f1786d91f455dc8c9238053fc53c8db11029f29a228c6635824d2db3a63de87383edac9df958fe2c1e40102ddd291a2440b78355d462e5d1e16fafaf28cb1662d83e43056e2ad539e3c3c8dcbe9d2bc81cb54418702df20fa9c94afb2589e75eeb7e792372f624910066d3674dad3cdfb890360b80668f7c683b7472939dfd1bca0784a4ecf6651c0b3cf9eaed5e3055163ee5a707dead4a90a192802fc57703bf027c6bbd5662b9d6761bd5bf12bce75cf6999ee5819bc80b8e28058052d40deaeb48771d2d78627643119514d03f24e3151d6e4b1fc0d2d40096ef6041cb41b953f73d88ab58018185606e8ecf94f6682cf5c4c192229cb22badc80b3339b170da131ca789a927b2314b1630ab66eb224be5f86615265ec62629893e3a74ffc242bea6d4eb47a3f33f681f5d96dfe8b0bb804ca1fc2461130c9982233e6824a90e97f5a3a0519908875f122a10d37b711d8a91fe6034b0f8ae9cc41d1fa1c3cd30bcde2e8e92d41313ecd9a48ded8ccc958e522acb6f3be265acc6e26a315161859bd055682ba6c8fbdaee906e51ba70266de92d2d3a6cf9d7eb64e0bbdac359a94921dccfffc4fffdcfd5b0b36f8a583bf296ef18e61059aa1b70da08ea06b5b45615e7f9dc37666bfdd4aaf2e037323562212d576e4a4e2adf579ab90a1ed2e9f096ecbd7ba1a258205014a96b385296c4c8ace0dabf2c15358e5f6e1aae7f5eeb08e2b8fb8152f9dfbd54e416dab045e688d86382ab2ca976165945ed801cc3b44aa1b3fde268cedc3808707eb4cdec9d7464d8506fe4638af102f91ca04fbfa68e013273e75f48d982e9899cd6d85c1cd190b69703d89b413b2ed9e672c1e47e3381fca2bb0829d9e23112563b7ab44901c5fd0837632b76343a45504fde2e7ebbfef6ee8a79e7aaacaa20b7bd3680e887923e7dd7fecb64c98ee34be73fcf311c32880a88b9d70a7065b93c18ea00d6dd56f85b2cac5d02ee3594a891382a38490a3176160fe705c981374b27299313de28d37a4978f023247e8f8020b996de2c458d2efc33436d7f96d118467c2c89c2aab67302d52f9cf354db80b1dcbe55b6f04573e655873a86d2d77fbe94f9f08ae3e6e1f81cd3f308deb4e5cccf7a6d356759defa0cdcde21765ebf14cc13805984080fdf1dbf39598c45bcff9e42f9fdc4c497c37f9d10b8c5a243c8679e1783de69f257778a2eff14e4cd1bf89e97dcb47fea6d329941525e3cb34f76635c6e1847457245d85ebd58f8bf75d5758f5f17c18bfdebb4ae35df793ad44892bf3972bb6bb954903af742b6e0142e835212d1d538e5b594f16efc796bc9cd67ba8563e20add08640d9c67fa210ca84e9a7b2997299a253b13d3768e432abf4614466826b1d0801e2bc11260501a4e59db188614c88dae2cefba81cf0e15f410c13181b193be0403d011c791d79c4d48d07abf320299f72b139f0fa1c6cb3e6f4864eeaa5a6658f1dedc4b61e8b08a1814139c78295237aeb4e4ba46cc2f5f1f46ebe8fcc2cfb588e6cb3d8d8a1344cc0ce0f4610b9d247f0d106030e594b9b3c531eb7a547246ccc60f8ad5ebe89932655295e70d35b3ff2d9cf66ffda979a0f4df540564dbc90ddd6cc44341667bca6ad9df181d9cd6d0a53471880ed28f5396cc9ea11bdbe7afc8ffff3ff6a5b98ae31dde02323bff8d9cfbb4994b733c00b07a906cf0dc7c45399d640826553a8a30af8022823ab4798b5be76fab5af7fad6337ab531c784461e351415bac8026c35f2b66ae57f3fc4ab3e2ffbf3cefcd459c2d68a1f7090ba8578b7ddc3779f242dc18c90a724c8319eb8a967ec1de724e4953ce14b7117357f454b82732d5cba59c611ed6751924b068c505bb6bf5a2dd694813957a2a82c4dc62ce101c660a4663fe2c4213a48edfc00fae84d259f5e672acec562e86c2e404815d8fa92a7469e03367ced4ce5fa681f4bbea22d016c4e5d5ae65e13db84f4f6050de3d6d29ebfad050e01c3879f264f1ae9995fac1ab4bce2886c098b35486360b87402ede05927bb4d1f3d1e804191250596dbada037ec52970f50ccc670b9887f611d6281e3d3e534bcf252daa31d1f528181bb34fbd62be0520a1b2accbd999c67404d532b093274f1dfdfddf3f98552477b42de04e299a52e0cd352e16c0d2db1853adfa693b3833fd79909f79fae95c67c29a43c7cfa2f0871f79a49f7cd2ee0df0058b729eca16cebcfceb7f17add675a506e6305cfd5e5b8cda49aa127a1179cbb9002ca22d600bae3cd35c438caea5a3c92e5bc378f8218a69c6bc488e957ba0a5de5b9878cf5bfdf950d250b374a638446b4aee3dc13017477bf5eb2d311169bf071e78e0e896bffdeaf454b6e76f5a584f7528949d43291cc54730c2d7cae096f7c54dbddf7266684cd306cef633896cdecb98c4cee63fc5abc7dc3b34914ab7f4cad10113b6723868d4523918c5bd60fc03f73703d742612657b7136dca82c258736a70d590ea7f215e4504d190fdeb7e11b4658a77ec5bc68e511c98d7ca0ae10a66489e81d157f5505228c6a3d67f8202be0d9126f7f5a0846c054eaef6c4317709a65e157c74b3da9e17d364f31a2f1326c2c8b174db7bb7b5e7461be51883538ea79f7b6e14b578f86d345326d89e098ba5568f3efa9d8e87093905ef3d33d43230534ada66d577eaa23605b46b8f1dddc427244bca98fb799e32578cf40373e217fcf57e770da02b8f1f58400360ddae0276990e6fa4dbd262ff31ebb20f343633ef9381f242a4d900c559b9ba9d8a89d822ddb572fbd2035eca08941515ef96a1f52a7a1413bd360edadec06c705ec457bef295a353f79caa29683804346debef6830e58e53847d0f3e1c0933f73766c7e8cb6133efe668338cf2cb5ffea23d5b57d3072673a99a75a3056ce5a912d8c60e535b53017384811effbd4e75ccca755554ae7928ae72025e02257e353e8563127ce2d567deb5474ed9686e09985e57eb8df908723469989e90e9d5afbfeef93062e63013e0b8e0376222ebeed6ab9a127016e43201f5a626e5ad153545a29ef2a31b21e385b5e44dd902c530f7f9ae41f841db0e5ec7da7eb6347164dd9895f12162be5d604ef2c33d14e757bf7aa68e2ca74a3bf2cf98fccd94f1d4530e268ab73969f994dc53b29a587dd08709ef5eef062fcaa5740a4e708608bc7327f92ef49db8b6e58a9642ca7d685e70b6b0f25dfdbcca684fb6887598680172dd85abb1ca8ba0dbd718d7f84583da2088f07e4d912483c89656ec0ec90dbe4b7efbaa8b2058b36bd7279034ba835d78c998046c6f4c4993128a4b31fd4c28f38ab567e90a9f20bdd561702458a385d6d89106b4da8189f2d2cb2fc6043cdb72967912448249ccc6e0b26875ad559fa5f3281af196ff60f6969d670d6b30cf54ed768f382aece036cf6770ff625661c8078cded037d0281073710bc745a6f6b4a1ce62163831c72d7e751a96b196f57d761318f49ff8e427a3cdc3bc0994090fe4dfa4473406ed21a0689263b6d166b58532e18e2e266ded96d686844b503673db7480b869df90213d8a5ecf7406a657c62e7d7a2c8b8e5914e7a358c0d0b2c6750b3eeb8185c08cf4d51969fffeefffae8e1c0e1da62e0febb3cf3ed33d69efbce2cba1d3aeda341528ccac0ac6406d6e6df766ac1bcaf7aebb4ca01ff814d41982887b55fd45afb0e8e2f9500e0ee357dabf740df8bd0b7f9f110926ac86f6d4fb416d7bbbd28c5618377ebc8c21066d45a3da5fa60122757b24951a380b761f874457c095064e18ff7b8f7d2f1fe2fe591bd000da8fa642383fc73eeb251673103c03dd0dc3b944006727aecfbbcec70199322ff958433f0e38ab013aa80f93a5c65b7e9731fd0c95a6398731bb2c283182d4703089cde368dec8c4f21dd9f2604cd8f160180e7db8ae7f97e98197b2da7d050cab77e66124ac8e461bda6ccc91fafa16d9a54b94d77c8554cfe14316bfc9c7d1994d6fbe319f967de4e1876bd661d6b64b70bc29781174632d822cc0bc262c664b682f932ba78e45bbcc61b494475047ce035e4c5e508a413bfad9186aa57c69bfb9c7eb49dc84cc58773986c0d2069639d95e235840d0d5f58141293999ebe4c99315d880af15e218041ee19eaf18bed2f60d417fda223826aa38055bd68123bc1fb8fffe9ae093387f014c9d5b7fd7fcf6fca8965eefe33caff7ee85f5feea7bcfd2eec336263b0430ef0f13ed93bb2bf060d73c928579f324a295d6507a158cd06d2f11b663d7ce89b8f227c35cf2778f8cb855e630f07ae7142c8375a605b2acd508c78e6d308bc310d9ba3426974962786a64c13de67d211f7e7826da9060fd3627df9a33792f732c790dfde2d3fae78ff2fd836f782e8c3f8d51801d6f8df74f4fc53b46901cb4f3899c8568f0ce394043335d9887cc4a30f514edd52284c65ec610e229085a9797eed2c593517ff90e75cc3ade328ccb818089f5e4e348f84d27ca312b8566f50c87cf0d71eabc9a55eb26ece1d46fbda51ec7339efa507a04caa74bd5542479aaa4d26668a4a7ee4032f470ee0927855e7e995b5a89a7d1ce0771757e24ee72cad53b7a466782002e987a3c5e5fe6eaec8503c54e80778f9e7efaa91c7874bad60767d2b4d9b5f916dc9d5538abfd3a7e4e3bd897e64ba7c78e3dd53a73fc944f722d1b029c32091c85685e8cf0b278aa7493cefbfea4dd42a705c427c04198a473ef79c5bb175aeedcfed9dff5aec393e0bd998bfb7480ad445703966ad83685eeb3344e1e8c44c868741ab2c7118489f434cddbcc2be730f00ecc56b9dd7333c4c3168d39aeea68c2301d4682979f81754db3a4b58e4dd9e23b891a0c0989d2c471f3fee8273f3afac1e38fa7812c2f1aa61fd7fd68441855f36b34665feab07eeac70c820b73ce18e1b630b139aadbe25a9f5dc23325c0052f7d8a2d1d50abf8069ef81bae9f3a11bef361620c8961089315269c13eaa017e7917bf5d5f9b8a13128a6c73c26ded778b0f9a35002a6f8524a7ecc4965a4f4d28632200076145b99af4722f04c31df067bfa574f07cf194719979ae805a7bd5deb33708c9b4c35bcf0fcf3a53fef5f972d8599bb6c29a77ea1b1bc04efd7bf7e21ca22ab4bb6b670a5186c09aa82089df08c6572942865a9073a77eec128ab59fe846e7a54531a4cd6ee2a00288da69d5b7704179130f49e7d8286191f8b45612823be21f056e8dd56bf891b38d386935e3ecfff7fc24a3fc6765971b2af170be8e1558a5d35568552eed2221df3d0dcf9c1a7bd597a3423d48570f19447fe0df1f56ec3a0797b9f2437d0c0618edaedd1eae95476819644785ad5798d8e78beebc4897ab8aa49115c3794404098386b558a1e46b9b41e416ad8b90383725acd2261798c7130a783644c1198d834e9cd83658d2513b0e5658ca35e9622e5efc054cfa08170f99f5b5ebafd413f14916555f0770aae39b8ef7ce73bf9dcec996a7367453225093d7ce18511c7b40be0d0b6b45462d248374ea1d9d4685e68e585f797bff4c59edccb24f3d3131380c7bfff78269a9f2dac698fecb14b6f688c4410d04d398441cf649e8bd7520f7c998e0a9df5189c434ed5d20bbe1a65d139ab98a7268ed51f9d840e23720577a66444e6972a99ff22184c777376e599bcd2869d748f23a4bb33f2027927a3ac6891ab76adf991f9cd379c899f0f029e3c79743c756df266c9dd64f634b46d41036392ec52f7fd8a035f183a1df276daa738edf349b709d964102120e661e269c47dbc2206ccfcf58c790819e69dad09b61b3019d78e5c30f7886dd450dcaebc3e6ccfdd2691f498c45915189d666632102a9e32e7e26b6c3d8b8338f510046f7aa7810b26fc2cc0f5f9242ee93fc64c14ab5efe414c9aa6ece39cbff1e0a73f9de3043e5b01c08ce6ad3a4db18d314b23d902a71a791332f1fd959ff6e5284579703cf1c91347ffedbffe57b98f3e7adbf428b6e49c3d7ba6f33ea60be0a36785dcf48ed3439676f316f685e986e0314129244c666ce7eda23b867de0d30f363b41852393cc8e052be3298e59819f6cc9e79e9389d9caa2300e3e7f3e63c308963196315fb7f0948e39572363cac71f7f2ce9428f94dd698896911e0412a06ebd792b95e73d0dfb5a353a76d60312543ded124cfcc5ec334dc069d432c09429e02993fea3f5539ee5690eaeb5589843cc78527ccd4c6d91fa344f014cf9f3d7bb81bba17df87277bfe442847a0887718dc89f9d90fdd9cbade012e1e0be80c02b16b9badf7e90c70cd54e6944db5dcc9731eb967926ff156187dcc41e564a1118d22a8daffceddf1e5dccf8c97887a9d69e24abf4697e0c8378ba54f540730474e31e5e84d5aa0f8c86799a6eabd7a41a41535fb862202b2b4ede9d15de318f5a47b0c1cc4faf312b54a668093043c711796feecb9e26f3780485c9ccbc53964da40e96a12894d5315bf2c2bcfbbf628ad1da357b9377da26f55bc4695d933abd98de840260bee96dd7f7d8cc11e96557bbbaaa9b4ffc86228dd7839b1a3186e62aa7bccc0d521882f28d857ff7fb6c19ca8650de4b5eddd762c2be1025a0c759cc253d9a57d96c65cddce408b37668ef957768b3cc7074ddf5ccc14f1a368031a589669b2c590c0b7fca52fb1fcf2ef90b6fcfae02746beb85561d83a68ca97756d344c91bebea518d25c59f8bf2d77b5f8cf200db8a98c37aa88bb0c8bdca9ed8fddfbf14bf4f31773b215b2f200be91506d9f574e5759f0a8be5974a6a30e6a2b193791b0db1ffa4d23068ea29f510a6393d0fb479a78208350df0f1f456c60f08612293e0812fcf686c33f803429a1957e57d607861598f31031c2d7f9266572f9153746ef6c140fe9d1c5c338e05cd3e4caa9c96a19ce495bd38a497bd78d109ca17ea46377e3261cd6181e1efbfef819ab6c533b0aa8cae09f903803e98c9f1cbe92df3adb5b8da35bef197b22648d8c277f8a3a09ef9739fff5c7b5c93b837dd389f4e22b872b6bcd2c17d685153762adcf74943f8396aac853c73e63735679709ce4bfa8b5ffcfce885177e5da6375e322624800465e1d75e2160b5fd623e0a0402c6cdde534c2be87599e20e8ca5044d3db054a490bf2efe08b5b2ec24109082f263f63a6cd666d2a6dff22066cb09dfad92d008ce7a33f832a59d42cc1b2bd143f1c43e929521e01ed64779fb7aa0d484555f4f87f7dbeb5e76f1adf7553d9997899fccbb16dab2cf8b1d2cc8174b7ff5209e93df98a14260501f4d61ccd1f312b3a8555079e52cf0725e1dd652a1a64e5a0da227924bc505a6426fe19b32f5201acb7bef8c634c523aef8f57eeb7d1bea74f9f0ea3f8c83b810990ad0a838d7a0c6c0d45037296f096315938564a9fe08b19686c6570134b435b7258d4159d41bbedfd0454cfe94cc44fe4b0194c35145083114e9568f99b89a307f3051b4c4fc806c9266ff973b7d1b074bcdce5527a7b2b5508ecbe1eee23085ba6d2bd79f6746cf989bb25735f5ffa62ceda3f7177cd315b4566ade39b478f7dffb17e48833344dbae00ef45bb8527122e6fe094979e3aed47502ccbb2d9d51a4c8b7599fae6f26ce979f43b8f7632bb8ead40e504d293719eec9645259e3050b826c57dce69f59ce84ac1e3939b62f6fae88676b36ccb385a5bfeebfffed7c0cc46ced004bf58286cbae5befbee2ddc55af7585ff0a687af8bce20fafeb7de98f2f931d841dc5760916d3e3b7e1b949991cabf1565a05340eb00d1fef0c8eeb084865eac64fafd0d68ff7a8c9c06d9e550038a0a5c91a2fd556dea685ab65fa3eef92761c00d39b69540c6fde860dcea43107e6d866fbcc3c5bc2e37d9581c295b0999770567ecb4ebc3a197798e0c5e8cabe9815f34c0c734c56118039a693ef376783604d92ec73e3a4d81ac4f8944962a2f69de4bb1c73a5da7d232c2c4641ede9c089c2f367799115508be6f095aaabc68758a232899bf93593d811766b13d72b75aae99ae3edd44b3c86b4bb1ccc5a1b61fed59684faa16874e61a13544f2823930f431390b72f39e33fc380a4119a3770873ed3b36977f1755264054e3d98392e1ca33b04c95a48caa06b302310c67494913a9bf79ba619fa1b97891b27cbf4fa7063beeb619f3bfd7cf0e488b10d2a6b546346eacd9565dc46386d7781cf534f3dd5096fed60cc7a4d7677e399dfa547e344b1bf6dd162b55f2bb9fd59ef0ee3dcafb487edd4341bcdd5a742b61268f409fbbb8d1f1bada0959649a9d1dba892e7e77dc72569088d21a848c7654918e5b10bf20de2a0acfbf57ae2fab4a56b3929c4b5e65fc667189a29a021f42206e94cb4dae0b1e9cd31612ca14507bf412140dafb1e94332cdcbf0a610e19bb992035f7648ec7a4ef2ce18ad0e65d976085496a3ab5c7985eb73dabb15634ab3aca2bfd1dc1c5ae6275183ac266fd6019e64f1e9b2e310ecd0cff2aacbca381a59f3a0c8c7195bfd15dbfc678d2aa84b426dfe5c7c41486deb51f790f73df932d1f6b3e2b28d6642478c522082e41aae3294e2763606b3657fb4fdb059b346a5abb3d815e01cc3be385e54831794ca88c190f57c037ef46037cc27b6c9ce5fb62e0335f95efe8058a0c0fc1c33b7999c89c52ac843ac122501401456131000157975132a3040821eff0eb31414ba35091195b85991ed158162eab7eebbaeab9e8e27a18b7ee5dff529edd2afc5d6250b6408c687b61446a58a2cf21d26a882dc1b04b0a836c0fd6c9bd9e4c23138c393330a90372cf28909edfc0c54c85b8556690e73c59df1a73e213e22fd3cc3d17f13203d7dc591920b8808f000beedc4f218b388b40533273346bdf32a9fbad6f7fabe6a10d826b39d272600c7d8c4352dfcb6b494ff05db54bb9762ecf3619c720ec8f54230813dc6c0d945b5f6df96098c7de389ec277526fb8a98350dce7a6f77a565e388ac5988a7039049599c569619e6dada67026a4f1a3b59d1f8e494a28e1ae75e1d0f6cc2dbaad80b9b9fa31a99516c565ab1ff3d711de0e49bd33d30f26bf79ff9cbb016e156e7a0cd7456765b40e5b637847c8f4564f5d6f11717a5ad64b8245d91403d391a04e3b1e55b89cf7f8d043f9a20adae7b7b76c8646ca719689009aefdd29e3574ffeaacae7baeb4659a1d36fce9ec9b4c6bd9b253082dc7ce599c1c5f39211f8afb0eae5baeed7bb5e93f68afd6457bcccc3022a1e4a031bb362af2073d018f34e414cc539ec1401393d986934eaf19c8d35907a69ed0f994739c2c4ad8656deccc1fcec673fcfae6527dcbe1613ecad12bf8d9e7268ada9a81e44defc5c52245c2b78ee8be8f6a275ba92a853bebacea2d677de7eab42a6aeab879ab2a6474ac278c352424c566335650e730df32a0f93684ce33ccc22d1012514b90bea70439c1e34b4f1c79cb2e5f5d48fb2c2406ad0f1473c6398ddf8e43fdffacf4e5c3b7a8ed031b798477a34d536deb193faedf4ac4c5826f1f410810ea090ebd0481ccb64ceafb428d830403ba2836025fd030f3ed031a7d5f94b98bc43bf0a6deed7b53cb3d563d284f6b9e97eb728082629478716839065796b02def86af871da0b6de619a4c17995d3fc22e1af62f9b10e3e91f1d94772e02dcb67f101ded4bb53a2d336034bf6c37058d6ba3fa4d3615af7dea5c58a63ed03991a09a1840e83e6b6cffe68a4bec61d0987638b5558620bb442c60318bb1ea134e61040e60d30807f25286be06a9a5922e3f3b096462dc6bb261accd1001ab760037352abde04795b546ea60ef3a6cd5b1492a3d72dfdf6b0f2a98b1eb884dda13e19e0177eafe62584d767bc00977ad3ccd924991eb14216af16f392e770d1694adcff6d7d5371e60e2163fef8885f2793ab381c8c33672332c94c3130cf984d3c66dffdee777b6c02a15a95a2e54d734c838f02c0c8e0724aad7589ab0d5d0f998842313739ce9fedbb026a103c095c97856d6db0dcef43c2a1917694f610e6bec6ee72bc41e8661e948b9d00d4224993aac78ccb7edf256be37419ba2e815ab017fe87b0e1f35e7ef88f97571e8e166db470540767e62b476f3def16ee7bb958f00febb1ee0fcbbce21ebfc412dc998b872f5988edb1739de2bc9dcab5b1762c3cf1fd0b6032790fd1b18963e3aa6834781d0271aee5d556418477af51a72156450a6f2b1831540671acc6a6f5986df2e48d3f0761b013116c0bbbf9570ac927637202acfc0d86f2bc4b86ade86ac1dee78fd8183d12f53f90b42941809789715f7191f9c9279feacf1406dc694ba6da72a260fcc3ba4a83662bac152126e19d35828600a3a915e57ffff7dfe821afe68c301e6bc1ea1a4a4dda9e7f1298fdb7814507f57331f9dc4d96e9e50cf80fc3fb318ec5c59c0acc3a0a6ee16e82d75172dce2deadbc2906197701231b57cdb0e1529508e1965e3a8accb2b43ba2347c7c9e90457506df5973ca33fc40f22fcfe6e4db17501e4a6962dc2b0b4e4c4df5b4def3b538c098ce84f83028cbe20463fabbee3ab1533a2bcdaa93e755ee8a6bb92abb85c3e795c6ab19e926617baf20b85e166129366aad78512b6eee87e9a6a8ed6f4c469e353f35ef1ac68c47fad04cfe10822b116c6cd22b0bc22b48c5243236f01d69bb88cb405754105a07f8e77e6060ac024d9e1168f187f0e151b342818169dca2f758d7511ac65c1bbe016705c403d9defe852f7cbeabed091b8f59773807bfa79f7ea68dcdb4a241e7f8eed1a68b5946b0c08ca3258cb8f35e46ab5a04cc63d8b7e801b5fc987b189a876d27ac79f1c1384a6c413973e66c53a2a1f42bb4ee7910676cd88ffec564e489036785d5ce431f748a991941268c7a33ccb8c64c9407a6b5c6d1e268f55974652eeab931b9b2b8ce99b0e0715a7ce6339fb9c299d1d5fd1923325f096344af69cd971164bb02a6d79d5a2d3c17dee8d30cb930cd7ff8c31f1efde4473f6a997608e8cdd493c9bbf8ae79b44d1c3ae6cd581c4bf807dc9e9fd6b3eb0a57f3e98a3fbce2bd113235c7402d759294311b7dd8547987417321942de4f0b57b8d9bdfacfc30205d7bcbc675ac28e16a22891b3486a15a06785b7a0d68e994f3349a37656caf644d1882ec1b59dc2057587dda03ec9c9a5c1b4204a1f0fa0c56ce7e7452318d9b3ad8eba4a116de98c140fa9e7beea999c32c106efac0850ac18ca730cbb8ce6d82c46c18d39c1ba1f263ba995e30ce7c29bb037c73ccde398cf227ce9c0d3f427ff11a4e9439a189ebfe86eb4383bc871321efb825755617aef755b78a1b7a8195ff29b95eb531cdf687a1c25539c896e449df980a0fc1669afe2a9b26c15f828941399ecac4ed49071fbdc973a74f1f7ddb370462e61338c20d8e76bcf7de7b2b5045280532917918b9dd798805f542231ec03f86769c418bfe4d70f067d121c8b59ece1b719a16fc46b8c611a372163a08f2a82ee132ddf3667a3f66fa28bf29ffa0883fbb5d65baaefb057725866f856c21beaecd5064923448e01fefc4e7461bcc8b5e10b5115bba2489403001540ec2177328e979a70a87002a38b006c62eefb4e8d03c254ccf3202ad5c70f4621a498134e58c0ba7ec44ae9b2baef2fa07ebae9f33868aab628d9b16436a64cb90cc35e9312d36b6cbd7bac817c3fcdf7df4bb1182975b27f89b9b232098692d0856865dc48e3be3ce66a65ca63d833b6d4aabf718b4382f1cc26a7ecd0ee39733f5c0bcc1a80d7860a3353aae76812ba6211c966ce939db56a9ba413b37b679b6f9e8dfbee1878a7bfab8eb7eb130ae32298481131aa1551a7ca1001fe513628b98d149bd2d0ad676e7d2c3e8a530ea82d33cf9634919463757c9db6c7a82a07956dfc39e8929cc64bcedb6783123647b419e05cf6864f1f794c17cded7a7e59527863f99d1e6d12c6476380f5e5a34c437480bbe1f6e300d62c99871aad5392bed157095a8c27f25a0a1b0d295f712373d595e1c2690a8e66373f8b37f3f4ff3b7407611f24c65544a4fc62c72a6a01db1c6659636994c14e0bbba6d15dd05f5d81ee1bce15d21e37d72c2942efffcf9ac0cdf76c7cabbf05fd7c338f0668508c69bcd7e3d4b304c4a73727d3b968ca6e6d62664cae90a0db4484f268e90818f3ec6987aa7377354dcad990fa250c47314981bba336334bd91d51b1adc4959e03df6d8f77aac9a81b65e8da626f0ca3096eadc5a8472873f5a6c6d1b4c6a7a5b88ab67000f7373e3f390f949ba1ab940b63f759ea0799e9557f778967e8183d957bdfe3cef146e3a010d30eedbeffcf6e8d846074a86194868d072f5027a520e19bd9355f0a63d280c533194841fbaac1e513ee9eda23e7e3c470f70de24508f705486ba12f2145dfe6982833f0b776998ced681b20a08d3e28b554f637c2b5acc474afb898c75d56ff872da78810657be056395e3fd8a5b6957dc2e4dbaca0ad902b07b01e06ad983dcd2edd2884fe19012e4d85ab83d997466e3113143d16a7c8e80e8cc1d8cc95146c9970000400049444154abd9f78c014e6336f3cbbb21aa45b6c7d3108e5fcbeaefaca4d8152e51c2aaf0d4a7318d2f2ec1c34c3f73c031cdfdfe7184cac25a8e0b3d012d691ca9c195098e71997572870c0428e130c16cceeedd77ef6c3d7d03002358af69ccf1f0239fad29c9816192f4e59c29783a8783125681d9682da61e79e89aba2b374ce1b9f508358ea5dbc0a428c39bf74a06f0bf7cf297154a26e64b2fbd52938a50eb2984a1017843df699cbec94b2b45d2b31a4f45513033a7cea96fca5637cf4b0064f0dc3d68b7ee0f49ed76ff805cbd2b9a52acca947799984f3ffd6c7b8bf46da99b8f0afa7ec0f4a2937eea4b69f03032d1d5a5b8079fb5eb9a8710cca1d5d4518d0e9fdd1b43ce2a908f1e3d7fecb9c07112d6f56d6307e31a87dab5dede2ef0c054b60da914c16180c382efeab9781d263ac0e1aae8f2e8ae275b80ae00aac157ae5191f324722b702d4d1a019bd7ad6890e5fa3ec6f99197bee9cb01c21dbc903d803e19b7d21485a1b6ffb2b76234d42d2192d5f476eeee70967bab3cfc310481a2a16fc907db11936019439930b54b9740d1b698412f923e28bf2122136a089998bc6342eaed5c31488aa8f0bdfec73fe454a567ebddeb3791b322840968bcf6852f7ee9e8f3993065662db3e0ed8fe4fc8d68f69ba228f4843b737783a7b7194f6286fdc10b03aa6317ce327342079ec4679f79e6e8f433293778120869fa0bae6835e41866181a89f43c66d2a21146b7a2a5db8862c2da0829ce625d3dfac99327cb7c3c7094aeef97d90541795ae59262734cc89caba2c7300fb884cc3b632f1f06b45a04dd2848719c24ed3d610529944ffa1b6eb22ed121b6d945fde69c0fa92eed2d3785308e9a615b79ae0ee0a9b30daaf7dd775fe05faa6235dda197a464958d97daf649ab1de42b2e9eaf023a382e5ce73a740de649bfdecbe6feeab89d901dc29d6a2783d23c6c61155e92330d133fe407dc83b8fc832806a6a931719ed9e7fdda4bb5f4e6575e70f35e288c42d4a4899b02b6789a3f3d598454378ff9578524f0aee3929808ec7aa61f130db3989b720214e6376629518387fc453b4ca4c782c6fca60eb49af7ea6155bc8679338d7fec58f2269e89f4a378b07ef293397f9f379090389bf0e4a97b2a00ad57c66fcab2c29e49a2677c434f9c505a19df44c88fe5dcff0f6d2696357804546ff36c04eacd68f1a450d54c6158ca653d28f7f7f438e0a097f75e4ccdf4b853f66c37b28b6168a8c730e5e003848f7ef73b1d27bd1607064f21985ffaf297ab9428a31568fb9eb41c5a58c572393d2c0560b915736e4c4f93edc22ce6bd3b5b55bef18dbf2bbdb44985288c6e6588fd79bb10dc675d623693661cacb76f9d92c05ce35b29af477247b8a543cf56b89073b7e3a112200ea80f7485bd034eb51da58b47d66a1d7997f7789448281698e5cc0dd60eb7ab6e5659a2078f7d82ddbb41a3efdf57c852dfbedc31fa1e46efbc4f82a9e3ee1dc1d0bcfe866582684daf4dc830c68c41d6716b2be3864df3ad387012bfbd52e716971b5ae80361448c504741d218ff69bccf3ef2f0d1830f3e50f367b4d59c7ceb7dc105485788eb15721f31dd4c5be69832f23e0d3aabece77c0d668b035f5e78e1f99a82834b0a4d7a8ae38d37b25f2e79c037317e6dcd3f3b72e7080082822232180732b9280370350878342a65f0c0030f1edd7bcf3db33c29f9647470272749997a238833ecafc5a01b7d24449ff9934b95c6bc5cbd3a0dae17707e0666d7bbdb66f2eb332f1c7deb9bdfea4e6cca07ade0a72731d663262fc6217084cc4e6eeb0ad14bdddf8d79ac07643e1bdf28d33bc2e0d42c0b83295d4a8a6388636ce52d654204cf0480125d93c2eda593362f2bc03eed649a031ed20f5e435dcf87b096b93a34ded2045763f38e819b7a4f42e9e0ddb0c1da92f4e2fd2a43c4befcc354132fa6b8852d94bc13b243203b6083db0eca0e30ce4858da52f508649febdbcf3373318435eee00ca049cd7f100c87644ef1a0402c4f0139607393e715a7ccb94f098179637a24e607ad645c9676a940132acc73e79d9fa8593790e7afb25ba7143026da34880211fc42c65270a3897b145abef365e272c63b2f47dbcfba43388f265cd88f901c8bf9a4fe10075f1d9dd561bc83b1e12ab8625a0ccfac5c95447be322f347e6deacfd53023898d699f4474716cf8e47b50d384d30f5423075cb3f65e8b5098245bab694182b31772dacc5a0d260424c6c9c43a80817a74b4da894a697f63109f8b3002aac1112a63a13bc1faf9816dfd5d7962293e58742d01dd4e9c1378a07b26a6bd3895957b45386b11445a03dd1efdab6192fe63ba1d9cb353bd1a33408acc5b7e0acfbbe0bf8c3725af0f6a7a42bdcadd5e01301bc901f5e29df863e681a20bbb2ae809177abccf78b6f1c990d1e3b21db57769f650179df7772974e4122ffae087954510d56132dc43b1781581ec6492b4f01f471a3f91617881bc8750dc434c251ddecb681582ae454aa158c7108ca98476297700ead26ddc273343037b6c36a1c90ea70192ef77e81328d4b8b73933bf454fd0917e66c0316d8c0d708a54f04c0ba42f7c610e6bef4567a3bee69f98c51adf7a32430543535da258fc1bd31cd6018e14f83ab33b38750820156698628219dcbd07efe1214c2faf0230f1d3dfcd0c31d8328cfd444856723e6c056ece598a4f3b51cbd0cb3099e7a7287dce8cd08ba745689607acbb74c106f2440e55d7a8a45dd099950bab8cec35c73bf68b87a0ecfd2a287dff198cddd8e8218895fe6fd9b7fcce7ac22f8b7e7501c79e559b01421f47963ab669fe82ae2bcec7bf168809e78c67ec03ab1523f6d6eb1b371a3f69667d503a875bfca7d3f1cb622b74b14df95117b022c60de6b42e333c83568e0dcac311b424f334fdaaedc4f02631a26832f4c5e8ebb5aa5cc9921dc1e18881b5572b7085e488121e90adeb1e399604c1dae6cef25e1d13379cbfbe679f0d9675e4491c1d481f9130e84c71e7b2c5fc63cdb05a25d651fab5c5a3f8ce99f3071f0c1fcae23a8a593ba26bd1e9a39a8e7b8efdefb7a251c2b18479ac3d1a358afb7735307962d3b18c82a7470d46b9c2e71de648c82e946c8d46d1fda364d9c583887d6a610b8a4397856bd31d5619b4e16f35ff3792642a66c30a4a5245ef8f5f381774d7bb57e54312b2fcc61594cabcd51be74083d67370037fb95fbdabc2fed22140befe649196574ca316dc131c22cb6d2e5f9e79eebf395353dd6722d843e75cf7e6eefb04e3baaac829498f2a5e1d97e2fbf2ae32a8c2c00485d4c941bfff99888ded2dce3df7efdab35df7510cdbf038c3c237460fa2dfa1edeafe4e2843d076c24582ff609dd05d892288594c0e235aceb3e68f412d5303d8435461964f331f2d8d3dde31546cd1014fe49bbcf3bc006b169c0d6714b90f8e040bbf0f29933abe9b755165ce320837093b2a3b9c13fd044a5f92804f13c75cc223fc13613f904b82d5ac08819d19017985f9d3802b87f9d0a6c3fd25dd17e8e4ae0dc60ee503053fec0c266e2098d35983d5eba65cd573e7b38681ada44f622ad41bb69068a05238227b4515722115bdd6af2860ee871f983a308bc5e61f51ef2fb318dcc4f117aab5ad4d58f89fce8a3df39faf7ef7c3b1f47dc4f0b287f681bb3a2b8e00d5302f38515bddf8ccb962b7c63f2f488a61766e918b37cd616fa281f4b82f0be9ecf5619b7ab8c7266b7c3dcc3cae6ca975f79a9c246291c86a17370d9e823bdfa195258c96f11c0cb29cb9993afe5cc4d93cf6865f7be312cbaf89dffc0f93881e6638735a3036fc1044f58cf5797ef79a5595771074206c0003c4c30382f493860d8a606621aa5c54b1c4402a58818e0f22e9a0b52015ac442d68e91d2cb0db2600ff2ae5b3d0af7f00f9c80c71496567d303f2b299817de5dc884b7cd916f672f150d398cb0c71bac055b69849580d8a2f17234b43cf0068bd9e4d92f18d554640221bac958db3e9cff67bc72dbc7e62b2433796d9e2d8215bc84056796f10c2e3d1c343d8d3d6396525db8805e42964b6d66ea47e11218f085e78ce33e3ce3b8a6dd37f4d02510421cf79894c7d058cba2dea534d05a7d9873e6f208acf28cad9e3bfd5cf3b5ee81011f9e37ceaa4517b4417b7028376d213d06f5201dcfab71dc5db91794839195c531f2eaab59fd1161e2897488ab437bba11b4e98d3709f0307b5a3a1086320841c7ab9b09774ad15aca8e8ff3aec8480da9ab82dee9a9a79eccf2ae7f6ff997b2d9b71f31519fd098c234a65e42c65ad05b5a68c019d5b60c5c90c157e715d6f361dc7ae72a8fd40742265ad49f23da3707c03db742925f9d25d9dbe9e5dac6484534b44a18b3d8feae41262c34b647e052cea2551bb144cc8b2dd2e01c8332859846844c005b831aa78933663bac3cd629aadb1f7bce3a1d905e5195f73811f4e9312d0236d0f791400e04e75ee8a53ecc431821776e851e8db00b50e548b91cad0d281aa9610bc80d7c088d31166fe8d9ec04e652b70cabeb20efbda7bdd82e5ff28fc76d76008b87275a0aab7eaedea5e43062ce2689030293bc77c72c02c09c18de894df69a9d3d73b66612c6371e61ea197b15864ac03bbf3a42328e9c802e5e4d3925a617add7e0628fdad9b367d24bdf5885f762e60df51ed66cbe9d7d794b09b6cd747f0969864c5dcc98156cbf15d4cbafdc92038050931b9f705af676656f36f0e48567bbd75c2806263b1a50ee8438dcd8725ad6aacfd652c6a34ea1a6043e75f1ee7612c503c80d3fcfc27aee43feacf8f5ec0ae745c1c6bf5f22710bd8ba775d69f724492105baff231f01c358c75339264c0fd64983de7003add062037faedbd3f630d0d6bb2d6918ccb8e6a68e25b883c15330d73bed4dcb2128534e1585e2da7562d87062e1369f81cd8703637ac0d1da48736a9649ddbe9de9b84c3502a52ed5b49b26df2085f1a7f7535669a5dca4816b6915dca6ae2334e6c0be9843464f9d3a556d69829c3384694868c19830b4671e1b67d1bac371aa3c14e95c4f1850913cb98e9ea3dc3871de7bef89f61add199db107c6b1bbdce05ef65d1ba6bc55e6c2bfcaa2450c0e2d602bb3750a826b4d2021b003bc736e8f3e7af41fdffb8fb4c57c526a2941b45bb4a819c892d96ab92ef35ecdf20fdfe51f67ce32cd2937ca6e792f27cdcaed0ae10d6a357d762d44d952687a7502eafdae5c3408415b77917d8e4d91cf6159a87dfe33efd669943783fbc6b08b56e2dd6b7f990fe35b177508dc0a59913d20b4cc87e1f0bdfb3f0bbba8ade9f30ce712295a1781112b98a681b35eaf9a1e820bd2eea61153c60e6821ad942ad3e555994f31b94b3b22d4a51c2caa27e3d163924a37586c39e1b40accbd3620ac777feaee9a3c4c416e6e2b1ab8bff5621a88f0a9079cfa0bdce207065a6c745bbd8b321483b9ce9d1bcf5cd72462b262e4f8b50fc7fbf750e15e178706daa00070530e9ca54e0830c2c76b085ff56bd85ea381ded6bc17f3c667618d8968fb7ff9977fc902e4974a73e34f42aa2d0821a794feb07550cc76ef597d09ef84a98f7b7583e30a04b182d68839fcc632b3b59113eda6970743ef11da214e82bfe284bacf839fb2e1a74e1c32ac86ddc138a91b45a3d7ff505cf804af79fb6798b95077f8b999fa32b7d1e7f99c8aa5a7d2bb7575cd56d7a654b1a266adedbb9d9c67e61a52ac30af871776bca494bc90bdb45bf5dbae8056c80e332c80575f57635c1d3f88257687f05418e218196c0dcaf4e099d3cbb07bc11b4d3f480fdc56f7aa22f21ec3f595c69a495df329377ff003b5d19977e081cdfce162a65dd47357d7402d9136bc144223de77dffd47274f9ecc44f1f108d5e6ea668e051e26d2282d3ef7a553cd1c80a76e6e94cddcb2d4889793f7cd3223632ebda53208c98419641f4fcfb890eb2afed066181196047952b7be612882a4c139770442ed9d9d025fffbbaf47597caa26ac1e1c84279f7c2a63e0cc4d666aa02b567cf43ce90b3db08d357a86bd5e36ff6668a5adc635ae8cd637d7d5f67b9ce69d7834dac1cc0d06be5e39fd05967f013e737c130f7643d2cfc47a4ea0ba91099d758596bff9be4084a21f1c8cc387b388c263f615efc016f67881dfa8ad5cef270ddeb3ba8479497899c6f5102f184956d62a3c30662ac1a7b438a2ccbd8e629250395b4192265cfd7c1837186488d0947fe10f008bd0921cde2fe03b0483f44aef8af8b43966d770d7f2b4a5c2b484bd3e973bff24cf61e183d6b45aeb9432e7bdb2a74cda294e8b9cd5b0b40ca2390997b7ce609ea019a3319f0e43d921e515e7c0a5d98d1f8e1ddbceddd8b4f7a58315e0ab4e6ddca41fc68c3b388e1642ad4ce519ccf34ad9d43847c4fdae757df081076baa1887958183902ab5da296faa7f48e7899168ab7a1b595d312117b771d95807598615e1bb2beefa3b6346edccb7d45d79ce37b45a05ee4b78e4d536d10a3376ce3dc170221711542761b5f5d0bc516dabc3f8753f6f53a785f062e0c0f66f579124dcd1734b630bcc3da74e1d7df56b5febf7c109945e4a1b2fe69eb667726fd45a703752295f9a858f74945e9d3cf1681b7b5a10ad67159602761faecacfdfbef0b7b4b2ebfbc59c9ff9a97cb79cb90e667be15c5739d21ed667e1275e0053dabf2a6487c056a64075bb0b05bc116c973ecf7a2966d2e508976f1c1bf7e8cd2ea5f2f69611c075764621eee0e649857259f01041504ce3f3ecdc7b30c7f4cc06cb98525602e8316c6390b7205dfd036f834150dc8b17aa699b66e2c5d5eb7468cea4f7ed5c5c883fa7654d4ff5db78ccccef9888d58371532fedcea961fe8beb790906d8578669e4c16e613435d643abb3ba6868630b8cb2c6392a65c7b571a841bda54bb3c0d8f79d1d679d435977349a52099bf1b15e9b39765bcc3066a6f4e68b7efef39fd7eceef8af4d31346a5b205ac2622c6f1a5f424bdcd712e416e2f9ab0207613d1f3224d35ccf359f459a9de42b8b74873f7c2570087508927bef09150561c8801e949ecda49662b1287e9fab39bc5a545b5b8303f5d2b8f80e6cc2246d7764a7e7638d08deeeab387757d767d5cbd53bd7f715b2c3040b484b8151c2ca7c78bf4bb761e1b99e1d5a976d9e59fc6bb2b78c0b751d78bafba452008dad9ecc5bfea9d2d6406c79651f94ff81cc937d2226c0a71f7c30eedc5b3a20e652c730ecf6698c8d5921ba0b8199ffc3cebdc973b479f06532d591e0ed7bd313779b0557b749cb68b65732a7a3f1fef4a6d3b2b2b5e57cce544f032b09035f93b9b6eb8cb1024f4f6a45014f1f06589a74a1a23a43b7033cafa8bf0782667e6dce805f9f0d420f820bb64d90f7de9323cdc20ce898ff5d846c5bc707c2c0968aad36654ede7df2eeae08b1aa418f6782dc3cd7134ffcb4a72c33a9665e74f09377e55f94d31694d568f769363485eb5421f7f9279fd0fbed4d2312e33fda31cd3a96cb8265bdd8beac2d65eb34b0bc6b6f9cbaeba966c58673f37fd7e90853124c756357ca0ddd5935f0a5d48d11e1e2ffc24d2970173a2d90ab8509cc451b39edfc90af29d46fab5333e4cfd5cfe2a75d27c5fb0ad9bcfacb7f7740951a64b73fbbc2bc1f94a7b09a2be9fe699e4bf130724cccb8cce07a181218b86b9a5d66a005c00a701888863746f9ead7bedad5e2e69eaecf2a899a50d57429bf84182c0a1b8803e2b0d58b7a006b34bd2b373733a1e724c67160e2f2a56c05e19dab4b3c84df23b7a072ecc4795160f0dbff14e70c444e081ab6df820e04c1bb36c4862766ddc54d0aa91ae7d12a1075b655e48f61a09a7c3137df8e276cbf02fea6d21563112c8c42b05bef00571e1add965399eebffffe4e1b543104f9eb5377dbfb6d11014f588cb2ae8d14bf6e7205bbcf48bdbd905efca2b77aa14fa2532389e0d24b689f9dcf3901d9c6ce7bee5d47126c703738762e4f2f351f433417e7901c0a8f79be84ca506421d17157ca6a9d0908fc8a53786e83bbeab5f0f44cc97209b1084c7958157222e6f8b5e131e928e356758311a80dcd9bba24fa8a20cf5ec8ae2292948748acfb2b206c007b497e69006d5a151a20d50e9d94362ebb661609ebde115b5895745f58fb3fa2924a3abf61447133a0cd5c597b8f89272c60e57f830aaf7bcc34049ac6b7edc69402138fa9371fdb73a291f58b39b3301a8c77c9f9efe0343f1811cec1125629c840444fab6c440e376164281004bdb615ebb6c17c34cfe3511b040feb0dd1f7a57121cda4b47586ccc6d22e65327d2e44f39bbad0cb72d1d3e8188346b7148ae934e49cb6719ab39e4a0f30214a86c72d78dbef6503abbace787a3c8cf05af597c77361e6fe62f0deb5f9417c8996f7da44981a4fbed47417e7bd31ad551ff0e2e55d74518e152267ce9ce96657665f8f09c818cb12342767ed4ce7c05487f5bbecb0d92d04c51d6ddd2f6c7678af84db95a0e9c9addfc4174c474eb2d53e7ff93a000ee14abb17b245b5830245c169013d7875e56d133665e34ba4004fce3eebc96854ae778d7521e316cc478b851fb770d0582b6abb86740155ea5468a611923ee310cc5dfc4294c4e41ebe7e03984653cec58be94133116e0500e78849598ce8b831676f302f9c42cc04e1ede392c012f0363756e1c0500b5badd5e7b9969839825bba12794b67653721b0b201335b0b282b6c8b68d2ad46997a7997e04f6928c128150266b589b1965ed138c651745cd4bebc72fad9d3ed3531a6b12178945bc7570143e809e81b6fc63ccb84eb850b3e3a38cb93ea4cea6a96391200f3f2b85ace754bc6ba7a453d5c77a4432ded08456504bd09ee17ce5b94f646ffad56bd68cae64b7aef559015e127885bef5900bf7aeaa9a36f7ef39b7d5f659634cb13cbbc9cb611397007c628157056e85dfe3073177d0fdf4bd7e7d401ade0e328398aca4a1f4aedea20fd920fd7f5bceea5df0bd9d5b9f3bc474fa50680640bd015f707ef5b680b5797fceb203513d2e9c9f43cb4a9496483f5e919c09f9f52d7807fee95721092b08d2b0ab1b68bb6aa3026bf95f8efbd87d1f25597ffa7b3737ff62e29affa3b93b930173061b80a8930049894fa432c49424c2a6aa5f4bf16a9524bac182f216a190c030c1733c31007c20c3017d767ad6775f7779f739868bfefd9ddfdf473efeedd97dd7b7fd5b8995ef8c335ea544cfdfeead51f6a7479c3d3d6dd44e08a0eec846a34228d24fdcfddf838bb281d68bcfe1176357c8ef8f091191afef7befb3ddd7539a6c43a4db4d2051df812120d95ed7482353f2aa83ea581e10bf21e1995a98e6ceafcdeeffd9e3e43f7dbf3bce883ee249ccdfbb7ffe6dff97c1e2354786447d1b2248c9b0dfad0c918b9f385a99c98c73edf5cd4193887f90ffee1dff7a6081d9877c158fffee55ffee5a37ff595af88ee75e907a7e86865950e84c4b41364ea1f230b37290c260f2d9f1760b7f4ef68facb8d8253f59ffaf4a7bc6955de897593d04d2e1b0ff05507e4a6a17f8e2d0b4d121881bcfe5296b555792d1d4749b1b17fbb1b0b3545c01d10245fb0a479fd473fd4eee48ffc819d6c9a0c2e380a95417d5d0365c0ef74b2567689ee6522e286326a7ec5e0e88fbb68a750de0091c3397dce1cdb774b9e299a9faae0d0337aa452167c8b4da52e3d7846d633793fb553985af0a62f3167d198fe7157a491c19b864803f00dc0bae278b48f1070f6c777e621a9a60c6caaf88d6b3db876ac46c22e1d0f4f1925fff44fff934ef57fd7535155b5eaea1d8f629cf2668461da710d6e840272c7a793e01b02b83432027e6624fbec675fb4eec0d58eac349b07ec3c32d5ea07554d245b3089bb3f1d890ecfa3130ee73225e654086b37361df015eb1c366bfef00fff501b497aa74b3bb68c14d0e23bd66c7c7dca3c23c07ab50db8914b068f53706676ff724a06b7aa0f786df9d9cf7ef6d197bef48f3d0a3ff71c275d723c8d3a3903f5c15a9417453954cd4653daca6e08dc3c32b5452b5e8065e7949d6c3eb6c468cecd05fc5c8d958a168cdcd4b75204b0d42c0c96d99ae1e8472fd47e7806ca21ea6bbf3011e8aa1fcaf8234d68fa4e273b0b8d39973211e5a8ab82f0ba610abaa7032ab3ae12e8ed560db5fcca23d312ce2ffa4f0b5a4fcbe29fcd909498d4a0701212eb1f39867f380307328f6701cc08e29d2535147685d859a2527eaebb11231b9474aab3d10a645ede4490432d4ffa622b23ce337a80cbf9468e3d71b6306f14e7a767794f8b4ec043d28c1cfaf516d9e6d7ea05a772f035f5c9da811d341e92739a04b80463a41bc8dffc4dcaf9d4008d989186cf03fc964e85b05bdae051d653d754e8bb3a5c4c603b9e4fd7f961b5d611ad64170a85a91debc4a79ed5e7b085c7a7d578c7ec3f7ced3f682dc789f4d7e5c7bcbec28f477cfe0b5f901d9a1ea917b70e783380d317effe4f31744352a49b426581e7b58c6c7aeeb9fcee17fab2a9e1c70be820ffe32ba6b77f4fcf9f18fda95b68cf3febad0b30f4cea1ec17bcc9813cdc4720cd86d713cfe98d734d6ba9a38febc40b1d9767957ff65fffcc533d084c032d8d53a783a88053f7f2a3445aaacc097f4b8677e8b841b20b6bdb6503ba9586f8cc976fe1fe4cb781d08831e91a7c4586c8d22d2078861d97d04c4588a1ff8927d3424632a6596fe916c10b9c3efe04af9119f638220c2bbffad431bed3c849dcf1bff9f2377dc29ae98c3b93f851b9b1239deb49c984297c192dcacfb174c174eee0feaea31a006b1ebe66c41637cf6e68bc38380f49b5f7245baa1b9a860fa323af8d3ce793171a1a6c04e6b18ee1a5401a32a382472ee9c21d9807d87ffaa7fff1d1d7bffe754fe198b671f7fec2175ff23b614c117b87af6f912783cc1f18d3f00f7de8577542e2d7f48dc87eb64e3e5063785a23d1afffc6a7dd61f9981067f868909c86ffca57beaa6dfbaf9bcf537ac44283e6aefd9aa6d3bffee9df78f4984684761c6878d31adfe2776e56e8c533507e0a978ff130f5fbe4a73e69df3125fcb67e9df32bffea5fdbae275469d0623f0789d9ece006927ac29ebba1372f765599ba7ee32fbee1d109bbb01f3ff173b79c03fd757de68077f9b87920e37f697dfabd57b4f12359aaa0dc9fa9e9711daf35d11eec4a5d7ca315d74c3383c4749229233f30cf3afe7c231b6da98787f44f1d8175acc93a3a003c098b0c6c0775306b7b8b4b79f16d01382243591cc69cd66b308139a4caab2f34fae903a28e8c3e1c8ec8c0967865710cfae48f8ea3d3fd1a15f1240d65c195e7d38ce0675a91a905e57418bebec49635a3c6c7d480325a7dcccf8eb873771a635bc40346f9874c86bee881dde84e47e5a02f5f5bc2e62cf8b50e5227e36b56bc6b05ddea3462c0c3eaefbef28abf21829e9e06aa31bea90ee9cd12e1af673492020ea1fec0df8cd03911f261950b41ff393c8fe778bef6f9cf7fe1d1efead73e795914db91cf949d9b0987acf10d7e830fd3c2d7b5ee625acd8d855d366878d8ff71dd707e438d99f534a33be73cfdda8f9e4f32ca7f503e7b7a8e4071c3c36ea6d2acff1a18d568b0c0b88975e3051904626ce006440367f381e92c3bc1e84d995d00bafe5817536f9ffa944ebcb8f3a56e5ed0630a74e207eee1856d10c43f74aa797bc3fe124715f34d1ab9de3796c7f5e9baf744437b65438c91f113fab4052f0dbbee5156c1fc1457efc62ef4c56bb23d2241704522dfe072b4319ea0e390e2505e1ea531b92eee6832a29de01d39904e4605ab49c05221f4f0200ddf3887b204b471a97058fc3235e0812a77b05ff8389430ac17eb8ff083920a607a4727607462cdf171bdc1cc3a833b31dbd794737774658dd9edf0a814d924a41b391b07773442465ec37127a321ab91a1ffbb6aa8bcb5fd863a19ef6879ca2a86dcd97980cc473891cba8f12bea1dd84d63e72e4c03798a6f7ee866446805a323e20d558cfe8c5234021aa4477395b28b4ba7e84600f4f8055b59c7d109d9fd04ce5a0a99fe64b562f464d4620b9bdd57e858af7100f9577fedef780d4827e18fd32db463fc049eacf6a8fe918fbcf0e8e5975ff6d4f23dddf128e3737e4c89e1dd351ff2e9d84ccb58023075f63135356e1a382f77b25ec537ac31a90d467d3a2cc7a6e0851ef027f02ad20bba71326be0a40e50d3ba981bbb4661cd00bc63fb910f7b94c627dfffde0fbc4b9b4fde65f47d4b3af188846fb0f4a6800cf899a7d2b619dd463ee504b2fa7db22402ba8b7c1247491a87f0cac18c80b9d9ad862714e35426e716a908a6172c8871a8b7f1d588549bc64656f1716282dda36439020d1e12b9dbf27ed9afe865c9b7e747dfc0e07b8fcf48166f49bfa0172bf942f027b58be50fca68a4e928c528618344033fa4590fd563ed851f38dec401479da18d1d1c170bce8d84cfbdf1ba0cb6bec5dd57d3126ca2e1f80c9dd6896c289846a4bc21cddd9e11855317b24cbc755cca0fb139aaa5d3175291f51cbb943cbba16172ba850692c0b7e4e79b85b29707bbad374ec4f3301cda8dcfa64abe3dcf1a8d87ae047cc2b4103bf89ee45bffed2dbf0fc65af70d6df9d3b9fee09ffce1a3cf7dee458f20d0d8479e826bfaeb980e063c5f9ec2ef9c47443ef753dcf5d6cf3432e8ede41fa8e1b259c39b14bc26d3e77b6c56b1a387edfd44439ee5650aec1b1f3705dd54e9649c78f9f9e77fee1b0776730382ef2735e567fdc7d7b4b899b08efe9046736ce60bcf1c46e6540c6b3ea6b774a63ffb2fffd5afb9b01b3c2d583729de969eadfc990d603bb21c1a273757bc907e71ff4f270d5199b4c2a06ec36b3c1c1d8d48a4fb7949f2345dfd9377992e72776434e359197718eed495030e21a35b64191068aef0b67ee1e986a1291f7718ee82be838bcfd31a195ed44edc3ffaeddfd667c93ee53b1b0dd1d3314f1d24c777445c419ae6ad9468d10355321a509832ee9cc8603d4163e7cef782d621bc6b060fe89e52da6b38551a9b1faa72d3731766cac88e97030214d089864003f0e708cc286f15f3b5acafffb9d64c924fc7e1c1280d92a9d13ff9833f70e3a541a11d3ea51331527352c575a632a4f8fca464d331d978aa7f19fd3ef399cfccc642def6fea83e00fab88ebffd0f3d9bfa933ff913e9fc5371e006c228fa9ebe85a167651a8d9f7a2fa346ebcc0e005376a5bd64e6c05a8da937fe8a8ff286f3cbdf7cd9fe60faf823751436867c0c4a7e823e4b8b3e86507d4827cbc26fb8541d163c7ee4909117ffe247ec659ac9c846fd7cf1b75e7af44549665afbc20b1f5107cba71c98417052c8b38a699f1cf3fba8de00601afd373f99578ac40f399c11a5e333357d523774661d04cace50fb55b2cabcbbb80b36fa1506331b299433ae90c6e680238ed006f8984630a6783488f7de7dcb1b1f1c99214082be216d23870f95567871368ccec5cf0871e7f2e9069188da53379cce94902fc7320a65aaa45148bb729ac139b0d9843dc84dccf42ea3091ffda16379eaa63b1b77da1eb3a2417ceec5171f7df9cb5f56c5f082a88278611b27e3d1e9d5575f338c22a6c6744eeecef88ace4121a7d03fa4e922e7e3780625160e3494975ffecb47af7cf7db7e5d85918c1b12232f9b47ac53f838293aa76e9822312a72ce8e67490abad001f92a17cf0999bad151b8fbfbed5fed2ad2617ffff77fdf5be48c6eecd651feed6f7f47bb82d4790e1264da9b1f7f60ba87bfafed207a4c6790781a30a3043baaaffe957ca180ddbc60cbc60be74199d1a4c1a69229676a6b1bdc18649fff8d2b458b8fb851d02ed854e2f51f463ffcc308d4c3c1d41bbb987c49981bca134fe8bb8ff237b678bc150378a077eb246bdb17346dfcfe82a323eb690e307ce6ef7d46bb99d994affdd0ca19d6cb0c6d692ee0045bf95656cb4d387060cd372ede35be9653d9347afee3c027748a800d0294f9999cc00363dd7f85954607bf731d06bfe8161d29df32b281c11ae879bd5b16c7cb58f51c1cfe536d8dd338dfd622fc71f52af3e22e88520a1ea97c8ba653b1d07edb0b6dde1ea6c3ae43a77a44c07481c6c7f40b63b081f5203f20f7823a71a69dea64b291dd303ed0f9c413dfca08291b7896053d77c34f7f5a8b74dd70508406051feef8ae345718532f7d4e417a78ada4343ea403d37019f158b3744d835d5e0f6aaac7eb2d9efa68c3201b2f1a7d345dfccfffe53febeb53dff2a606a7ed19ddf864faef6843845d416e48f52bd33b3a24df57fc2b750ee0a8d55dd29f680dc76611fa104ae7cc714157a6eb1f13ff6fbdcc06449e519a5fa796f297b65c6cbbfb140d4541e27cb3cb2b5100d290e924fc28219d9c43004c473ff2c247bd8efac637bea13a7bd53745365e90fd41ddecf02fa37ca7f84c603c9a2384a669c1b9f1e5918dd672aa5f3e2c449be5b3e4d0bea9293cebdd679ed1ae359d0a9d26343d4dcbd0f0d5cd865c1148b7e08491be3205f71a8a6787487e55903a6e24ccf53d0d50e500f136be8675e32f63a34ff440eed6affcec99e14e45d328389540434407fee8648c1cfcbdfb2e3f00c753ef3416f40e8e76af98fee92ec59d9d1d2f7ea28711a827b0d95982973bb0e8d09f0ec65a8bf388d052ee06377efa80a78c7c80f549cfe72b979b0afa9c9d431a79b3e2831fd217a37403e245cb36067c40e7c91418ad7dbbf24606534f6e0a34026c816747bbdcc4b6dfe814fffdbfff0f3fafa29180cf4e9a22af13cff51afea673b05dcd4f19bdaaedfcea8f3ca6c0f1697624e1d536e3ba72cda22b6d891b489eafa113a33332536604582f7aece66607cfe070139da35dba09f1409a1b023700a67d9cb37cfeb90ff926ca33310e01bcf1c65f7b4383352edf7b61c3e4c73ffeb1db48f99af75c469d258fb6c47a8d9f0b6616c3cd84df2ff8e847b583a99b236bbbb323995097c24e7fb46c8d6400eeeb4855ac71091b170e6d9d5d5e78945123af5f509e39360d92d18ab93d2319d38727d4981aa82c7bbf002a4ec05356ea21f02ed8691c2c94dd14e53d9cccce1953b52755c975288d85a9d977b4bdfbca2baff8213623c34f34ef7eebcdfce6d892251dd0f7b1c73a45c086d8ca6b303450f875670b1b590bf06880f7da68fc04a6ab8c823450d60f39916fe778c3819f58e246f17f7ea105378aaa88692b7ac41f8c7cba79a811f27c31236deedae8ce0379ecf9a6d73a79b00c1fdfb1c58e4d021a319d8bbb349d93f013ad3d7eacb38c6c0865bd96f51d3bae9e7aca766e220446633649904703640386b07ce55d57ab29a82bd1bee0c40675d4b3892e533153756e32e58f5f3352e95755d5b8f980119b133cb466d3854d193e05c83a96d1051bb8d9e1639eb9b15bcaa91ba6d3746af462e6c0f49132df08ad3172259f36eb7c7c2c74ebfbb9cffda6eb0f3ff1d6033383a79f7ec67ecb8c255490b65d9e6d3f75b6716e3a599d65b9ef7329a393f922a155c48a80e44d4fd104e6ce4ce560308d8e4ec6e257db71535910b653d1986b48e225032ca1528e13b8cb3185e0ae4501f37ca6446c49b36bf7b4ee40c1d7c96e758ebff88bbff0af40320d0417fdf45f7fec60714d23441e70b44aca095d72048a7391f06357b181ed781e2bb016e1ce9fa00f0969fdf0d73fcae97ed66c19a1f2022a7766604c293d728a3f9d021cb69b1fd3af6cd22069989c20c9d4e835a799d6f2a07b37561a1f1b22b7218d126b70511af78fdff8891a627e80904e46008fcd0a4e7870f3600d062fe47353e1813530eefa84b405cac9a4ce98aa5237d435a3019b099cb1e4743cfd9b76835d4fa9f1d2a1590bf1cb2b9fd21bde6c26f1e901ea13f9cc0878d19487d069d47994815c6e44e8cb71379ebbbda64d9fd915c9145d1fcfe1bc2a0fade18796b48315946916def06277d89b28dc4cf91b5f7a73ec8658dc261f1fc4b7d73e71d3c92af82428ec1a975185c4f8740e70517d2b9fe69906a3bb8ca66e38f8ddb77230352301d88bc2e2a824c2c5ae310c5c10b8fb5dbe2aac673454b6bfb7a1f7c3385ea5f69e2032365f18f50874301ceb16110c7114df0a1d2546156350c41f3788ffa3e909a325cfba980eabc43710eeba1c80fddf3fc83340bcc1230b46204633d60c4f68c70cb9bd63d2b0992ec18606fcbcd64974defe2a26233e811d4a3f3bd20da2eb1cfc9fbf6988cb97e82ae943eb860a13c1d816673acc9497f596066007f8b063ca9a8651d98f11e4133a3da3112343d765956b42aac481c61a8f3da11187b5135fff6294c53faa01eb4a07fabc3625bef4a57face7541c877ad623159d8a9b048e68fb22ee1a13990d24c9d381d84164adfbd64fdf721de057e4b12c61aa9a40edeeb689369b1b197259e3563eb24f99e1f3b7bb427bb3857f32fadb3006e70c273d0a5a5d706618a39c4ae663921eba85c050cff4e7bd47d9ad0acb9a2e67180027fec44a45f861e3319dcb7391de598d278e4c6f680cec32d1c8dc9954886cee9edcad18056c6b4542ac505b002f159cc0e18c72f0d70f36686b9a9d464691c7a6e3d28999fa71774616532ba17baa4787615b1c5ad60d04a620ac7f5e7cf1735a9f710ef1c3baabeb41b9a63834ecaffff99f3ffa8fda4e7f47d34c1b6f2a3555f9e1f1e1819e09db4fd88c6d3e32a4f8bc99007bcca3bd1e0ceb6e4fe761fd541e74008e693122333db401628d9df96ebc3e62fa29dd28241f19f6a1eb85ce909186693a6b5ed6b7d88bfcde4446909f19f223216c60106c872ee8e7338f82319ae84af1c849da005de0cb74f4a31fe18ce9c734b378d30f973974f009edc07efad33aaba972747cbf40bd83c59fa5d2d8c80f6ddb8581ba185f658d0b3f63d7f27d8427d283e9ddda6f8c8fc3a55c1513832a8c439896f0b09603a3efa88371a761edd077c0c0defe889198cd2b3084c663bf1b29cf3df82e1f73f1777828adff59a8e7278c681c6e64e2c35d9235111d8d06ce2b28345edf1624225246ae1541b6518233f6602777794626647974349e46024d4f19cd807123e11d263a0cd32c5e3804c69acdbe920eec467ef9cbbf6f1c3a1a0fede9a04ccfd8ee6644612d4760c43c2b151e344ae206caf133c78e7843800e0f8c07d33c14b7414266947f5d5fe5656dc6baa56d015aa6602fbca04fa9bdfc2db3f51454f5c4fb68affdf055dd3c7e73753210189dd19751169e7c5f239fe17e551d55321552074a48576e3ebc9d4027ff35d98fbd99224f0d485f42ccdab6d5ced30774a22f7ef18bfeb2338f45785ec829a05fd1b63d4b147e56f90cb5b371e4dc1db12a8bf25b0ed22b445c6f7c1fccde78660bbf8c4e81a67ce002be71272efd8223f442eb064c135503cfb3b27cde8bcd0fbe248473b3930665cd210ea7ddb17699ccc03aa3d36069d4fcb6f19b6fd318d3f0589779f3439d8013f3a03395611dc781523abbcf3d9a027e4a58fe6da305ea3289b706a03212cc91231a0c154d21e7fde8143d918f0e662be16cbff379031e6abff79e7ebb4cf894b17661f4e8740e18fee437cd3ea42f733195e40736902defdb5ff80c1cd60a14d0a17d4c4b6b8aecc4e953e29a86f9e3a9dabd64fdf8b57fff35efa2c2278d3abfb0c9e8c48d888d87d62323db0b2fe8a3aa426654f2da44a27ea291986753dc5808c5471f4e717c552f587e5347a9b82930b56ddd22cfd34068f40f3837a8d735d2fdba78514ebb82df19ca1fd8436d145a465dd672e074baca14f3caefe471c31b01928d6fc4c46927b94c4033974f7c935fbadfca5c6bb20aaed0f78b2b9418da3b860c42dd85531da4a1371670282399d62054561a4cd61c1861539308992df3c5b2f0012c6da43a2e0d9446c1c3547608238fad6d7600f5babd627702b988c6c2363baff34347473167464afdafedcb27166449146af6cb9442ffd4c97a028406c5c848e56017a709fc5bd4ea1c1c194ac8fa801d3a773cd1a732b75cdef65efe142f1a0fd35ad647dfd516b5f74e473ea342a675fcba685e7e64eb99b5893f55a0d11d9b1915d1e9f9e75ef72e220db01b13ba2378cacb94903ac0019955e846a11b179b16dcbcf8f6093725463b1e60bff8e28b968d5de89bbff88ee78c8c4eee54d231a39770843b5e4c5a190e41335233a5c796f2bbb62717cc65f9e7c83b293d08d0f6885d75bbf223cfdf959719e8e28f04112fc0dd36be781e72412f1cde049d5d0c7105b6a0b1b17ec9a50cef43c1a10ec89a3fefdb69f8c6f1fcb198679e9e456d9482cefa388643aa26ef98354fe5c216fdd97acd77eb69546f68c7cc5882f3ab1d4c85a844b6d5192d098c34dcf97820cb668425989f0ac5f8f44b6d1c9f797df7ae74d71ccd9db8fcbdce81b9f830aaf0909c3f5eb9608385518a69229d8e5d471af5133a108cec86ca228f0e6cb97fe8837ae8aaf5c5d34fff2febdd4fb97d4c47803ea63508c7a5e8d03cc361138829303792b30ee18badac7d388d6f9b2598b5129dfed557f52b969a3ae2bfdac90d8b1313fff25ffe0b77988f68ea886cb6b5f11f9dc8f53404c8e4c12f47b3befdadefe847f5b46b2c1815048aed941fa86b6c07c6b346a6af4cbbb999d46ec7baa45e84f840386d3c514ef8e95370ae7960d68f86e41635722998603b490771ec36603076846cf04da3f4cdc6c77dc2212dfc1a6fb63b75e2e01ad4b673ad7c1a2f0f74991ef13c83ef6e301af0611bee64dc65378f4c8b68b45a328b539c8d9d092be146cd3314eebe3672a47246d22f70eae97df942cbbb6dfec109dded691c2a0cf7f8f906177c57da38cf79f1e71feb39a67fac45986ee541b04620d9c8c89ad14d14e3080ed1f292266710bdc3a84f1db080a47e91c188839d34c25fe820ed9bda29e3ad6e1e07fcd11ffd9117f6ac93d828e1b57c8e42e14bfc96ca1537d1121891dc6ee028b6fcb81e0d99918907efe0a92b7a26c107673385e5f44dfccabaec131fd3b1349da86073890ecf549f802fb79ca9610961f3898d1bde1c785b1d47db435923db01599351cfec247293e3edf2cfe81375ddb4b29f2d212e9be44d74d663f1b115bd292b2cfed8ba9d74306cb9f1f1ffd8ed322e134c870309e3d04d1bf0793d75003ed3452a288eb3c083e254ac8c8fe2db64981856dc51cdf5e6d9987018c1f80e23bb8cefbe99e343ec6ef17e57471a98a491d0f8c5658c9c48a570e60f2766914fe361c1cb0157bf02af125e7fe9f12aeea0d3467c17e67508d63a34203a48aa43d7246e2a4bac5c81c484f83a6f36b305ce68f68e4e963ca987d67532ef606587910f92f29a46ee6e8caa1cc7e2392137853c5b5203d13f4e7c33d57aedb57ce8e7fb7ac6c3b1287ee2f5777fef77d5c93eee1b0afecd5f74a1c3d6e7d130d7dcb4e23efcc85b0b4c2d797192919dad733e61fde28b9ff521e569438b17a3153f7d45c0ae762cf2a73cd20446384eb9b3a5cea15adeae7ef2e9279de7fd33def1c206746063e5b96773e4890f9c12dade4ede2e980b62d0b13e6e9cb6103b4ffc5f96aece96d98625e6c04ff96e6582d32c4e9aa64f5ce435df783a19957fb7f024205d0790be37c044c19d62100ca9018ae968aa2a29a2c5be461376cad8c2f74369dd59dfd5cb7270008bbfdca159100f43c39b6e2c3c7514460e3a1a9db88d818eca193446842cec67f3430cc1e7ce4ec7a60cbd6341f926beda6de7a908f87bdac964eac7035d9e35d16708e07077a661d1917ff643d68991c0a8c12f8c700c8a91ae95c1d499531b5ffdea57f585a6ffe987e8be3168c7b4777a46ad066c8496d018db09e846391ff461538687d57c9a8e9feb653df53bbff3259fa4607ad7b713e8500dcb66f1af1c444d151b0d9ceade0246d68f7fe2638f5e7ae90b8f3ef9237d6c56dbeabc65ce07793804ed35a2a6a17e1636a3766bbbb2892bbffc9b4fdb08e68dfca1a91fcae3c4699ab285373618a6cbf56b5bc0859c16791a3f7e77f95c4efe809abfb3f151a34a0ce2ff4f301da4ab73c430f8fb6130d345ed64d18978a0ca33ad748c12c830a671636074603a506d56c200dc406361ca48e7e5854916f5f0e4d914533a1a1b77da1a4f85d3c878798f1104fc86550902dc9786876d94baac65d86d23e6dd36027ab253c7484647e26d63f760e1b353c76971ce1fbea74658fe58cefa14383b813c4c656dc74d80ad767600d7a302e1b64341cf1fb6f2d0155bd87cc88164bda7f5da6bde5667844487977eeb25adb53ee35318b20e754d6b7b9c4b032159dea4b7ef7779714c2b04a6943cf7faa7fff49f09e95d4f69f9e9e1ae15cb83b83edc5e87dbe68dec5327f267b933970bf8c5a368a50ff8c9131cd3109ff893be17177fab5ccc215e3a5656635008ab9391a982205d99df0783c6018f8d0306602518b508272ff870c7a461fb472894a7e1e47855d612980b3b6fdbdbf2f0a988f09b0264205e9d95b93e53c027d8aad794cc5ed385a928d323760019e9cc5cccc0f7f3140d3facdb465d152755bdafb62fb86d9bd3e9ea148c68f92647e8d9aee6bb219cca8726eb068d9aea0c3c5640a7ace3520ddc70d08f0d063631d848e186843aec46e65b899a92ea0645874aa7c277bca6cfdbcb7cfaee87deade3cbba742ed67ed0f677dcd8b46064a1d3fa9c679d1a17fb5afb0b3aedbfa66f7094c172a6c97e3155bccb4bd6cbd6db06593f9607f9e217d618be577cca4e9afb681fa22b5f9cebef302e4012e14bba3ac129f2964cdafd11aebe018fbfd5c98a0eb0a14a9f30caeec02f150507688c37f12d0f8d663ad8e9c3aa6a447c229b46e0dfd0b22d31e894b3d3d10e35fde521ab2b7cc5ac1dd841eb68952acf43cf1f6b0bfa4d352cd3cd948a51825186e91c0d929db0ada71963ad052ef9d8a63f1a38272a807393f8a91a396b2dbe1da12d82608994691e6b8f27b50665dd047f8f569abef169364657f42030727f4067f978bf8c07d26f6a43a80f9e79ede5356d75737a829b061b2dec10d2a978f8cb3638eb389fa5942f3932e51dcd69e8f0eeda9387e174484eb357b615382ed8555f34a6b8e9fae3c47379794c9b40071c76e217c5f8d33e82be659e384e638712c82faf3b3837001ab800a3c78a0d4a9d1a41e5465bb43b077deca330f0b22bfaa94f7d73c2c05b9d2c1a6d67805822101f4a53765fa8f3ed105b2bac8981f9785577ab047f5b2f4872574f1827d8b0eda7ad0377c82db5701a0c8b79460fbd1bec7f540dcfe2983272b01657cdaac55355463e16e9de7e57a7610a063ff85bddd9aca043b13eaa2c46639e4571529dc5fc8b9f7bd1cf907c2a02290852606383738d3cfce60896ac319cd3e24cff38bcdcf51605f9fa934ef06b24a3e3d29921a1637de73bdf76c7a163d2b95e7ffdafb581a2b59d4e9160231d8b902dfc6cff1bc00583f49fa9b97f7b4bdbe6e7d473e12911fba3e70927ddb2d66ff3f54b6370233272c937dce244ce348da2383ef100b8dd9c157f839d0cdca87982d365ec3a8d2c171a21f9da7296221bf8701992c2922d4ee3417274eabe3f09573655eaa4f87f489f02a3246aa2ac4c1f0701a731fb95047534022319c7ab68c84c9b5063fb739b7ff24fdae4bec08f3510a7d9e1ef6f4ea893d07959e7f0e7e999d63854046b1dd6649cbaa6d3b8418b137c69dbd679f4e0a6f0ecb3ea30da286111cf81575ec1e0a43aef5ec187359e3ba9b9a7a37a4aaacec8415f3a99475f99c3491746b23e1f72454be23a2c2c7e5e4309864f3803c82f45f247e7e06b5fbe29d99779b0cf4d063e6d1a38103b634f62bd16ee696a7f6986519f603af0a7fe1bbb70ca818147b8961b785c520eeea6398aef4d9e3ced8f915738f17d7a8e4ae699ee253cbc70d1b5f4e557194b998351ed5c659328ed15fe501e3e6ee1252ce32a7312de07a3fc0a2f8f1bdaf83a201b2217b893e9c33a340c95f08ce6677a2e9403a4401a681c690491a5b48a706665c1b238de31d4f317ceafbdf3162f84eabf7a0c53398ef0300a70f4cad5203a46113a259d8129181d8d291a8d8f3246460eeaf21903be70c57a8987b97ef0ab972ce954dea0b156e831d58ccefa47c3674aca5aebd557f396317ab31e62bac7ab398c84f8014a8fc65ac3fd9a46bfefe91b1fe8637c757ad65e20f9599a78706340769fadd56b1cb3f25946acc4390ae0323af221994fead02c27e37d4398860b4e75274d80b6b06bdc7262ca5a5e79c009278f339dd25c4f9af2212ebc741b461972e18fd708b9a1602d7fc125a1bf09e553be30b8035b04f1db559fe2972771f96dfd22bf65ee644642e309256a9eb8b093d1093f71ef4b97bb9d200fd1387c86513b68ac15d815632b9f4e468388abc229cf97aa832af4c611e15cfdd814a0a3f123813fa79311248f8d15a6676c103ca7c3c1bed3e9ca79463a52b7d2797ec3793f1a3e7f6cc1333a72a0988ec7ba8f07b8c8c3177996475dd6c2113938de61a4d3e878d2775e79c5272c40a513785dc5e6874626466191b8e1b3aee4b57946b5b77fc61a2f36b8330b07d9f1891a0965d64323b63a641b053e641accb7edf906e10bb2833518df3be130320f8273bac31c165dfd08db866b9d033ff15a5ed9a5233e61679ab22b8fc2aefc62af4bc58f29064ee03f1d2bfaa794eb6d90ab1c7af695cca298c2a50705aec65dbf26866670af36b49cb838859107dfc7aa00c27b0927afc22bd1096b9a985086571a1752beb823855c0cf1e70898beb1f5ac8ec05488edde3aa7f477e318708b2783d430e964748afd592f4dcff4bc88e91aeb97777f95d18111489d4c9d8617fafef88fffd8a3088d9167678c628c6e3c66884d7428d9aa4ae63b8a84da5e658da7228f246af07c5b846930cfc3e22fba0bafd7b38daf75e24f34baea0818a32b0d1e1cd674cfea277659c771548ccf056cfe48457674f14b84caf18c10bbb1851b02370abeb8cb2e22a33037113a1c5fd20287d1123e9d1ea3776d899e3b1fffc65e11d917c695aea9f90306c23da1bce3c7e017ad65cd37bec22d6b462dd24ec615e4f467a863fc0b0c1ec8e45fca8349293e3d651863f0617596958fe974818656e54b00002dc1494441543f7eb90bb760a3d5a7c499a388e0048275cd976163731a3cd285376e391a6dd1c29b02f8b326ea8f50d07879a9914ed6105edb28e0380c1e5b4e39868a3b3823156ba04ca34066d3e21dbf9ace83691a17a327816f7ffc5dbd8dcbb31d74829ed8f6e3c8d9ece8884565309d1442a65bcaa30b3c3997c86302d67e1c85e24c1edf17fcee775fd16f85e9cb4c3cbb437feec6529b0ec46911e2e79f8f1dc8e5d3644c4979b340fb1a597b89d241e58c7a9cdc6033852f637134891f33e75d2a46abe798ceaa3cc7adb25e832fb209341214b08dd44f000057087e7189cf3123680fd1175e66e545fe4c377f957fa5b76ce97faa8909b12657724d5137d8b7f1b175db82dcda53dee09b7e1359c2a96f71cd1e26378136730358b63ee12e208d617635eecc9f69585df8993b4a10aa8c33f015dc2510a1893636e800dc8199327a5da3221ed2bead861a3e77f5811f7c2a677282ecbb121d97bb36d33ff8b26d4e77622b9975191b0dfbcc60d4a163b1d942e80e22d3b986a82c2e83133c8d541a1d594b72ca9fd750f8c547b6d1f9ee212f83b2e6e2847f6e1ceac0acdfecb9543a23186bb26cfd338ae506c248e3b784d5c9d80dc457cf7c4067fd34c2f2e55ea67b1c877ae123fc56d9873df5c366be1ecc7943d7e5f8045ff9cfc64c43c22005e0ad57e2c20a37602e298dc749e39dc28a575e273de96b70f900cfd2d25374ea73a56ffea40596bcb4b25de151f9e557ac1ae069f72676f3ac75e3928abb8937bf82afda14ee2d7c1a337a8d8ae54c567ff71b7e3ffc8e60b30cdf8a847d2a07b979439ad1045a3a199f0c38479a2b4f57d0e8987438576df0736650273934357afb6d4dd50483270d9e87ce34eeeeaa859a4f54934ac74e8a6c3a6f3712788ec768eb1f43d748c5b3b51fe8a1efabea507fade34a4c4fdf7c8bd76a1891f3ca8a6f20ea5c7dde959680c3e9a4fa154e75b26eb8f0c3ea34103a198f0638018f0daca33ec98f5f28e660301f427d4653ca1c87ca4dead4756e69634fea0a9ba64a57e7a86f1b837386c2f133b4ce4fda784abb2eeb27e57738d3077470ee2b6d1b041bbe96e90cfeda37044084b32d0c2444a2640d26b556b8caa36cd3c3db226ee8a2ea965b7f3486f9e61151d09c72816a728e4271608d2ca1e3552561085185348e32bb1c9c06e308017b6de8200367fdc19d97f5041d8e1f07fcc53c48a5bd11aa13f8d56b4a4413d705873453383a99de15d33a843515eb210cf40e23273f3495634ac7baad7cdc01d411505255e3518f674e1ce0650dc7e90c9e473152f1d0978f5cf2e62f273cd8f1f3ec4f17a6a4d5d1bba678dba35724d5063a3cdabe277b7fa26d7ca68cc8a373d19c19593fa1ddcc7ffecffeb99f7fe5ed820fd857745a42a7acc863f4c5c6566ee5908f57129b5097ea08cd229ac2d2928d5f773c288ea8cfdb00c456d98fd8e183aa06df622ff9b70cb65ec3c954d4fb99571a1d09f03979610e3adc4a03a69042d3d6aef2a1ce09616b2617be8181535ad2183aaa2cfce629ad6ede5d2ca0711dddb870e2fb028cc7061757f91b207a620bc8fa03876d7c1a94a76bcaf34d07d6353cffc931580820ac91e4af015e611e1d58d3648791d7cfdf18120ce6d9925f7b51277363d6d4d24138347cca79484c47e4c02f0f7c33f5a353bdeee75a74d0f5f11a19c4c9954c77a5876ca963eb03a69dfcae32bea08c245f5e6207f1c37a378bef4ff0cd454f576d26f6e4fd384664a68a8b179d6902a91e07c2c41e611b73ed63e41118c9cbc30a18aa0b821a06175871cdd768b9c1410b09a8f82fd4bdf9011ffd26024bdcc401403a0be2e0bff898e77002785f18be8bff81630acae109dc31f22213b94953a8d4f0da3a8097006c8aad9f5a850aa253446cdf84027f8c6f8647e1e113deab93d14ea928881aaa50f337716d3880272d365a84a46d8e4276068747616f7e6834a3a1f25a0adbf8e7e60704558978f1bc618af35004a05ed9a7933d9fdf097bfc87fd5a94d67cdac1644dc6e84487fac563e7615a6d52682df583ef7fdfa315dbfd74484e8940872f10c108d24e6579962ba982af1d2fe1f939956888d19be929e7247990cddbcbbc6cc97b61fcaa0cdbe9d9f113330c8140c132f9d54a781ba2cb94b9fc848d7ea863dcf201aebf455f9a2977193cf9034620ab6850863ef647137806cfe8203794281cc4a40589db2897a8b127f2843c79f43a7572f9b08aceb77c5168482f05955bf30ec52f989659bd55b6e5d006a3c1d67b1353145a6031387602d70c65a1c2436123277fbdae8e348c9dc7c0412c7de0a92ce4061eac7431caa2bc47324601edbefdfce77a81532f72a2dcfeb0cea145ad342877118c8c51a97c762c1929f65785754712126b2a3e76f99d57beada9de1b3e70fbbff52b8a1ca6e57405af83305231aaa15d4687e8b84fa19057e9d8843ae033656344c5163a390fab3905c2bb556ca5f3f5297601f90605d359b6d27d83d168ca68e5aa396cab3d9859dfda64e118d7995d569c2b1f94a5acf562e54b3bf2285bf493ae941cd23e258e4c1140ee8d03d300e03ff2caadb1042a09dcb2a4536333b1452004bfba3ab6a3433b6a1b8d32fe85822974352eaef2a376d88219c08859e550987eec899cea52b5869642df4d456110f52ecb821ed2430e383a560593adc060ad2865308c90c6611c67816c198363e2e24f19f876036c9009922e8c0afd054e0abc8daf29631b6d940fad696c4052667493c716469bc7bd85cd8e1b3b983ca4c5234cf3f86594af7d2daf8c302d64678f45150d1e56d807bd0068681817cc41aa2b571dcaeb2f772c9e51e953d2fcd8831efcd291f834991ffaeac815a314eb3f3629e87cfc84947d2a866ca8c05457183bdd067afa99e206704feb0b2f0c07405b19f13b4453572d2ba1e50eb5a24c41274f9949f1bf02f9fc4f7a3449e701e108c3b774b60bfea00c5feb3924c67311ba03acbe930622b8db90cacc4f7c72546fa8950fa9af1813bc40c727f01b3ee05b18f4e228fc51cdb276d92ddc0ca01831a121532ba6c0387ae432ee5bc59b418c3c2b7b551eecaacd10444d048f736c18cae96f8461881521d21fb874329efb3002e038a68afe0ea3361104b4d13526a270468cb8d50129d182726fe3f3352a7500761311c8e6023b79ece891cef45825face8637123c748907fcf507ff9c22cfb40ff9f066a3e6034ff3830739c7c83736723a449d8add3f4e87a853211bfbb0d583231a2ab135a5887fd19c82fb6c5b76a2d7e0924ace20f3b4dec98a215212c2bf0deaa0133f971d75045578eb3a3e3097536900215cd858605ac15de7c8d79fed3783c0696f95315dc279e8135a3a22906059433ffce3a7299032c121bff1629d21869b636d520c0dbad6e720998f8d1b9218ba70e1017eebe44c9782f8e4cb4315979dc022b9602e657ae255b996c1cb2eba2871f2711ae348082f671879289d4d08b6b579f6e483bcfb25e01088ae32431eddcdf3700c15cb33231ee8f231183a1677a93ac436688ae68e851a439b6a9ece953d7d777ea676eeb41c8f62faa7d7f5f951024e8770269193193e6e25b939c132d3178d546e488c58134efd698484e5bfc1b9134d1d9dd6dec1194071e05c7b37eec83bf801312e482e1607df7daa5739aa7cd151cf786df81d3c30c9bba763dbb90b3a6e369d8b87f5f6fff05384a8e4504c015c1185ad3301b9b01d77181ad6cb094b7acc108224afe253da6e2be542dc1bc6595f671a1cf285452b6fe15384514b5a00eb0afc54208c282eb35666f3b7d86144599d4925368d583f98f62e9a3627f4ca4b7718a12c6f1c721b90724aba4d338ae44333bc090d9fe80b9677eca443f4cd2855dd4164fbfd397728fdcc29d33ffd18037ffed1038d8e74b8a7742a8373895e4fc1547fe5e11867236bb45ceeddca3ce8f3f211a979da076414285b7515a35290c2183a70e3026fdec978de2aa3cb3dc19a1f45453b6bc0691b654ee13272e85826273f30c7abbee29560f57a2832026b671beb6e0b701f6d86ffd99ee2bf2937dbc82319f4e663e48d4f8d7fa94bd3a5bda0c3d66390154566e2a631175c6f7c1408e03694f1052abcd250d2f47dc2cf72611e8c700b0a73ea23eb1a3e80f31e3b8c1acd78987b1be46a3ae75a74d2d880c590ead083a0fc9cedf31fe4cbb9cf6963037dc54de6e57894f89830eb375ea8643382cf9df1c9343a14a7ed3959c15bcdeb1ca33a1f232f66f46e9ddff71ee6a7c2f85232b65e532878bd0c1581bcf186c6c0cba534055ba28d2a2471f9dce00f9e61b6fb283d789cfac00d5e77dbc4adbce4a897812b019fd5d194aeaee13552c01f7b17292c863e4e0a2e55cee08aa788dae902d9570a634e398e2c50ac45cb0dd025dceeb3f16ccb6779ebd31cc777f7c12a8178ed2e3ec4b4ce3b1d5ea627cd29d4026c9f2ec3a066bbcc192a306ef3ef49ab01f3a6f45b8ff2d96e7eb8fd6e985d3f7bbc1cd390ab1f3cb19d351edfdb603ac7363a9d222366a6926c46d0a998f2f9277ad4b9f8fde0e7f4109b0f9f428f3edc00ca1319deac38ea0e2d92e51a9dea9fea5f3f19031e1488a969c993468842ed28b7c84e635f65a201bf7c4d785ee0393827f88a6f8987dc69a14bb7d0c2cba061153d6335e9b9e10e1fe0b5a5fcc0b2cdc30138e885e311bc511ce084941f5027ebb79443042ffbfc608ace09200cae01f7fbc6f425515cff5d7d06ca897ba6970b2e7cd2c9545aa64739ec56b68e3bf14e010bb1097b480e81c5b0c9dd887c8006e3008f667aa83b1b1dbcd0d8d75ebceb2792387248cd100134346047c51a3702d946f76b2a8af9dcf5873fcc0fc865f78fd18a737fcf3faf13f77ae8fb943e5bc614135d08b58d7857988b5c63a96ae1f1cff2a78cc672d1a7bc8a418cf6845589d44140b92214e38cb94b4e5e67fa2485c6a4b7c0c5bf74b96521a2d60c816447626c5f752fe81dbd4d1e287c6f545e4ac05f1ca7bcd6448f29abaed8bda428a96ce517c5f69141acf1a7c4fcb71f116f198816f252c764d56268ef89eaa7c6a09ce92bc9a9ca5976e7e3a6adf4fbe293106135fe4c2f1c6c10ceea58ca7ad88fd7f09c51e1c133a81c16cea8c12b2f6ff92de9d95a87661c1f23715860312c0edc0e489e337e2fbdf492dfa7e2b47ad65473429dd758e621385590d3f6a84cd34ba87dcd9ff1b6397650165db65f8abf704118bbb7aec1bae6afb476e70636b5ea00c092637f212a1d1e992e8b829bb629c1472de31d6a0e463a9b3363eed6b7f65743e5cb8038ff5d7b96010c7f4f7d06b698a69aa903351c305bef4e5e2eb649b0e2c5fed86d199615a2d83798ca801bfcea1f3c600f85b3acbebdc6276d64b2859fd42a2ba35381e2b40ce40ddbe9cd84541a9b699031ca572930cc43658c1e3e62a5f50e26b326e3e76fd8cee76c23b4fc0e34f8950b3d32c425a98b7390cbb4ef377ff3f38ff8e54446447687cac3ae1c1a47f08693e2d34ed2d5730439bad5c3d642ba68179d594e2307e112e0036ef1c3f7b6a25d0e1ffdd962f3696e33ac4ee5459cd0268d2de878578f8d9b1b54e990089bc55b05a5367b659a2755895ef38efcece2820523fd473e8803aabe754fb51d65e31f214777c860029f252d79242002be47a8eefeb0108497b06d877eea5b38271c1efc15d63886c0b0ba6cc382a3fd8653298055887419957963b38450a1b0d2196867d6e1c4a95ceb811384948788b84bffd8fad654913f3f3cf6e687ce08aa93bdf794a8fd6c29cf5cb28d6a0ed22fd272ddce03de69df934fd639db69accf84b0dc5d1b805d43edaa2f5a5eccc29b4ff9ad1fe1513ea53fe3961197df594ebafc1bb7528b5f1ea5cb888c5c410e3fddfa8c8283a3fd068721306d18580ec9818111eac1858cb2092e1b7b521ff7e3816e1d4d170616415e84b91f0802f9b0707bda597736dbff80ffea23e8d31647ce3df8a71f5d1f217ab05ecafbae5f3180a076eb68304e01c09b0f231a00509129715b16f8ba9ecac71e544d98bc677baa15afc7e8641a697856c6cb94744bd664accd682cddb88897cb691895af63606dd02947d7be235687a0fb7dd4b7acd022a17438e0bc432f7cec5566fb65f3af9f16ee24c03dff4e3cebf78082c56b7cf22d3ffc24f62b24d96be452b871ac7d1afb92bbf15741d0a034b11eef3b462e30bc60f2611c0e672742aa492c7bcb0f2ca5705a82d281426578640ce7d1959cdb8818a24b777e17bf819df92b5e6d407a836193dde5ab54897eeea13e05f94c83ebef2e864b9800dc01189599b8f0e4aff837791ab1feacaf78d8f3c424222e309c34b88c66798193531e39b8cbabfbfa092f8f708fdecb540fc2ea759bae7eb01cef6f9053c05b567d1ba3c768689ca6cb028ec0aafe828facad534a8c6b9bb73e4b9650d0e3e4e5f4e0c3c1e52742d8da769227afdbbc250f3678d84c16792ad3ffe4ef612eacf04d67a9176b3b3cf86726b0743a79b8059f36e342f142d6b9913432673d3d58a6eca39972d932547cf865fb59d63023416a541876f7db157fa6ac69e20692cb7665f694ba3467f9a9129d0a3bc3abf572a89c2dfc8580c4b3d4f9a851e2e4725d74a239d3f0a8a9964dc68a6ff8f28c74f368365be68c6854242f70721c8a914cef149bff0dcf51e4944b1a1c9c5ff8a089cfdc30001c3646357b3832062f2e0375db565e0fc5c5b51e95a7b8f0d22ddd2893cec8e26fe942dac6fa32f4c0843b8a511e3ea60a6b034313bca4533857d1579f8d334cd1a024035aba463bd3a26c6d0c571a6509b75ebb43a66ce92e06d681d8bcc40514649acfe605ffc51b06faa3943ae64c2a32ac23b40a1325735ccdbe0a0c7cf1759e0e9bb0e177eb0e450f534bb2755c909dd0d9c55b07ed225294393293ed706cbd4bb795db5cec2e59beca605863eb11e51fd7f941bfc0a9ce066f4eccfbd3dddcadd8fb1896f039653badb29647e12dbf29f0a801d383af3c53bfaa503ce2ea6a9a2928ec2c275d9c150b76e2165edc963546fe89236a9b80ba84e2b91eb695cb07c5736cfba04addc017f872b912f809d68d9383e66ea8ecea575e9b269c2c3b0a9a49933e2c200858a7447286e9e27a1b020638646edd9457fd3b3fc28d0f0371b07dc31d2450aaeb70ae24185b6875dbe566b62edbe6055a75b021b08b265b5e6067bef8f9c68772149690c222c764f4db154739b8a5692c2084369b24b43833ea40a58027145c06ba4174b2d9fc9875d9bb74329d616444e3c33279d5044ea187477921d750841abc71aaa70b84252b4cb777a6543274a7fdc1eff52edf9634ae9c60065a7ef525d0fbd2c51b2ab0925c9e83ab60f69d3c6721f17f69c6848376580011292478a99c5b57959d6a59a52adefc83b3cbd4f2cd74aa720b5aa97a61e489b4922bcfa8a3b4dfb9b37e61506a72d19bc4d6275071b402624ed920b30b9da0d8bb26cd878a6bdba5495cbc7186d8f637ddf8f409b0d6e5157ecdaf131fa7f12771055053879b0d2e4d63d7a64aac32c8a3fb913cd8a9b9cb21c8e21faf97e459d94c0d754a9ee9e2db3ea80ba32d7deae6802cb64e9c46d69629086219dc92ddc9c127b645f6b27330e15dd88d9c4b3938d772f2a75f6e79c975878e29dbfe0fafe826369566f7972c7070280e92e501506273a33c1555dae48d4de10aa74e0b784f22fac506a7c3fec626c8e85cf0b47f008c4f62eff6591e0180104ba64bd906518f6f494dc9c8ab1d64b9b9c6bee02c5b65a6cb75b11edba1ce5b28f4e8a6bffb7c50f83586169856a538f336dcc728aadce29103f70efeb24008561a1991e36bcb95893f52e68f9db28dcf4b8c9a26f80ce3accb9081c20d4d3f04bfe235ff508c1e573bc8e79f0a0fd9f7f1286de2f8a43048cd0b7eb51d79c328acb18f7280d6c6a5b5b33c866422fc519f94dba6ddb82dc30cba576890e5bc6057b9e8006ceb3b366da64e51ee472207bcb6026aba3b7ecd138f00eba04b4c213e02fa6ded5b0024f4604bb3c10116b6601a6ec306c30fb9951e9871aa0799919df2f826e0fa0bb97743f1af3198c0f859c8617ecba0047759de42ec848374d189af2b13fe54aae544688dc121569f8b0c6434e35d2da68ef06177f1e77afd9f2d78810cabf425478087d2c5bdc6e0a3f719c8a1e9b5ac4db2e5d0dca1bdf02a0f9b6c315b1eb0ea0b9ff82f1d2cbcb9de17ac9d68a1217dd15fbc228f98f2db808abb9cb2f2b3eb8dbc75298fcd67db249865a5ecea8b4a3d7538d32d870761d12f058391ecf61b50f319ba60d161e28974a353df5848175dee50c2368a18e9d010a8e3a46f7d5abd1b57d7e61b9bc971295e41335d1ce5886ee514cff1c9f46454878074975c907a8272216f57c411a193a96ce3f3bc4ccfca28e177a47fc6f12aa68c3e57d8ca87651c76ea71ea07cf3314bfb0f731d568e5dd1860d3e577e6cb1b2f54975670b7a8efc737e721a751e42f7e2dd77a8db23ece280c795b3728a25f69930f1c3cf22d2341c76d3e317ad686966c9af217ced4e65ddc69d04374dabdd22374899efcd61d5b239dc89a0a70bbee0adc57ec1a7da05a7c4989ae72290b3fc1565af8e61dc2ab3da1111584ef134a5b1fae3599e96ce4764e91288be1f7397e4bad11c5354ff4c21278638481d70b95cc1f9d8c4d10d665fa72ae4e7cf0111bbf9d2c925b5a981280368d985b1d4f838d7e5c6ef4840b428f70e6cba731683b5dcd22fba44b3a8de584574c75906b6c33f9a46f7589bce85835cb3bbc4a975c71b69cf0c75ff027547672a73d475a6ad4cef0dc7a5da789e5d378d36d9ab3acfe680d1a1f798cd4a3631ef8ce4d00b0477171310a6d5549707dd9729c6a99996dbbd1c16d9284da8e4a9cca05cac92fe62901ba2504f6d0b5b653ee278506986f9817a1318838843c71d32dafb3c02b8cd86969e5723b018c09cb2920e8ff1c12e6d73739f94187e337b75897f196b41d519e6211fbe3b8256b589ffa00aa4e535c623bec4ed9420a5dcbaf3c8386bf78be12bf11a3d7cec383bc8124ac0b3c4fbcc8c0963cab495970c30b26bd11998be8816db940c90752fe81222c3ca3c7690bb2af36363f2c5779711b5be2413fd21c9d320a870eb8adb142b12190a425cc665946093959c1bfc7120707fced816402c3cae136ae576eaaa01ef2f949e385067a3a9ecb053214dba01b5a7008e8f67ee1b47f6de19bd89738a24cea986bfe6442999d37c2bb64a8a2a56dec1b154e16beaadfff68059e2aea557f3eacc31aec1d7fbd2a3f4281d538e094eb7a2ad3894f079c3a51bc6c416e89955ef083c72a370c1c73706e54575a40670edec6082e340bd7e6c284bfb3e648170e5d7c9954af67270137f256a984d46edbe2f2969e99a175517906aff4a5aa4f0a272eec8ad3fce9efd251765ffad424f4e3631c76f8c39e11a88f5c5ccaa568261696f2e01a9ccada00385aa02f338a256df2cb459a880f9c261475e2dbb651a41da73cf690f64846318c471363d7a9c9f8ea86793a1228724f27a25c86e2a859fdc2214e206d13b8a8c7d9af4094807f0f0bc3d79f88d36876ca803efa91ba2f8cd43a7b1c16e311130796e743f98e16919551e6569af8d86d572bd10f5bc0c6d9209106107865030d2c3a9133fae8be4ca0204c9c3a2f27afcde5c4b84de7b59ee815dbeea6a1586587dccab22523acb0c6573d4f3e6e67e6971940ca3a5259681c1005ac436d6a6c39cbe52432164262e739a12404141b3778a98341b844d59f96db11cc03c6e099dd6d852c0eb66b148c4d29227dbb26139c061761a1a0a190473021653b06569a36d62826a3426c1b8b535e361959e0c05d317f7d28cd2608b87cb69b87d23e2aa5ed7d6c84a4b2907f0de0d079e18b872d8214a009d6c332634b3726621fba000f3d24e5b1e56e66c105ab32c14f79e5a0cbe6595ce284e8569cd05656e84e79e855bd930637badfc2b7fccad90768a1d932c22758e1d1b4b09c2c2e99ad8d522aa691152f75548cc6c34dd974b4c937329b5bdce688d180d8725c305c5420cb5d182dc3d034019b3658a3a3e0e607ad7100847aaacd4c6ad3e9c3d3079194ab6d171b0238ad0bf2773ad95908c299afb01376322c7cc18e56dd328c49ba2e51be4999eef7bed499f80514020fa4395ec539359ea38d2fcc23068f65e34abc0d8e65283ef195334f2e850720778e9faf3134d53734c12d5d62f8568fc629e1ba47b5abdc8d436aebb4f915b67c3a24f527f1b5ec96ebf8424070adddb821febb62275fb9d8bf436cc3470924f427e460919f9471a67cb041358650ece7c28fd8e5ca87924e414675609cd86aa001292f2eb16d9a32cba82162542dcdd238660d95b96f5a415c7ec08561fec6fce597d64db1d674b1801d63d091db9edd40a5ae0c290cec201e8ae2c607345652e34819ca93fd9b77cbb419c2ee623e1197df2d5b34c33ea3cd0898aa482e0851db122d0b79f0281fa7872ebc427736c0f8a13ce01ebd9d32b8657448a0f820b1ab76e97aca0607244d95bce1b108c6efeddc91566e8dabdf6dc3a95de1bd7665b7326e4ed0f6c7db499757794722fa543ff846cffaad38d65afced47c71ccea6ed847edf40873bbe50d1b8c43758511fa2272d5e467596cb6061cba020c259709536cfa183217659fe30f2ba0ebca1df4277ea8ebebbc8027e69f9c83c4948af1f66c749779d7d45c7a8686803ee492f0acaea49014b67390b6927dc90fd9c2ccfcaf8dad4da61d4f332ff08f9a0476cf5288f54746de8748d46587cc7e864a74307cdd00b561f44d7f28f11f04547f0535e26e90c918b0f5b0edfd04442f99de5e197f2dda9921f2a093c7d0d14f9d14199aa31f0e25a5f174291101d9b1329b40af6013c8717be5bfc97803a4a6543147ee431dac0303543f2ca962c59e5e19d4cf5a18e08956909e3ec514918e3cb21b6541b8074f92e0ac457e2d5dd43cb377b7080ebba0c47aa42c45b577cb0541ee1514572a68755cfea1f1e1062db6d7dc14b6385c2286ee4e35266076825cfb2330d3ff3f4250a57304a55b16588390a59652893676539f5d143c17c87912963f940125ea462587418a18027e0fc9461bc8008b6bdc0494693ed02231891b21e070a214c83bf6360e1037ef821e8d465d3549e891eb85c71ce3ce9add3556ef2f571d95ff3c0d370c027672f39763a40db728eb2c88e2e221203a8ec0ee3a7719d569bf3c90b0a482998d04e78da188c41941cfe11ccaa72c95bb88ba63d6cceee0fcd8a152c9c35fd2d6d6c882911129ec04d03fde8e0143ae9ef268c9dc0968fc4002f717440f4b704cb6033bf61b532c5b1036451655c49cab9f88d712e65290f152aa14b373f8831fd676fe9c33afa9d313f2fab20d41759b331a13b56dd09ac5ef0af26cb0427d259711ad936a2e2a463d63d9575cb2bba4311c7c368c336fc1606bca11556df00277ddfdfa649eaa4a92dc5f965716ca199e69f7536f0ae9ed563f3ab2f279ee8d405dc3b9c0008f70e7c334e0a3d40f25ffcb001b7c8b4bffa6f952cda05f128b573a841f317a2f441fd31c1a9e60b03afe993c74369fbcbcaa3351f37a532613d2de9c6517f0bced012429f9cf9e912ce292b5fe3a586478dd097874f7ce858150fa4ddc9c4876f30f20381e70983f04392a59909bc6d2020054ce2afeb36d79d2e63aa30366df1454152013cd2c9439b6954cab812829374709047fe967730ee5eab734beaa7c685178f181f25a481817b96477629896bc3b6bdf658cd5c4c50ad1f921f24d5ec56210d587a15e44669043b2275a2c26004069f25035afdb5bd545f3b72985a2fe134803f8ede7c545819f0ea0bb1877996515e5b6313dee9884bd6b2ecfd6b75d984fde8a8ff7a184d5a298556d664ac5bcbaee51b2e0ea20fad29738125cc914223209bd617952372a1041367f7bd325ee2ecf12aed306a5dc62fa9dc1f6cc5a5484e8e6a82eff21bbd4d6125efd00200970e5abd1befb2d0068ff4ad9cf864d343571869c2c9f34ca774d70df9adfb38af488a4b9bb8ba986ab0a2eb548161a52153cdcf7a1ac2a9c2d02f5809a0b53a53bea25bfc682c58c1b43957100ca67b0569c1c337124f5d0d598694e1c683f5fa2256c0539868fb115ad5cfe058fcd261935c4083bdcb6dd46272d3755d94ddc522880e636a90e7cbfc6882ca0b33eb03bfa2e2b34d6b14803281347f7794ada3ca4ff874b2f545614633c1f864f7cf3565e41371377ac07b7834ae3e659d3cfa3745bc3375f8a68fbda7b636e390757232b7615e5e27ccb863f896813e5bf7f283fefc63242fcfc6e51d5e270f6ce22f53e6f204b6e5aa1c5daccff601b8ab6e6e1da502950c6af9380bf8c4257dcb322a88de9d8532feb6a094dfd0b470788b67d76cf64b28369d686fc8291916b27afe153df9d38f2989895b32b25bb2d82de02d1e0a6c25b68e9b01b0bd852ff8a9c0464360a623f4511ccbbf863aba313c8c33c2c1dcc24365c795451062a98ad5acfce52a3e3cfa847ed0811739df7df76d9dc6d78f036aca5839e8d450589d6379f286db4781459ef80a3e6d87ee3efe1716cb5fb5b771e4a7c39846aa56c7f2280ef953f66d3e36deea73b59bfcfeab9cea92bc6acc5bf08a799d5fc6f70f792bbd59034e00977fa7c3a6a5c54d071100fd193238e49cc48818629c00836ffb41b25ec0c8d0da4cb950970ee89222d3e804f97446c1f3df3cec637172b052931e596562be941b5e287901464e8be1007e1f8f5ceb8ef2e82f0ad3ce890f9ce832e0625c637ed9705d1ce8489f74a36bf828e372f082bce6ca1ed2e971c81d6bac8b60d8c7b31c961f7d81939fa0655d960d11985d0312909e501d5d270b6c2de2870b6e1d56baf269dc72f2274ed32d6fbe748d5b8e6dd1692965ff15ef36be347017d25182b5796d9daef2c91b0f12d221f5f5c46d3b388adb32c65f5322d9233e00f357bb512e363a353283096fd7f1498852e53470838c3bb2c0107f734c214256a179ee6ce0835fbc55acc4127ff0004f2413464f882a5749ecaaafec4f35ccf26dbdc2c0769a574a497a245b480723606502714305355fdafbe062b1c2b2a1c0021ad3b12c1122a5a419a8e13fdf61641b5f9d2ce160bea49c0918072722c94758756ebe54d870b583b2136ebfd486122a067ec5a3b8f85be64134c9d05d4718b6ead1a7d3bfe8df1129a4d5b7f1fdbc6ba73d7bd1fd3e7bcb455c9dbccfd317362551ac7a9c427853a7fe1756c13b185276163903007f0e1e3763fe51667d293f8892a530ec5b14dbc63722371778ee1ee5b4675690a26fe50e9351c1726b5763d85cfd577c6ba27264fa5855111b475e5535ba2f36f4864b8458b1517cf118856f0c0ae3613877061b2d10e2946694f2f73efcf52afdae320fa51fb1bbc8c98febe687343af44105f2e870c24ffda2c2b6adfa9e3875e243b88517effde3dbca1857890d0d006e09d50578f41fa71461e253d7d81a5f568f0bba788daf55509c2d2bd827cea21f7f3a3f7eaeba95db7a5b0d954a1ca32a6bd10387cf661272f403095262d0843b2243035cc172c0d31fe5b603fc65637cc70dc26fd40bde7fd01b5f3007ebd9760843fd8d02d523884537d4996b5fa83f6ca275093b7c71e7ece2c9f49aee9ded0edcca5ea131681b3f79a1e1a82b2ff0f2c7898f7c51985f59e17304c0798173bd5b76c8a32c619ceb329c5178e341bb44d0dbf11738d9c2ebc07b5016a87cb63eab68f1c1ea86c304eac1f54bf97df49b26f4d50bf815ffaaeb43e5f7c12bc76aca6d3738e3d2d39ba43ddad8989418b618c587e633f4a997d881d1273fe4aeea3c7890349e2e74bdd2c40fca190e1289f0ce4f5ca5957155ab1a2617c6665e8ef08fcec622333adef8c285b797553eba9c76ec8d8f5b9a955bc40b9244e18d2fc52bdb06d1182b6a885d406602bcfc2b988c667e5696ed7cce33f209825f68f383b38ce7142a7c0f26e61e8631f42cab247c17c7562fe2fcc5c9854351dcb39636ac3cd3c9b1aebc761a9c0dcff3be9d6f99b1468fc575e9756bc7a91f69f401a37a6d1dca2971cb1774fc40be34749ca6179e11f0c0d18aaa9265cb9ee1511a78986cf40301af2f1d2eb6195e9e65728d2d3e48e8627ec241ba3510009867446a3704d4b02e26338300c104993f05bf02849202f89fec7231f6156774360134e04f19b050b454e52a7bdf91ac8eda64b729ca71ce15ef3e78a71455ac0a11db457843a147abfa5e190e7bfbed773d5dfc998e583d3b1fd6b9d524b95581ca56a7c6277e61d113dcd29f58851d5ebc5b2c88b41ffab318985ce3b2caa3bc3a16d6fc2ded3d0c0f84fb68287e88e7891f9bc72629892472278e1587e103a11ed9d3b7f27b8000708906a5f2aa73f56afee4646f8c3f475b4502b800485bd3d4c5c8b20cd283775351a23febed54afba558796594a332a74de6a30c612aa87337aa2323f3831d99bc886a2185a2c05374a1d81324defd2a420433062dbc12871de25e42e414494675dc64775325d64f4e20728589b455e2dc55155f0c2eb816cf0434727206047fe48f72fb0e2c711a72cd2f93b7538f177da626e74ad4c4a4efa60febf5feb9152dec71318726f8260b5a470f04a9fd294acf4d50d2a5e5c57a2dc26160df4e6211d9abefae18e7ef0164fb35d72cde510903c4d3d2331455184f6c4ff45aa12d26e36be149338757e8b3d048ae07386f2b48c6b2162a5f8bdd3c5b322422c56d5cab1c494fb2911709516f3b363593e86dad810bb22c5c84331f8fcab37514e5b8cfe24813eac8375f93e3e0785b3e35659a8605ed330aee996139f015184543229e972d8959dbc2d6bf3cd6b1fc1cf6e6778411f071125fdfe7175b0066303e933b4e19567cbc85396261628b005471185fbe8823dd7c12b2c3e29df711485a77f4a5af82aa31ee3d741b9956f6715191909a78d4d977e21b921092a229fa81f0478e003ffc316ab1cce24d3ba82c3332ed7ad99a8c4bca6ed85b54a0e9b47c68a4e5f295d7faff22622fefe4e761a18b5a39ce546e331427009297ed3ab42ad2717541ea5c19fb4e95004a5ad5070d239055096737a7c228e87d4eff877cbb226039e861d8b96cc1a3871756b0c38b4712a7068db71f37e57f29c55cc2b33565294b7716586de061c3ac52f270eb24f3dce346584c24af7503ed8c1a78ef0e94334c0e1d3f253ce4d9a7a98d07a5ff9298347f9d4ffd49a6b2ed5a71cd4546be422bb7f14b8515b6311805321f019a6a53d8ac233c44ba035168fc775a7e69ff9c34330421ef92074a400267b4aa5688a297472e8e17106db5f5e2aa8be27ce99a63cef93013d088d5421821ff2ade00684dd767a3097e0d829f4b95b1dc6b91aa6dcde0b2b5fd9fcc8ef953da9df727efad133cf3e23fd346dd4b63e01c7554665bbe072290ee09a17b3d033c2439f86b3f15b0625e93450ca835f3b552a86a5bbea72cd9bdbf8b565a5a5ace18415af65f7c65231cd7a976e5d0383cfc9776326657c256df9e83839fb0e78c1e5ed9631ae4c7d0ab25d7b23225e14c8e5ca4d855c755af60a6fa56f38450fd8248ce5c3d7700b535df9744bf10146bc3f6de1b460328a12aed8533fa067280287b63eb45eb405f3c09468d372701b80ddf939db169e71584491a8390285644123e4a439055a290a61349ae5b447f8b4885b1cacd8f878e2c9a7d4b99e7bf4b8b6f13ff0dc07f59a0bf05f79c4ef40331231f2d438d38f0e270cb86f9b92990682f0a60fab5478a55b3a8762e8421f230095878b6f2ea7fdf02ebfab9c12b59cfcfbe13c545e5e677ccabe4f4661a996a9a089caa738d85dd9e68baee3127738089a3f6c066c96e53b65d5ad60e395aeae86e58900121d03d81952c1814c99cfc1ea33724b8e782b636582321d65648527194a29a383214e30e8262c1f4c9e72608517aff00777172d6a188f0e3782c208c66549713265de122a203c4e65876be9c95a51b6ef957c5c0fa2f50dc6a79e79d65e8ec3383c9c759a0d17df1887a432223dd2309eec845bbd46becaaa3768e11b58d3c0c169fec43fd3e0359cf0d2b5ec1a535efce29e30f09b3fcbaf7cc8c3a73867fa8a5b1c11c03c2e9b1ee3aa108171a89321ae8e8bd790427f2d5bfce1e3fad872fc815267a79e16c39179c8830f6addf0a8cec42eb9955ffdd1899d69c2d20f122c72bca20de21ca430d091d8d60fae7d05f9114e3b015b5feb25da89ef74b213e9e0759bbcc7a91550c4c5e770d0e9d2918f8f56b041f2c963fa0cb5eb5d48369493f92688d9f688a80c1afac853554c3efc77be42965e02906e80ff993fd3e09cf9335d4796cfdf263ee9aff8e5d7185cd2fc5de98a038fe2ddc78fb287ca556092f09af4768bfd0fc229eb2a83bc75b3d3214ebd2d3889b3a20ffe2e7ae0629e2d130decadae628b2ad766ac2d9d837682c4f8ce19cb8f0fcba7e58e4b35bce001bc65d5e5f44361e19f2be5c04fbcff0b6812f2e14af2660e0000000049454e44ae426082\",\n        expectedOutput: \"https://globeon.mobi/jyri\",\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"None\"]\n            },\n            {\n                \"op\": \"Parse QR Code\",\n                \"args\": [true]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseSSHHostKey.mjs",
    "content": "/**\n * Parse SSH Host Key tests\n *\n * @author j433866 [j433866@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"SSH Host Key: RSA\",\n        input: \"AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF\",\n        expectedOutput: `Key type: ssh-rsa\nExponent: 0x010001\nModulus: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`,\n        recipeConfig: [\n            {\n                op: \"Parse SSH Host Key\",\n                args: [\"Base64\"]\n            }\n        ]\n    },\n    {\n        name: \"SSH Host Key: DSA\",\n        input: \"AAAAB3NzaC1kc3MAAACBAMnoZCOzvaQqs//9mxK2USZvJBc7b1dFJiBcV80abN6maE+203pTRPIPCpPt0deQxv4YN3dSHoodEcArWxs1QRAIuRsQIvsUP7chovzGnxP84XWK5sbfrseD0vxZ7UR0NaAFPcSgeXcWC1SG9uvrAJQlyp4DBy+fKuqiYmwaz0bHAAAAFQCXNJ4yiE1V7LpCU2V1JKbqDvICMwAAAIB/5aR1iBOeyCVpj0dP3YZmoxd9R7FCC/0UuOf0lx4E6WHT6Z2QuPBhc2mpNDq2M0VF9oJfVWgcfG8r1rlXaCYODSacGcbnW5VKQ+LKkkALmg4h8jFCHReUC+Hmia/v8LyDwPO1wK6ETn7a3m80yM7gAU5ZNurVIVVP2lB65mjEsQAAAIA3ct9YRB6iUCvOD45sZM1C9oTC24Ttmaou0GcpWx3h0/iZ8mbil1cjaO9frRNZ/vSSVWEhEDNG8gwkjZWlvnJL3y1XUxbMll4WbmI/Q1kzKwopceaFwMbYTPKDg6L1RtCMUxSUyKsFk1c4SpEPlDS7DApZs5PgmWgMd/u6vwMXyg==\",\n        expectedOutput: `Key type: ssh-dss\np: 0x00c9e86423b3bda42ab3fffd9b12b651266f24173b6f574526205c57cd1a6cdea6684fb6d37a5344f20f0a93edd1d790c6fe183777521e8a1d11c02b5b1b35411008b91b1022fb143fb721a2fcc69f13fce1758ae6c6dfaec783d2fc59ed447435a0053dc4a07977160b5486f6ebeb009425ca9e03072f9f2aeaa2626c1acf46c7\nq: 0x0097349e32884d55ecba4253657524a6ea0ef20233\ng: 0x7fe5a47588139ec825698f474fdd8666a3177d47b1420bfd14b8e7f4971e04e961d3e99d90b8f0617369a9343ab6334545f6825f55681c7c6f2bd6b95768260e0d269c19c6e75b954a43e2ca92400b9a0e21f231421d17940be1e689afeff0bc83c0f3b5c0ae844e7edade6f34c8cee0014e5936ead521554fda507ae668c4b1\ny: 0x3772df58441ea2502bce0f8e6c64cd42f684c2db84ed99aa2ed067295b1de1d3f899f266e297572368ef5fad1359fef492556121103346f20c248d95a5be724bdf2d575316cc965e166e623f4359332b0a2971e685c0c6d84cf28383a2f546d08c531494c8ab059357384a910f9434bb0c0a59b393e099680c77fbbabf0317ca`,\n        recipeConfig: [\n            {\n                op: \"Parse SSH Host Key\",\n                args: [\"Base64\"]\n            }\n        ]\n    },\n    {\n        name: \"SSH Host Key: ECDSA\",\n        input: \"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGxZWSAGJyJQoVBwFCpr420eRUZDE/kw2YWm5vDro8050DZ1ZzqIuYaNl0BGzMcRTeasGtJuI8G84ZQQSgca3C4=\",\n        expectedOutput: `Key type: ecdsa-sha2-nistp256\nCurve: nistp256\nPoint: 0x046c59592006272250a15070142a6be36d1e45464313f930d985a6e6f0eba3cd39d03675673a88b9868d974046ccc7114de6ac1ad26e23c1bce194104a071adc2e`,\n        recipeConfig: [\n            {\n                op: \"Parse SSH Host Key\",\n                args: [\"Base64\"]\n            }\n        ]\n    },\n    {\n        name: \"SSH Host Key: Ed25519\",\n        input: \"AAAAC3NzaC1lZDI1NTE5AAAAIBOF6r99IkvqGu1kwZrHHIqjpTB5w79bpv67B/Aw3+WJ\",\n        expectedOutput: `Key type: ssh-ed25519\nx: 0x1385eabf7d224bea1aed64c19ac71c8aa3a53079c3bf5ba6febb07f030dfe589`,\n        recipeConfig: [\n            {\n                op: \"Parse SSH Host Key\",\n                args: [\"Base64\"]\n            }\n        ]\n    },\n    {\n        name: \"SSH Host Key: Extract key\",\n        input: \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF test@test\",\n        expectedOutput: `Key type: ssh-rsa\nExponent: 0x010001\nModulus: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`,\n        recipeConfig: [\n            {\n                op: \"Parse SSH Host Key\",\n                args: [\"Base64\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseTCP.mjs",
    "content": "/**\n * Parse TCP tests.\n *\n * @author n1474335\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Parse TCP: No options\",\n        input: \"c2eb0050a138132e70dc9fb9501804025ea70000\",\n        expectedMatch: /1026 \\(Scaled: 1026\\)/,\n        recipeConfig: [\n            {\n                op: \"Parse TCP\",\n                args: [\"Hex\"],\n            }\n        ],\n    },\n    {\n        name: \"Parse TCP: Options\",\n        input: \"c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402\",\n        expectedMatch: /1460/,\n        recipeConfig: [\n            {\n                op: \"Parse TCP\",\n                args: [\"Hex\"],\n            }\n        ],\n    },\n    {\n        name: \"Parse TCP: Timestamps\",\n        input: \"9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308\",\n        expectedMatch: /2766719221/,\n        recipeConfig: [\n            {\n                op: \"Parse TCP\",\n                args: [\"Hex\"],\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseTLSRecord.mjs",
    "content": "/**\n * Parse TLS record tests.\n *\n * @auther c65722\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Parse TLS record: Truncated header\",\n        input: \"16030300\",\n        expectedOutput: \"[]\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Change Cipher Spec\",\n        input: \"140303000101\",\n        expectedOutput: '[{\"type\":\"change_cipher_spec\",\"version\":\"0x0303\",\"length\":1,\"value\":\"0x01\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Change Cipher Spec - Truncated before content\",\n        input: \"1403030001\",\n        expectedOutput: '[{\"type\":\"change_cipher_spec\",\"version\":\"0x0303\",\"length\":1,\"truncated\":true}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Alert\",\n        input: \"150303001411770b5b5d11078535823266ec79671ed402bced\",\n        expectedOutput: '[{\"type\":\"alert\",\"version\":\"0x0303\",\"length\":20,\"value\":\"0x11770b5b5d11078535823266ec79671ed402bced\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Alert - Truncated within content\",\n        input: \"150303001411770b5b5d1107853582\",\n        expectedOutput: '[{\"type\":\"alert\",\"version\":\"0x0303\",\"length\":20,\"truncated\":true,\"value\":\"0x11770b5b5d1107853582\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Alert - Truncated before content\",\n        input: \"1503030014\",\n        expectedOutput: '[{\"type\":\"alert\",\"version\":\"0x0303\",\"length\":20,\"truncated\":true}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Truncated within length\",\n        input: \"1603030032010000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Truncated before length\",\n        input: \"160303003201\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Truncated before msg type\",\n        input: \"1603030032\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Hello Request\",\n        input: \"160303000400000000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":4,\"handshakeType\":\"hello_request\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443210200010000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before extensions length\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600000412344321020001\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within compression methods\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443210200\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"truncated\":true,\"values\":[\"0x00\"]},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before compression methods\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000041234432102\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"truncated\":true,\"values\":[]},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before compression methods length\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600000412344321\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within cipher suite value\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004123443\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"truncated\":true,\"values\":[\"0x1234\"]},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within cipher suites\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000041234\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"truncated\":true,\"values\":[\"0x1234\"]},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before cipher suites\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076000004\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{\"length\":4,\"truncated\":true,\"values\":[]},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before cipher suites length\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd0510760000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before session id length\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107600\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within random\",\n        input: \"16030300320100002e030345cd3a31beaebd2934dd4ec2a151d7a0\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"\",\"cipherSuites\":{},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before random\",\n        input: \"16030300320100002e0303\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"\",\"cipherSuites\":{},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated within client version\",\n        input: \"16030300320100002e03\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"\",\"random\":\"\",\"cipherSuites\":{},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, No session ID, No extensions - Truncated before client version\",\n        input: \"16030300320100002e\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":50,\"truncated\":true,\"handshakeType\":\"client_hello\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, No extensions\",\n        input: \"16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae0004123443210200010000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":82,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, No extensions - Truncated within session id\",\n        input: \"16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":82,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, No extensions - Truncated before session id\",\n        input: \"16030300520100004e030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":82,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"cipherSuites\":{},\"compressionMethods\":{},\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, Extensions\",\n        input: \"160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010000e00000b6578616d706c652e636f6d00170000ff01000100\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":111,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{\"length\":29,\"values\":[{\"type\":\"0x0000\",\"length\":16,\"value\":\"0x000e00000b6578616d706c652e636f6d\"},{\"type\":\"0x0017\",\"length\":0},{\"type\":\"0xff01\",\"length\":1,\"value\":\"0x00\"}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension value\",\n        input: \"160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010000e00000b657861\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":111,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{\"length\":29,\"truncated\":true,\"values\":[{\"type\":\"0x0000\",\"length\":16,\"truncated\":true,\"value\":\"0x000e00000b657861\"}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension value\",\n        input: \"160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00000010\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":111,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{\"length\":29,\"truncated\":true,\"values\":[{\"type\":\"0x0000\",\"length\":16,\"truncated\":true}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension length\",\n        input: \"160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d000000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":111,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{\"length\":29,\"truncated\":true,\"values\":[]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension length\",\n        input: \"160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d0000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":111,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{\"length\":29,\"truncated\":true,\"values\":[]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated within extension type\",\n        input: \"160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d00\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":111,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{\"length\":29,\"truncated\":true,\"values\":[]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Hello, Session ID, Extensions - Truncated before extension type\",\n        input: \"160303006f0100006b030345cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd05107620dc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae000412344321020001001d\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":111,\"truncated\":true,\"handshakeType\":\"client_hello\",\"clientVersion\":\"0x0303\",\"random\":\"0x45cd3a31beaebd2934dd4ec2a151d7a054eab8bc0e4e5b9d4b9abdaacd051076\",\"sessionID\":\"0xdc78c85fdcee405ebb7963543771005a3d1b7dbf88fb9f8df12e4f7ea525e1ae\",\"cipherSuites\":{\"length\":4,\"values\":[\"0x1234\",\"0x4321\"]},\"compressionMethods\":{\"length\":2,\"values\":[\"0x00\",\"0x01\"]},\"extensions\":{\"length\":29,\"truncated\":true,\"values\":[]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions\",\n        input: \"160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132004321010000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before extensions length\",\n        input: \"160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113200432101\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before compression method\",\n        input: \"160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132004321\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within cipher suite\",\n        input: \"160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b011320043\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before cipher suite\",\n        input: \"160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113200\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before session id length\",\n        input: \"160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within random\",\n        input: \"160303002c02000028030309684ab9c0f6e739e308cd42a18a73d9a\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before random\",\n        input: \"160303002c0200002803030\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated within server version\",\n        input: \"160303002c0200002803\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"\",\"random\":\"\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, No session ID, No extensions - Truncated before server version\",\n        input: \"160303002c02000028\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":44,\"truncated\":true,\"handshakeType\":\"server_hello\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, No extension\",\n        input: \"160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b95984321010000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":76,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"sessionID\":\"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, No extension - Truncated within session id\",\n        input: \"160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df7\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":76,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, No extension - Truncated before session id\",\n        input: \"160303004c02000048030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":76,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"cipherSuite\":\"\",\"compressionMethod\":\"\",\"extensions\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, Extensions\",\n        input: \"160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff0100010000170000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":89,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"sessionID\":\"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{\"length\":13,\"values\":[{\"type\":\"0x0000\",\"length\":0},{\"type\":\"0xff01\",\"length\":1,\"value\":\"0x00\"},{\"type\":\"0x0017\",\"length\":0}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension value\",\n        input: \"160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff010001\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":89,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"sessionID\":\"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{\"length\":13,\"truncated\":true,\"values\":[{\"type\":\"0x0000\",\"length\":0},{\"type\":\"0xff01\",\"length\":1,\"truncated\":true}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated within extension length\",\n        input: \"160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff0100\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":89,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"sessionID\":\"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{\"length\":13,\"truncated\":true,\"values\":[{\"type\":\"0x0000\",\"length\":0}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension length\",\n        input: \"160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff01\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":89,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"sessionID\":\"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{\"length\":13,\"truncated\":true,\"values\":[{\"type\":\"0x0000\",\"length\":0}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated within extension type\",\n        input: \"160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000ff\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":89,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"sessionID\":\"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{\"length\":13,\"truncated\":true,\"values\":[{\"type\":\"0x0000\",\"length\":0}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello, Session ID, Extensions - Truncated before extension type\",\n        input: \"160303005902000055030309684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b0113220a4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598432101000d00000000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":89,\"truncated\":true,\"handshakeType\":\"server_hello\",\"serverVersion\":\"0x0303\",\"random\":\"0x09684ab9c0f6e739e308cd42a18a73d9adc579378aa6b4228df7ecc422b01132\",\"sessionID\":\"0xa4fe3d1e9a7dc5ce3d9341b4d48a2df755a0fd83876d0330018306707c9b9598\",\"cipherSuite\":\"0x4321\",\"compressionMethod\":\"0x01\",\"extensions\":{\"length\":13,\"truncated\":true,\"values\":[{\"type\":\"0x0000\",\"length\":0}]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - New Session Ticket\",\n        input: \"16030300ca040000c60000070800c0626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470e7d67d8d33647e245e5e789f547d6a1be91ef0985bbfcf3b88760586b8f02570e0b7e8547fdad75530bc0261756ec994dfc725c8551c762f26e105e62290cd43773ea9e8a42ac8ac21467053240a29ef93c2e34c2f13ce8ff494d8c64f727248\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":202,\"handshakeType\":\"new_session_ticket\",\"ticketLifetimeHint\":\"1800s\",\"ticket\":\"0x626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470e7d67d8d33647e245e5e789f547d6a1be91ef0985bbfcf3b88760586b8f02570e0b7e8547fdad75530bc0261756ec994dfc725c8551c762f26e105e62290cd43773ea9e8a42ac8ac21467053240a29ef93c2e34c2f13ce8ff494d8c64f727248\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - New Session Ticket - Truncated within ticket\",\n        input: \"16030300ca040000c60000070800c0626f6889ce97edae08b0870505f9251e1d0713438ed014ac8f5e6969cf9e500aaba6080dfed5474ec85ff48d882d526cdae7f21d51b4beeb0be83fb822f18d22d2086b7519b29114364af034ac9a6915562ba686b81917bcb89fc4a750284470\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":202,\"truncated\":true,\"handshakeType\":\"new_session_ticket\",\"ticketLifetimeHint\":\"1800s\",\"ticket\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - New Session Ticket - Truncated before ticket\",\n        input: \"16030300ca040000c60000070800c0\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":202,\"truncated\":true,\"handshakeType\":\"new_session_ticket\",\"ticketLifetimeHint\":\"1800s\",\"ticket\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - New Session Ticket - Truncated within ticket length\",\n        input: \"16030300ca040000c60000070800\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":202,\"truncated\":true,\"handshakeType\":\"new_session_ticket\",\"ticketLifetimeHint\":\"1800s\",\"ticket\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - New Session Ticket - Truncated before ticket length\",\n        input: \"16030300ca040000c600000708\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":202,\"truncated\":true,\"handshakeType\":\"new_session_ticket\",\"ticketLifetimeHint\":\"1800s\",\"ticket\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - New Session Ticket - Truncated within ticket lifetime hint\",\n        input: \"16030300ca040000c6000007\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":202,\"truncated\":true,\"handshakeType\":\"new_session_ticket\",\"ticketLifetimeHint\":\"\",\"ticket\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - New Session Ticket - Truncated before ticket lifetime hint\",\n        input: \"16030300ca040000c6\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":202,\"truncated\":true,\"handshakeType\":\"new_session_ticket\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate\",\n        input: \"1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d000599308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a59e12a5a72d436036b31fa130f6a72c717b3df403113ee3b3d1605f76e57e96b83e501ed5fe9200e2ea9aefa797fa0c8b6c5d8f12e4bea7359be03d3ca35d3e22e20639fc7e03c990a494402268a08fb1589dc086995b0ba3c9ffe255b6b7cf0203010001a3533051301d0603551d0e041604143e8bac5b946c2eff6a8cb337081fa4fe6ce07312301f0603551d230418301680143e8bac5b946c2eff6a8cb337081fa4fe6ce07312300f0603551d130101ff040530030101ff300d06092a864886f70d01010b0500038202010081ad7acd39e5cc60682c962d367a84d32191e5b465ed531f617daf5fd33394a3ac9a42116d34211708ada0d9bd2cbf1d4a4175d67c87116c7495ed372c585ae6bdfe0bc713aa1afd0cc3f025c322dc45be0c3be982918dea938deaaa9e5bfd1fccb3eb8a111aec0498f64bdb16f6cb07bcecd85f6b9e445cf596d85596b4f0d7147d73cbc26000d374085e9c69f56262827fa3d5a037cf1d2cfe0f0eca779b101da08a8d732ecf584a193d93449697ee24ed6f41f9735ea3a3f206f8e6b5bf0b0ff3488a31d0feaccd701a144d35c265dc32d2e650f855debbfa5bd2d9dc2d80a1b8f81013f8049bd7be83a3ec5ae554c19fd4241a6686d4094ff073022d1f16afa5a0297e54a9b56fd469b44c6904d2b542f83ff0cf6af3b649f408f72f7cb49be5583ec4b1d912a677ae1fd81779506af9b688d8b753fdb0451925752fba8efcdaedf935f2a264caa1f4fe746ac6c339cca647b25f0bd2139205e67b6e90987da8b993b85037931443a6652426ab779db090cf08b28fed862a0ccdde1568bd930bf2d39ab7b850f97925e9bda13a6ee5166e48959711065c054bdf5ff04e4b8d5120caabca40c7707da3bb10f2ae7a00a6e56b012a6c00daaec5ddf0b63f61622aeeb81a71a5aa17508e5471e777bed8d09023c24280495adc38ffc3615dd20b139d32d7cc30b0690ab7f3e47a0131fa3d81929e64c6b9c6b363da410f6e5e\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":2767,\"handshakeType\":\"certificate\",\"certificateList\":{\"length\":2760,\"values\":[\"0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf\",\"0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d\",\"0x308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a59e12a5a72d436036b31fa130f6a72c717b3df403113ee3b3d1605f76e57e96b83e501ed5fe9200e2ea9aefa797fa0c8b6c5d8f12e4bea7359be03d3ca35d3e22e20639fc7e03c990a494402268a08fb1589dc086995b0ba3c9ffe255b6b7cf0203010001a3533051301d0603551d0e041604143e8bac5b946c2eff6a8cb337081fa4fe6ce07312301f0603551d230418301680143e8bac5b946c2eff6a8cb337081fa4fe6ce07312300f0603551d130101ff040530030101ff300d06092a864886f70d01010b0500038202010081ad7acd39e5cc60682c962d367a84d32191e5b465ed531f617daf5fd33394a3ac9a42116d34211708ada0d9bd2cbf1d4a4175d67c87116c7495ed372c585ae6bdfe0bc713aa1afd0cc3f025c322dc45be0c3be982918dea938deaaa9e5bfd1fccb3eb8a111aec0498f64bdb16f6cb07bcecd85f6b9e445cf596d85596b4f0d7147d73cbc26000d374085e9c69f56262827fa3d5a037cf1d2cfe0f0eca779b101da08a8d732ecf584a193d93449697ee24ed6f41f9735ea3a3f206f8e6b5bf0b0ff3488a31d0feaccd701a144d35c265dc32d2e650f855debbfa5bd2d9dc2d80a1b8f81013f8049bd7be83a3ec5ae554c19fd4241a6686d4094ff073022d1f16afa5a0297e54a9b56fd469b44c6904d2b542f83ff0cf6af3b649f408f72f7cb49be5583ec4b1d912a677ae1fd81779506af9b688d8b753fdb0451925752fba8efcdaedf935f2a264caa1f4fe746ac6c339cca647b25f0bd2139205e67b6e90987da8b993b85037931443a6652426ab779db090cf08b28fed862a0ccdde1568bd930bf2d39ab7b850f97925e9bda13a6ee5166e48959711065c054bdf5ff04e4b8d5120caabca40c7707da3bb10f2ae7a00a6e56b012a6c00daaec5ddf0b63f61622aeeb81a71a5aa17508e5471e777bed8d09023c24280495adc38ffc3615dd20b139d32d7cc30b0690ab7f3e47a0131fa3d81929e64c6b9c6b363da410f6e5e\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate - Truncated within certificate\",\n        input: \"1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d0005990x308205953082037da00302010202141521d02e945395325d99051e616ad01c97627ee2300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d3020170d3234303932323130303232325a180f32313234303832393130303232325a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65332e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100bd7c65b5c2c7027e4eb77722f84d7dc9b45f9fae45c59dd0035340b3d8fec5ea644ac4563c4260b2c078880bf81ffec0e4cd9193b708ded6431c0e7d9e8f45d595712b733262f8f62f1b4c3ae69f1f39bc68a39b1b5699adddfd7c51b83f59479fe5ffe0faef6376b1c5cea434aa9db85e792f989b5977c6fda87f7c00f79e67e417d826c1ab1fa304163414fc6321790f07cffede43170718536e5fe3128f6d101de82a7b1de37f89e61d822f09eef7304213d41998a49e5ab6b1a7eb1ab4ece21f005061828567047aaf640cff2f87c85eefc2d3a91ebf48aaa893e59451acbea894975df2587b203302fb39755f2e21e012d1fc89df86ec53723df497318d8b44eee9334a2699ad403a7df6719747bc37429d3c47ada354308380b09bb6d76e21dc1735a1479470c94c0282bbbdf5e2e6af60cf1f2e9b8dad20e45307729813eaaf584b31984e036d5452dfae47a4b8640bdf4c02ecf4ce4240d64d2ab895cbf512558712533cd3fc6838bfd24a2a588b9f1b1848bb0d6b1cd77345add6e9dc547a7b95b027bb18e96f30c4f9cd780c96984472b70ea39a7acdff9c649ac4a5\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":2767,\"truncated\":true,\"handshakeType\":\"certificate\",\"certificateList\":{\"length\":2760,\"truncated\":true,\"values\":[\"0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf\",\"0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate - Truncated before certificate\",\n        input: \"1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d000599\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":2767,\"truncated\":true,\"handshakeType\":\"certificate\",\"certificateList\":{\"length\":2760,\"truncated\":true,\"values\":[\"0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf\",\"0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate - Truncated within certificate length\",\n        input: \"1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d0005\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":2767,\"truncated\":true,\"handshakeType\":\"certificate\",\"certificateList\":{\"length\":2760,\"truncated\":true,\"values\":[\"0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf\",\"0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate - Truncated before certificate length\",\n        input: \"1603030acf0b000acb000ac80002923082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf00029430820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":2767,\"truncated\":true,\"handshakeType\":\"certificate\",\"certificateList\":{\"length\":2760,\"truncated\":true,\"values\":[\"0x3082028e308201f7a003020102021468f6f88ecf1bf3d14e7503ef2e1b789cb77b86c3300d06092a864886f70d01010b05003058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d3020170d3234303932323039353335385a180f32313234303832393039353335385a3058310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643114301206035504030c0b6578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100c3df3e5745f05b3aa220ce4108838107653c3ae9584ace27d7088506ebdc3531afbe6265719278682eaa4fec7ae1f319395d356be79477bc62edbe7207d96f5717e9bd9083fdcc797c1b8e38bcf9fd08df6f101bc2a06101ddce6be2f5a0de80ebc8fdce2538867c1d6a84acef26b2068c5d27771abcee071bcf378899cb32730203010001a3533051301d0603551d0e041604144c9b134c1575c51ae9d03c4020da7541278ad928301f0603551d230418301680144c9b134c1575c51ae9d03c4020da7541278ad928300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381810012a06cced33d721b1d7912ff0b190b74524ddfdeca103aba0f168f4f15f57212ba7d66328e48b021f32cfec84f65d79821bc1fe9f472f60c094e537160708a48a0898dbf613cece86892cf48fcd598757aa4379e18673626be2f048e35f585086ea7a3766ce50a14ca6f691b369c965e062f40619cde6262ed8019b522e76eaf\",\"0x30820290308201f9a00302010202142a3329f5e2e92940318cecd036ff135525b1d491300d06092a864886f70d01010b05003059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d3020170d3234303932323039353531375a180f32313234303832393039353531375a3059310b30090603550406130258583115301306035504070c0c44656661756c742043697479311c301a060355040a0c1344656661756c7420436f6d70616e79204c74643115301306035504030c0c6578616d706c65322e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100b27c861d957c49111b4f37f65bc142da564429c74a925e3de6d9add55ccfccf1316a5002b3ed2d35ec9822499e7256f9caaa2191010df354185c63a32c8d080ba49510953d7ec2210685030564be69a9f2262a9da22f3623b2a9b032f3a82b1c31ce11336c288fc3d5f63565aacc8c0f85ebaad6af2cd3505a7cf3945ca2ca690203010001a3533051301d0603551d0e0416041485478b7936ecd417647e9d8582d3f68fc670d839301f0603551d2304183016801485478b7936ecd417647e9d8582d3f68fc670d839300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003818100652656aef44c7a507a376de248cd1b36028fb1b0292593f88eb36b429f7de4c668aef7b0d862c9314e5d870f7c28353022657a7de07ec69505a54e48337ab6ba425bfd8865b720f1f2e86c92edaa261fd73e44856ac45c4d9378c86adb96b6f999f61e5f651cb885e06a3d909b5fa79458941bea36785ea585aeb5025032a18d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate - Truncated within certificate list length\",\n        input: \"1603030acf0b000acb000a\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":2767,\"truncated\":true,\"handshakeType\":\"certificate\",\"certificateList\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate - Truncated before certificate list length\",\n        input: \"1603030acf0b000acb\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":2767,\"truncated\":true,\"handshakeType\":\"certificate\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Key Exchange\",\n        input: \"16030300840c000080a90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b87175e472c7c07b97699f72958e0af489df00d34e5e03dde2e09dfe06d448651ee45c07fadc05e0d1585589e3715a04b935e72bc28c34593712acef7883ed69a\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":132,\"handshakeType\":\"server_key_exchange\",\"handshakeValue\":\"0xa90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b87175e472c7c07b97699f72958e0af489df00d34e5e03dde2e09dfe06d448651ee45c07fadc05e0d1585589e3715a04b935e72bc28c34593712acef7883ed69a\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Key Exchange - Truncated within content\",\n        input: \"16030300840c000080a90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":132,\"truncated\":true,\"handshakeType\":\"server_key_exchange\",\"handshakeValue\":\"0xa90c12174921d7044303107b6e37523957439b436e57904e82702784bfc261a8f0a7e4143a77144357d29ee322f25e4fce393ac7570ee26c378298a6ad18fd8b\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Key Exchange - Truncated before content\",\n        input: \"16030300840c000080\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":132,\"truncated\":true,\"handshakeType\":\"server_key_exchange\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, No certificate authorities\",\n        input: \"160303001f0d00001b040102030400120601060206030301030203030201020202030000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":31,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities\",\n        input: \"16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d001813b3cdd60a34fc74f2e4ef2344cfd2156924d8d2810e2c86\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]},\"certificateAuthorities\":{\"length\":40,\"values\":[\"0x546bf13f358cf3ddc1eef77d\",\"0x13b3cdd60a34fc74f2e4ef2344cfd2156924d8d2810e2c86\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authority\",\n        input: \"16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d001813b3cdd60a34fc74f2e4ef23\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]},\"certificateAuthorities\":{\"length\":40,\"truncated\":true,\"values\":[\"0x546bf13f358cf3ddc1eef77d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authority\",\n        input: \"16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d0018\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]},\"certificateAuthorities\":{\"length\":40,\"truncated\":true,\"values\":[\"0x546bf13f358cf3ddc1eef77d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authority length\",\n        input: \"16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d00\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]},\"certificateAuthorities\":{\"length\":40,\"truncated\":true,\"values\":[\"0x546bf13f358cf3ddc1eef77d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authority length\",\n        input: \"16030300470d000043040102030400120601060206030301030203030201020202030028000c546bf13f358cf3ddc1eef77d\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]},\"certificateAuthorities\":{\"length\":40,\"truncated\":true,\"values\":[\"0x546bf13f358cf3ddc1eef77d\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate authorities length\",\n        input: \"16030300470d0000430401020304001206010602060303010302030302010202020300\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate authorities length\",\n        input: \"16030300470d00004304010203040012060106020603030103020303020102020203\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\",\"0x0203\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within supported signature algorithm\",\n        input: \"16030300470d000043040102030400120601060206030301030203030201020202\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"truncated\":true,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before supported signature algorithm\",\n        input: \"16030300470d0000430401020304001206010602060303010302030302010202\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{\"length\":18,\"truncated\":true,\"values\":[\"0x0601\",\"0x0602\",\"0x0603\",\"0x0301\",\"0x0302\",\"0x0303\",\"0x0201\",\"0x0202\"]}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within supported signature algorithms length\",\n        input: \"16030300470d000043040102030400\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before supported signature algorithms length\",\n        input: \"16030300470d0000430401020304\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"values\":[\"0x01\",\"0x02\",\"0x03\",\"0x04\"]},\"supportedSignatureAlgorithms\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated within certificate types\",\n        input: \"16030300470d00004304010203\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"truncated\":true,\"values\":[\"0x01\",\"0x02\",\"0x03\"]},\"supportedSignatureAlgorithms\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate types\",\n        input: \"16030300470d00004304\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\",\"certificateTypes\":{\"length\":4,\"truncated\":true,\"values\":[]},\"supportedSignatureAlgorithms\":{}}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Request, Certificate authorities - Truncated before certificate types length\",\n        input: \"16030300470d000043\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":71,\"truncated\":true,\"handshakeType\":\"certificate_request\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Server Hello Done\",\n        input: \"16030300040e000000\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":4,\"handshakeType\":\"server_hello_done\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Verify\",\n        input: \"16030301080f000104040101009310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2a93d1b85ae2d0ee7c7170aac3397fa6de77183d30c99e6bb0e81f925793f64d8b490cb74d051896ebee9086c7606905b21bab6ebd9866a451958f7d839134aeb335b2ad5f9ce89a69321a099c081b5166332cf2bb231dd135b79cf94218e6ada94644eaa09ae6c0ec0164e3cca631c0f4b7b9a2d59fb40909ec88805e61b5917\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":264,\"handshakeType\":\"certificate_verify\",\"algorithmHash\":\"0x04\",\"algorithmSignature\":\"0x01\",\"signature\":\"0x9310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2a93d1b85ae2d0ee7c7170aac3397fa6de77183d30c99e6bb0e81f925793f64d8b490cb74d051896ebee9086c7606905b21bab6ebd9866a451958f7d839134aeb335b2ad5f9ce89a69321a099c081b5166332cf2bb231dd135b79cf94218e6ada94644eaa09ae6c0ec0164e3cca631c0f4b7b9a2d59fb40909ec88805e61b5917\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Verify - Truncated within signature\",\n        input: \"16030301080f000104040101009310d3dda84b149a00258f0bb4501e710f7ed70a45cf4f0bab39dac1a456027f0f6167924f08a8221613bcf46c27e91458d05163200fd1bf3673351d74693c08c6640635d4e9f84e9568e39d3346e3ff2f3eacf9887d738935d8b07e42659dd3b212662bf028bcefe98b686a1a83fb2f24aead94cccd3f6b26c9d42ba43254d2\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":264,\"truncated\":true,\"handshakeType\":\"certificate_verify\",\"algorithmHash\":\"0x04\",\"algorithmSignature\":\"0x01\",\"signature\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Verify - Truncated before signature\",\n        input: \"16030301080f00010404010100\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":264,\"truncated\":true,\"handshakeType\":\"certificate_verify\",\"algorithmHash\":\"0x04\",\"algorithmSignature\":\"0x01\",\"signature\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Verify - Truncated within signature length\",\n        input: \"16030301080f000104040101\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":264,\"truncated\":true,\"handshakeType\":\"certificate_verify\",\"algorithmHash\":\"0x04\",\"algorithmSignature\":\"0x01\",\"signature\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Verify - Truncated before signature length\",\n        input: \"16030301080f0001040401\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":264,\"truncated\":true,\"handshakeType\":\"certificate_verify\",\"algorithmHash\":\"0x04\",\"algorithmSignature\":\"0x01\",\"signature\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Verify - Truncated before algorithm.signature\",\n        input: \"16030301080f00010404\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":264,\"truncated\":true,\"handshakeType\":\"certificate_verify\",\"algorithmHash\":\"0x04\",\"algorithmSignature\":\"\",\"signature\":\"\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Certificate Verify - Truncated before algorithm.hash\",\n        input: \"16030301080f000104\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":264,\"truncated\":true,\"handshakeType\":\"certificate_verify\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Key Exchange\",\n        input: \"1603030084100000802b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602ad0b4cb7319fc52694a67f1e381b4d8a581823410920717ee85ef352dea39097e6b131bdfeb3913f0f7eaa3b3882abe4615cc13e2a133558adff159771dfdc8d\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":132,\"handshakeType\":\"client_key_exchange\",\"handshakeValue\":\"0x2b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602ad0b4cb7319fc52694a67f1e381b4d8a581823410920717ee85ef352dea39097e6b131bdfeb3913f0f7eaa3b3882abe4615cc13e2a133558adff159771dfdc8d\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Key Exchange - Truncated within content\",\n        input: \"1603030084100000802b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":132,\"truncated\":true,\"handshakeType\":\"client_key_exchange\",\"handshakeValue\":\"0x2b45af77539975e975c9389030193bb6d7841d870e058850a5aac5f8ded75d243ae8bec2bc8ba4e683eba22d5820b555c69f97001aa7d56cba1839588e7f1602\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Client Key Exchange - Truncated before content\",\n        input: \"160303008410000080\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":132,\"truncated\":true,\"handshakeType\":\"client_key_exchange\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Finished\",\n        input: \"1603030028ed83078db91b046358065ca3f7ea4494af3deb59bf72f522e15ef9071c52becb0069a093b23994c1\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":40,\"handshakeType\":\"finished\",\"handshakeValue\":\"0xed83078db91b046358065ca3f7ea4494af3deb59bf72f522e15ef9071c52becb0069a093b23994c1\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Finished - Truncated within ciphertext\",\n        input: \"1603030028ed83078db91b046358065ca3f7ea4494af3deb59\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":40,\"truncated\":true,\"handshakeType\":\"finished\",\"handshakeValue\":\"0xed83078db91b046358065ca3f7ea4494af3deb59\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Unknown\",\n        input: \"1603030024120000203c210cd33fd2a7379ae02700b208ae7357f98b46a1dea566c4061acfb6e188bc\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":36,\"handshakeType\":\"18\",\"handshakeValue\":\"0x3c210cd33fd2a7379ae02700b208ae7357f98b46a1dea566c4061acfb6e188bc\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Unknown - Truncated within content\",\n        input: \"1603030024120000203c210cd33fd2a7379ae02700b208\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":36,\"truncated\":true,\"handshakeType\":\"18\",\"handshakeValue\":\"0x3c210cd33fd2a7379ae02700b208\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Handshake - Unknown - Truncated before content\",\n        input: \"160303002412000020\",\n        expectedOutput: '[{\"type\":\"handshake\",\"version\":\"0x0303\",\"length\":36,\"truncated\":true,\"handshakeType\":\"18\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Application Data\",\n        input: \"1703030064bbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca6731705f5d957c481bade800cf1cd066dfd997851af09e820e84ee0b531b4eaccfd8b5f28b74d756a8aeadf78eefb2d26e46b5b69\",\n        expectedOutput: '[{\"type\":\"application_data\",\"version\":\"0x0303\",\"length\":100,\"value\":\"0xbbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca6731705f5d957c481bade800cf1cd066dfd997851af09e820e84ee0b531b4eaccfd8b5f28b74d756a8aeadf78eefb2d26e46b5b69\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Application Data - Truncated within content\",\n        input: \"1703030064bbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca67317\",\n        expectedOutput: '[{\"type\":\"application_data\",\"version\":\"0x0303\",\"length\":100,\"truncated\":true,\"value\":\"0xbbfd70f5d2ae0fe62262830040c264fa578bf2000ea50bb2c92d4837727f5db06b580e43896eaa1a0042b4fc3eb5aca67317\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Application Data - Truncated before content\",\n        input: \"1703030064\",\n        expectedOutput: '[{\"type\":\"application_data\",\"version\":\"0x0303\",\"length\":100,\"truncated\":true}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Unknown\",\n        input: \"1c03030020c02beaae1dd2e9ec46c4d201d72105457af1f8e92d56ad95f339398e5774cb6f\",\n        expectedOutput: '[{\"type\":\"28\",\"version\":\"0x0303\",\"length\":32,\"value\":\"0xc02beaae1dd2e9ec46c4d201d72105457af1f8e92d56ad95f339398e5774cb6f\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Unknown - Truncated within content\",\n        input: \"1c03030020c02beaae1dd2e9ec46c4d201d7210545\",\n        expectedOutput: '[{\"type\":\"28\",\"version\":\"0x0303\",\"length\":32,\"truncated\":true,\"value\":\"0xc02beaae1dd2e9ec46c4d201d7210545\"}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    },\n    {\n        name: \"Parse TLS record: Unknown - Truncated before content\",\n        input: \"1c03030020\",\n        expectedOutput: '[{\"type\":\"28\",\"version\":\"0x0303\",\"length\":32,\"truncated\":true}]',\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Parse TLS record\",\n                args: []\n            },\n            {\n                op: \"JSON Minify\",\n                args: []\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseTLV.mjs",
    "content": "/**\n * Parse TLV tests.\n *\n * @author gchq77703 []\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Parse TLV: LengthValue\",\n        input: \"\\x05\\x48\\x6f\\x75\\x73\\x65\\x04\\x72\\x6f\\x6f\\x6d\\x04\\x64\\x6f\\x6f\\x72\",\n        expectedOutput: JSON.stringify([{\"length\": 5, \"value\": [72, 111, 117, 115, 101]}, {\"length\": 4, \"value\": [114, 111, 111, 109]}, {\"length\": 4, \"value\": [100, 111, 111, 114]}], null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"Parse TLV\",\n                \"args\": [0, 1, false]\n            }\n        ]\n    },\n    {\n        name: \"Parse TLV: LengthValue with BER\",\n        input: \"\\x05\\x48\\x6f\\x75\\x73\\x65\\x04\\x72\\x6f\\x6f\\x6d\\x04\\x64\\x6f\\x6f\\x72\",\n        expectedOutput: JSON.stringify([{\"length\": 5, \"value\": [72, 111, 117, 115, 101]}, {\"length\": 4, \"value\": [114, 111, 111, 109]}, {\"length\": 4, \"value\": [100, 111, 111, 114]}], null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"Parse TLV\",\n                \"args\": [0, 4, true] // length value is patently wrong, should be ignored by BER.\n            }\n        ]\n    },\n    {\n        name: \"Parse TLV: KeyLengthValue\",\n        input: \"\\x04\\x05\\x48\\x6f\\x75\\x73\\x65\\x05\\x04\\x72\\x6f\\x6f\\x6d\\x42\\x04\\x64\\x6f\\x6f\\x72\",\n        expectedOutput: JSON.stringify([{\"key\": [4], \"length\": 5, \"value\": [72, 111, 117, 115, 101]}, {\"key\": [5], \"length\": 4, \"value\": [114, 111, 111, 109]}, {\"key\": [66], \"length\": 4, \"value\": [100, 111, 111, 114]}], null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"Parse TLV\",\n                \"args\": [1, 1, false]\n            }\n        ]\n    },\n    {\n        name: \"Parse TLV: KeyLengthValue with BER\",\n        input: \"\\x04\\x05\\x48\\x6f\\x75\\x73\\x65\\x05\\x04\\x72\\x6f\\x6f\\x6d\\x42\\x04\\x64\\x6f\\x6f\\x72\",\n        expectedOutput: JSON.stringify([{\"key\": [4], \"length\": 5, \"value\": [72, 111, 117, 115, 101]}, {\"key\": [5], \"length\": 4, \"value\": [114, 111, 111, 109]}, {\"key\": [66], \"length\": 4, \"value\": [100, 111, 111, 114]}], null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"Parse TLV\",\n                \"args\": [1, 4, true] // length value is patently wrong, should be ignored by BER.\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseUDP.mjs",
    "content": "/**\n * Parse UDP tests.\n *\n * @author h345983745\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Parse UDP: No Data - JSON\",\n        input: \"04 89 00 35 00 2c 01 01\",\n        expectedOutput: \"{\\\"Source port\\\":1161,\\\"Destination port\\\":53,\\\"Length\\\":44,\\\"Checksum\\\":\\\"0x0101\\\"}\",\n        recipeConfig: [\n            {\n                op: \"Parse UDP\",\n                args: [\"Hex\"],\n            },\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    }, {\n        name: \"Parse UDP: With Data - JSON\",\n        input: \"04 89 00 35 00 2c 01 01 02 02\",\n        expectedOutput: \"{\\\"Source port\\\":1161,\\\"Destination port\\\":53,\\\"Length\\\":44,\\\"Checksum\\\":\\\"0x0101\\\",\\\"Data\\\":\\\"0x0202\\\"}\",\n        recipeConfig: [\n            {\n                op: \"Parse UDP\",\n                args: [\"Hex\"],\n            },\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"Parse UDP: Not Enough Bytes\",\n        input: \"04 89 00\",\n        expectedOutput: \"Need 8 bytes for a UDP Header\",\n        recipeConfig: [\n            {\n                op: \"Parse UDP\",\n                args: [\"Hex\"],\n            },\n            {\n                op: \"JSON Minify\",\n                args: [],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/ParseX509CRL.mjs",
    "content": "/**\n * Parse X.509 CRL tests.\n *\n * @author robinsandhu\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst IN_CRL_PEM_RSA = `-----BEGIN X509 CRL-----\nMIID7jCCAdYCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV\nBAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN\nMjQwODI1MTE0OTEwWhcNMjQwOTI0MTE0OTEwWjA1MDMCAhAAFw0yNDA4MjUwMzIz\nMDhaMB4wCgYDVR0VBAMKAQYwEAYDVR0XBAkGByqGSM44AgOgggEnMIIBIzAJBgNV\nHRIEAjAAMH0GA1UdIwR2MHSAFLjJrf2oUFTVhW40i0xgL7BJtodGoUakRDBCMQsw\nCQYDVQQGEwJVSzEPMA0GA1UECAwGTG9uZG9uMQswCQYDVQQKDAJCQjEVMBMGA1UE\nAwwMVGVzdCBSb290IENBghQ3XUv2vXwRfMxGGv/XLywm+B5LPTAtBgNVHS4EJjAk\nMCKgIKAehhxodHRwOi8vZXhhbXBsZS5jb20vZGVsdGEtY3JsMFsGA1UdHwRUMFIw\nIaAfoB2GG2h0dHA6Ly9leGFtcGxlLmNvbS9mdWxsLWNybDAhoB+gHYYbbGRhcDov\nL2V4YW1wbGUuY29tL2Z1bGwtY3JsMAqgCKAGhwR/AAABMAsGA1UdFAQEAgIePDAN\nBgkqhkiG9w0BAQsFAAOCAgEAAxsr+9nELUVWhFekwy6GsqH8xOf6EqGjRaEdX49W\nmB40m2VajOkK8UHGoVyZzoDI2r/c8OPXUtbpK0fpvEl3SZU5j/C8JbZaZFFrEGeH\nfSEqdVHFjohpawNcG41Qs+YT21TBqH1hD5yVI7gjVvfKICRfxDpl5oGClxBCVOSV\ngVtLbe9q44uCBJ1kUkoc9Vz47Hv7JyckgqVXkORWHt2SFNALxlMEzOEQTpuC5Kcb\n4i7hTCUF+kpkIvr02LJImq0Aaqzs6cC/DcdJiRPPyfaN8fQryFv76gg9i8zZcb6c\nW42rvumiyw+7nnZfmq53webr5fCHaXhZk47ASOJD6GC5cX9rje1qGRgULXRhqcvK\nn319s2iXj3FStDDorKGgsCV2zYmotX17ExB98CcCgBE52zMtRZilwhOGeh8mx3qT\nl0W2B8uKKAq5BMmiziSBzQt700JPiruURZXbQ1fH1n7pKP6wGEh2e9TfQMlN20hE\nI+CMt+1bG0Bpt5AfiwE8UykQ/WvpVxdJrgj0JM0yA37KfC8XD+cmavJ5/grorbj3\nt0zBdK7bl+Y45VU/5/mX5ZR3O3ea1RclPM3hKMREfPneOlpan6r3dVwFqEN/TeTu\n46vuDeKaEr3yJkOFfy0lSYPhPhzhU5vDR5ibxqvwxZNznI2AdTnZLEf8LRqnTVo1\nqx0=\n-----END X509 CRL-----`;\n\nconst OUT_CRL_PEM_RSA = `Certificate Revocation List (CRL):\n    Version: 2 (0x1)\n    Signature Algorithm: SHA256withRSA\n    Issuer:\n        C  = UK\n        ST = London\n        O  = BB\n        CN = Test Root CA\n    Last Update: Sun, 25 Aug 2024 11:49:10 GMT\n    Next Update: Tue, 24 Sep 2024 11:49:10 GMT\n\tCRL extensions:\n        2.5.29.46:\n        \tUnsupported CRL extension. Try openssl CLI.\n        X509v3 Authority Key Identifier:\n        \tkeyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46\n        \tDirName:/C=UK/ST=London/O=BB/CN=Test Root CA\n        \tserial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D\n        X509v3 CRL Distribution Points:\n        \tFull Name:\n        \t\tURI:http://example.com/full-crl\n        \tFull Name:\n        \t\tURI:ldap://example.com/full-crl\n        \tFull Name:\n        \t\tIP:127.0.0.1\n        X509v3 CRL Number:\n        \t1E3C\n        issuerAltName:\n        \tUnsupported CRL extension. Try openssl CLI.\nRevoked Certificates:\n    Serial Number: 1000\n        Revocation Date: Sun, 25 Aug 2024 03:23:08 GMT\n    \tCRL entry extensions:\n            X509v3 CRL Reason Code:\n                Certificate Hold\n            Hold Instruction Code:\n            \tHold Instruction Reject\nSignature Value:\n        03:1b:2b:fb:d9:c4:2d:45:56:84:57:a4:c3:2e:86:b2:a1:fc:\n        c4:e7:fa:12:a1:a3:45:a1:1d:5f:8f:56:98:1e:34:9b:65:5a:\n        8c:e9:0a:f1:41:c6:a1:5c:99:ce:80:c8:da:bf:dc:f0:e3:d7:\n        52:d6:e9:2b:47:e9:bc:49:77:49:95:39:8f:f0:bc:25:b6:5a:\n        64:51:6b:10:67:87:7d:21:2a:75:51:c5:8e:88:69:6b:03:5c:\n        1b:8d:50:b3:e6:13:db:54:c1:a8:7d:61:0f:9c:95:23:b8:23:\n        56:f7:ca:20:24:5f:c4:3a:65:e6:81:82:97:10:42:54:e4:95:\n        81:5b:4b:6d:ef:6a:e3:8b:82:04:9d:64:52:4a:1c:f5:5c:f8:\n        ec:7b:fb:27:27:24:82:a5:57:90:e4:56:1e:dd:92:14:d0:0b:\n        c6:53:04:cc:e1:10:4e:9b:82:e4:a7:1b:e2:2e:e1:4c:25:05:\n        fa:4a:64:22:fa:f4:d8:b2:48:9a:ad:00:6a:ac:ec:e9:c0:bf:\n        0d:c7:49:89:13:cf:c9:f6:8d:f1:f4:2b:c8:5b:fb:ea:08:3d:\n        8b:cc:d9:71:be:9c:5b:8d:ab:be:e9:a2:cb:0f:bb:9e:76:5f:\n        9a:ae:77:c1:e6:eb:e5:f0:87:69:78:59:93:8e:c0:48:e2:43:\n        e8:60:b9:71:7f:6b:8d:ed:6a:19:18:14:2d:74:61:a9:cb:ca:\n        9f:7d:7d:b3:68:97:8f:71:52:b4:30:e8:ac:a1:a0:b0:25:76:\n        cd:89:a8:b5:7d:7b:13:10:7d:f0:27:02:80:11:39:db:33:2d:\n        45:98:a5:c2:13:86:7a:1f:26:c7:7a:93:97:45:b6:07:cb:8a:\n        28:0a:b9:04:c9:a2:ce:24:81:cd:0b:7b:d3:42:4f:8a:bb:94:\n        45:95:db:43:57:c7:d6:7e:e9:28:fe:b0:18:48:76:7b:d4:df:\n        40:c9:4d:db:48:44:23:e0:8c:b7:ed:5b:1b:40:69:b7:90:1f:\n        8b:01:3c:53:29:10:fd:6b:e9:57:17:49:ae:08:f4:24:cd:32:\n        03:7e:ca:7c:2f:17:0f:e7:26:6a:f2:79:fe:0a:e8:ad:b8:f7:\n        b7:4c:c1:74:ae:db:97:e6:38:e5:55:3f:e7:f9:97:e5:94:77:\n        3b:77:9a:d5:17:25:3c:cd:e1:28:c4:44:7c:f9:de:3a:5a:5a:\n        9f:aa:f7:75:5c:05:a8:43:7f:4d:e4:ee:e3:ab:ee:0d:e2:9a:\n        12:bd:f2:26:43:85:7f:2d:25:49:83:e1:3e:1c:e1:53:9b:c3:\n        47:98:9b:c6:ab:f0:c5:93:73:9c:8d:80:75:39:d9:2c:47:fc:\n        2d:1a:a7:4d:5a:35:ab:1d`;\n\nconst IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `-----BEGIN X509 CRL-----\nMIID9jCCAd4CAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV\nBAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN\nMjQwODI1MTIwODU2WhcNMjQwOTI0MTIwODU2WjA9MDsCAhAAFw0yNDA4MjUxMjA4\nNDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAScw\nggEjMAkGA1UdEgQCMAAwfQYDVR0jBHYwdIAUuMmt/ahQVNWFbjSLTGAvsEm2h0ah\nRqREMEIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xCzAJBgNVBAoMAkJC\nMRUwEwYDVQQDDAxUZXN0IFJvb3QgQ0GCFDddS/a9fBF8zEYa/9cvLCb4Hks9MC0G\nA1UdLgQmMCQwIqAgoB6GHGh0dHA6Ly9leGFtcGxlLmNvbS9kZWx0YS1jcmwwWwYD\nVR0fBFQwUjAhoB+gHYYbaHR0cDovL2V4YW1wbGUuY29tL2Z1bGwtY3JsMCGgH6Ad\nhhtsZGFwOi8vZXhhbXBsZS5jb20vZnVsbC1jcmwwCqAIoAaHBH8AAAEwCwYDVR0U\nBAQCAh49MA0GCSqGSIb3DQEBCwUAA4ICAQByLp7JWQmB1NhlLACH6zFOe31yCTVy\nxJQtgujtSri1LNu6IwzBGsKBQIl3ucwMxPvoZzlujNLmshUT3nSogV0/5n1q0Gyj\n5Yiz2iw8mmKJLmGZ9Oz3QoGxgFww0/0x/VwRHuS2hw+A7JB8tO/2nW3oTclvS55l\nR+VtkDjUN58+Yl2SQksvb3qD6bHHJTCaP7Dskls0fdBIoYIDvZejrTYSSzTX/Kw4\n735P0GBMhj7zVF8azGz2PFpSISg4huJMyp7EDKZf2c2dnkuwmEUlPQEBLX25j/Il\n81OxfVVFja+wUagaGtjEPGy5gsU8zFwkWhjaD5PGBbZvnT+EDsOtJPU7Ot/sBHfz\nXqUtMrfmz/S/GsQ+QCpnBvarBy9QYuk9M0ePBGy33CUQpjPULxuJJVAHxNoetHCv\n7udng2Pi4D8vDNfzbMwHt7HurMo0CsSju+cL4rnIfsz02RrD9WC84KxBLWkqC7Hi\nIKGIpF740Yc4BliVE1HDaOKyI6FEft5asj3OgXwmBw7pVlxSNWACaA2vOFkdN/V5\nXZZjVJdRJxkgEfCvsJVenFp8ND6gmJmWum7tqM5ytmiXjPtejsPpVq4IclG+Yhnr\ntFQ9TDEuCrNsRIGGGDodyXq1+kGXY0w8RqGEb7J4Og/M6r4LMAKPkO7e0nEibTqX\nd2igvR2e5p+yKw==\n-----END X509 CRL-----`;\n\nconst OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `Certificate Revocation List (CRL):\n    Version: 2 (0x1)\n    Signature Algorithm: SHA256withRSA\n    Issuer:\n        C  = UK\n        ST = London\n        O  = BB\n        CN = Test Root CA\n    Last Update: Sun, 25 Aug 2024 12:08:56 GMT\n    Next Update: Tue, 24 Sep 2024 12:08:56 GMT\n\tCRL extensions:\n        2.5.29.46:\n        \tUnsupported CRL extension. Try openssl CLI.\n        X509v3 Authority Key Identifier:\n        \tkeyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46\n        \tDirName:/C=UK/ST=London/O=BB/CN=Test Root CA\n        \tserial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D\n        X509v3 CRL Distribution Points:\n        \tFull Name:\n        \t\tURI:http://example.com/full-crl\n        \tFull Name:\n        \t\tURI:ldap://example.com/full-crl\n        \tFull Name:\n        \t\tIP:127.0.0.1\n        X509v3 CRL Number:\n        \t1E3D\n        issuerAltName:\n        \tUnsupported CRL extension. Try openssl CLI.\nRevoked Certificates:\n    Serial Number: 1000\n        Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT\n    \tCRL entry extensions:\n            X509v3 CRL Reason Code:\n                Key Compromise\n            Invalidity Date:\n            \tSun, 25 Aug 2024 00:00:00 GMT\nSignature Value:\n        72:2e:9e:c9:59:09:81:d4:d8:65:2c:00:87:eb:31:4e:7b:7d:\n        72:09:35:72:c4:94:2d:82:e8:ed:4a:b8:b5:2c:db:ba:23:0c:\n        c1:1a:c2:81:40:89:77:b9:cc:0c:c4:fb:e8:67:39:6e:8c:d2:\n        e6:b2:15:13:de:74:a8:81:5d:3f:e6:7d:6a:d0:6c:a3:e5:88:\n        b3:da:2c:3c:9a:62:89:2e:61:99:f4:ec:f7:42:81:b1:80:5c:\n        30:d3:fd:31:fd:5c:11:1e:e4:b6:87:0f:80:ec:90:7c:b4:ef:\n        f6:9d:6d:e8:4d:c9:6f:4b:9e:65:47:e5:6d:90:38:d4:37:9f:\n        3e:62:5d:92:42:4b:2f:6f:7a:83:e9:b1:c7:25:30:9a:3f:b0:\n        ec:92:5b:34:7d:d0:48:a1:82:03:bd:97:a3:ad:36:12:4b:34:\n        d7:fc:ac:38:ef:7e:4f:d0:60:4c:86:3e:f3:54:5f:1a:cc:6c:\n        f6:3c:5a:52:21:28:38:86:e2:4c:ca:9e:c4:0c:a6:5f:d9:cd:\n        9d:9e:4b:b0:98:45:25:3d:01:01:2d:7d:b9:8f:f2:25:f3:53:\n        b1:7d:55:45:8d:af:b0:51:a8:1a:1a:d8:c4:3c:6c:b9:82:c5:\n        3c:cc:5c:24:5a:18:da:0f:93:c6:05:b6:6f:9d:3f:84:0e:c3:\n        ad:24:f5:3b:3a:df:ec:04:77:f3:5e:a5:2d:32:b7:e6:cf:f4:\n        bf:1a:c4:3e:40:2a:67:06:f6:ab:07:2f:50:62:e9:3d:33:47:\n        8f:04:6c:b7:dc:25:10:a6:33:d4:2f:1b:89:25:50:07:c4:da:\n        1e:b4:70:af:ee:e7:67:83:63:e2:e0:3f:2f:0c:d7:f3:6c:cc:\n        07:b7:b1:ee:ac:ca:34:0a:c4:a3:bb:e7:0b:e2:b9:c8:7e:cc:\n        f4:d9:1a:c3:f5:60:bc:e0:ac:41:2d:69:2a:0b:b1:e2:20:a1:\n        88:a4:5e:f8:d1:87:38:06:58:95:13:51:c3:68:e2:b2:23:a1:\n        44:7e:de:5a:b2:3d:ce:81:7c:26:07:0e:e9:56:5c:52:35:60:\n        02:68:0d:af:38:59:1d:37:f5:79:5d:96:63:54:97:51:27:19:\n        20:11:f0:af:b0:95:5e:9c:5a:7c:34:3e:a0:98:99:96:ba:6e:\n        ed:a8:ce:72:b6:68:97:8c:fb:5e:8e:c3:e9:56:ae:08:72:51:\n        be:62:19:eb:b4:54:3d:4c:31:2e:0a:b3:6c:44:81:86:18:3a:\n        1d:c9:7a:b5:fa:41:97:63:4c:3c:46:a1:84:6f:b2:78:3a:0f:\n        cc:ea:be:0b:30:02:8f:90:ee:de:d2:71:22:6d:3a:97:77:68:\n        a0:bd:1d:9e:e6:9f:b2:2b`;\n\nconst IN_CRL_PEM_RSA_CRL_EXTENSIONS = `-----BEGIN X509 CRL-----\nMIIE0DCCArgCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV\nBAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN\nMjQwODI1MTIzNzEwWhcNMjQwOTI0MTIzNzEwWjA9MDsCAhAAFw0yNDA4MjUxMjA4\nNDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAgEw\nggH9MIHiBgNVHRIEgdowgdegFAYEKgMEBaAMFgpDdXN0b21OYW1lgQ5jYUBleGFt\ncGxlLmNvbYYSaHR0cDovL2V4YW1wbGUuY29tgg5jYS5leGFtcGxlLmNvbYcEwKgB\nAaSBhDCBgTELMAkGA1UEBhMCVVMxFTATBgNVBAgMDEV4YW1wbGVTdGF0ZTEUMBIG\nA1UEBwwLRXhhbXBsZUNpdHkxEzARBgNVBAoMCkV4YW1wbGVPcmcxFDASBgNVBAsM\nC0V4YW1wbGVVbml0MRowGAYDVQQDDBFFeGFtcGxlQ29tbW9uTmFtZTB9BgNVHSME\ndjB0gBS4ya39qFBU1YVuNItMYC+wSbaHRqFGpEQwQjELMAkGA1UEBhMCVUsxDzAN\nBgNVBAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBD\nQYIUN11L9r18EXzMRhr/1y8sJvgeSz0wLQYDVR0uBCYwJDAioCCgHoYcaHR0cDov\nL2V4YW1wbGUuY29tL2RlbHRhLWNybDBbBgNVHR8EVDBSMCGgH6AdhhtodHRwOi8v\nZXhhbXBsZS5jb20vZnVsbC1jcmwwIaAfoB2GG2xkYXA6Ly9leGFtcGxlLmNvbS9m\ndWxsLWNybDAKoAigBocEfwAAATALBgNVHRQEBAICHkIwDQYJKoZIhvcNAQELBQAD\nggIBAF/9L4aGmId2igw7+MfDxokevIJkJX/MkmHpXBl1b4hL85FGD7OPCmn47VzC\nWejlc/AQB7mWyUugvrVEq/FiCO8a8Fieyjw5uCYz0eiNnuvHVRGM2mOEkiA0I/rn\nF5AFB1YfCFGXPyRkXNRbOBE91mhOzh1H9PX2qVnj5l3KsPE/7YuteacR0TkfkRJa\nBXLic+5F/CCV/J/iYR7LncuLUlhBfsosG/ucHL70EytlfX6CBWY3kBbmj7nd497T\nQG392+m9xp7MIsJAS+3qEzwJAfni6zUV0fWh/ucOl8BIjHEh97VqI3+8yzhdXfkF\n2gkfpkqJQY0+5OO1VSRYTlQNld3QjN/VVJjatfHyaXfPCx4VEKW1kWYo+0zxO4SL\nSB/+S/o99bCeNy1MXqEvy5HoDwFHePXGsAEPHWPdj7EWm7g9T/Fl1iSR6hpohvDD\nK4LaGdVhzvCraLIh8H7XW3KztvZvDQejYQAgADW0UO0rFHJ1XXhKYSqXNGnfDt+3\ncRpt2XxSxt5HJtHlatiI25PuBMNWV2Zod4RHB/8UEvs1KC7dcwkAiCEY+E3o/zkC\nrdZ/8XtNf5a4WSN/D7pPsfsO6SE+7lxkJ+UQcZLXAz8b5ArPTlWt2HdJIBEVs25K\nFAkizyldhnAcNHFk7XN94eTLNeD6hUbFL9pNHiSmKu5A9YW0\n-----END X509 CRL-----`;\n\nconst OUT_CRL_PEM_RSA_CRL_EXTENSIONS = `Certificate Revocation List (CRL):\n    Version: 2 (0x1)\n    Signature Algorithm: SHA256withRSA\n    Issuer:\n        C  = UK\n        ST = London\n        O  = BB\n        CN = Test Root CA\n    Last Update: Sun, 25 Aug 2024 12:37:10 GMT\n    Next Update: Tue, 24 Sep 2024 12:37:10 GMT\n\tCRL extensions:\n        2.5.29.46:\n        \tUnsupported CRL extension. Try openssl CLI.\n        X509v3 Authority Key Identifier:\n        \tkeyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46\n        \tDirName:/C=UK/ST=London/O=BB/CN=Test Root CA\n        \tserial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D\n        X509v3 CRL Distribution Points:\n            Full Name:\n                URI:http://example.com/full-crl\n            Full Name:\n                URI:ldap://example.com/full-crl\n            Full Name:\n                IP:127.0.0.1\n        X509v3 CRL Number:\n        \t1E42\n        X509v3 Issuer Alternative Name:\n            OtherName:1.2.3.4.5::CustomName\n            EMAIL:ca@example.com\n            URI:http://example.com\n            DNS:ca.example.com\n            IP:192.168.1.1\n            DIR:/C=US/ST=ExampleState/L=ExampleCity/O=ExampleOrg/OU=ExampleUnit/CN=ExampleCommonName\nRevoked Certificates:\n    Serial Number: 1000\n        Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT\n    \tCRL entry extensions:\n            X509v3 CRL Reason Code:\n                Key Compromise\n            Invalidity Date:\n            \tSun, 25 Aug 2024 00:00:00 GMT\nSignature Value:\n        5f:fd:2f:86:86:98:87:76:8a:0c:3b:f8:c7:c3:c6:89:1e:bc:\n        82:64:25:7f:cc:92:61:e9:5c:19:75:6f:88:4b:f3:91:46:0f:\n        b3:8f:0a:69:f8:ed:5c:c2:59:e8:e5:73:f0:10:07:b9:96:c9:\n        4b:a0:be:b5:44:ab:f1:62:08:ef:1a:f0:58:9e:ca:3c:39:b8:\n        26:33:d1:e8:8d:9e:eb:c7:55:11:8c:da:63:84:92:20:34:23:\n        fa:e7:17:90:05:07:56:1f:08:51:97:3f:24:64:5c:d4:5b:38:\n        11:3d:d6:68:4e:ce:1d:47:f4:f5:f6:a9:59:e3:e6:5d:ca:b0:\n        f1:3f:ed:8b:ad:79:a7:11:d1:39:1f:91:12:5a:05:72:e2:73:\n        ee:45:fc:20:95:fc:9f:e2:61:1e:cb:9d:cb:8b:52:58:41:7e:\n        ca:2c:1b:fb:9c:1c:be:f4:13:2b:65:7d:7e:82:05:66:37:90:\n        16:e6:8f:b9:dd:e3:de:d3:40:6d:fd:db:e9:bd:c6:9e:cc:22:\n        c2:40:4b:ed:ea:13:3c:09:01:f9:e2:eb:35:15:d1:f5:a1:fe:\n        e7:0e:97:c0:48:8c:71:21:f7:b5:6a:23:7f:bc:cb:38:5d:5d:\n        f9:05:da:09:1f:a6:4a:89:41:8d:3e:e4:e3:b5:55:24:58:4e:\n        54:0d:95:dd:d0:8c:df:d5:54:98:da:b5:f1:f2:69:77:cf:0b:\n        1e:15:10:a5:b5:91:66:28:fb:4c:f1:3b:84:8b:48:1f:fe:4b:\n        fa:3d:f5:b0:9e:37:2d:4c:5e:a1:2f:cb:91:e8:0f:01:47:78:\n        f5:c6:b0:01:0f:1d:63:dd:8f:b1:16:9b:b8:3d:4f:f1:65:d6:\n        24:91:ea:1a:68:86:f0:c3:2b:82:da:19:d5:61:ce:f0:ab:68:\n        b2:21:f0:7e:d7:5b:72:b3:b6:f6:6f:0d:07:a3:61:00:20:00:\n        35:b4:50:ed:2b:14:72:75:5d:78:4a:61:2a:97:34:69:df:0e:\n        df:b7:71:1a:6d:d9:7c:52:c6:de:47:26:d1:e5:6a:d8:88:db:\n        93:ee:04:c3:56:57:66:68:77:84:47:07:ff:14:12:fb:35:28:\n        2e:dd:73:09:00:88:21:18:f8:4d:e8:ff:39:02:ad:d6:7f:f1:\n        7b:4d:7f:96:b8:59:23:7f:0f:ba:4f:b1:fb:0e:e9:21:3e:ee:\n        5c:64:27:e5:10:71:92:d7:03:3f:1b:e4:0a:cf:4e:55:ad:d8:\n        77:49:20:11:15:b3:6e:4a:14:09:22:cf:29:5d:86:70:1c:34:\n        71:64:ed:73:7d:e1:e4:cb:35:e0:fa:85:46:c5:2f:da:4d:1e:\n        24:a6:2a:ee:40:f5:85:b4`;\n\n\nTestRegister.addTests([\n    {\n        name: \"Parse X.509 CRL: Example PEM encoded CRL with RSA signature\",\n        input: IN_CRL_PEM_RSA,\n        expectedOutput: OUT_CRL_PEM_RSA,\n        recipeConfig: [\n            {\n                \"op\": \"Parse X.509 CRL\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse X.509 CRL: Example PEM encoded CRL with RSA signature, CRL Reason and Invalidity Date\",\n        input: IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE,\n        expectedOutput: OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE,\n        recipeConfig: [\n            {\n                \"op\": \"Parse X.509 CRL\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n    {\n        name: \"Parse X.509 CRL: Example PEM encoded CRL with RSA signature and CRL Extensions\",\n        input: IN_CRL_PEM_RSA_CRL_EXTENSIONS,\n        expectedOutput: OUT_CRL_PEM_RSA_CRL_EXTENSIONS,\n        recipeConfig: [\n            {\n                \"op\": \"Parse X.509 CRL\",\n                \"args\": [\"PEM\"]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/PowerSet.mjs",
    "content": "/**\n * Power Set tests.\n *\n * @author d98762625\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Power set: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Power Set\",\n                args: [\",\"],\n            },\n        ],\n    },\n    {\n        name: \"Power set\",\n        input: \"1 2 4\",\n        expectedOutput: \"\\n4\\n2\\n1\\n2 4\\n1 4\\n1 2\\n1 2 4\\n\",\n        recipeConfig: [\n            {\n                op: \"Power Set\",\n                args: [\" \"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Protobuf.mjs",
    "content": "/**\n * Protobuf tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Protobuf Decode: no schema\",\n        input: \"0d1c0000001203596f751a024d65202b2a0a0a066162633132331200\",\n        expectedOutput: JSON.stringify({\n            \"1\": 28,\n            \"2\": \"You\",\n            \"3\": \"Me\",\n            \"4\": 43,\n            \"5\": {\n                \"1\": \"abc123\",\n                \"2\": {}\n            }\n        }, null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                \"op\": \"Protobuf Decode\",\n                \"args\": [\"\", false, false]\n            }\n        ]\n    },\n    {\n        name: \"Protobuf Decode: partial schema, no unknown fields\",\n        input: \"0d1c0000001203596f751a024d65202b2a0a0a066162633132331200\",\n        expectedOutput: JSON.stringify({\n            \"Apple\": [\n                28\n            ],\n            \"Carrot\": [\n                \"Me\"\n            ],\n            \"Banana\": \"You\"\n        }, null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                \"op\": \"Protobuf Decode\",\n                \"args\": [\n                    `message Test {\n                    repeated fixed32 Apple = 1;\n                    optional string Banana = 2;\n                    repeated string Carrot = 3;\n                    }`,\n                    false,\n                    false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Protobuf Decode: partial schema, show unknown fields\",\n        input: \"0d1c0000001203596f751a024d65202b2a0a0a066162633132331200\",\n        expectedOutput: JSON.stringify({\n            \"Test\": {\n                \"Apple\": [\n                    28\n                ],\n                \"Carrot\": [\n                    \"Me\"\n                ],\n                \"Banana\": \"You\"\n            },\n            \"Unknown Fields\": {\n                \"4\": 43,\n                \"5\": {\n                    \"1\": \"abc123\",\n                    \"2\": {}\n                }\n            }\n        }, null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                \"op\": \"Protobuf Decode\",\n                \"args\": [\n                    `message Test {\n                    repeated fixed32 Apple = 1;\n                    optional string Banana = 2;\n                    repeated string Carrot = 3;\n                    }`,\n                    true,\n                    false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Protobuf Decode: full schema, no unknown fields\",\n        input: \"0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ff00000000000000\",\n        expectedOutput: JSON.stringify({\n            \"Apple\": [\n                28\n            ],\n            \"Carrot\": [\n                \"Me\"\n            ],\n            \"Banana\": \"You\",\n            \"Date\": 43,\n            \"Elderberry\": {\n                \"Fig\": \"abc123\",\n                \"Grape\": {}\n            },\n            \"Huckleberry\": 255\n        }, null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                \"op\": \"Protobuf Decode\",\n                \"args\": [\n                    `message Test {\n                        repeated fixed32 Apple = 1;\n                        optional string Banana = 2;\n                        repeated string Carrot = 3;\n                        optional int32 Date = 4;\n                        optional subTest Elderberry = 5;\n                        optional fixed64 Huckleberry = 6;\n                    }\n                    message subTest {\n                        optional string Fig = 1;\n                        optional subSubTest Grape = 2;\n                    }\n                    message subSubTest {}`,\n                    false,\n                    false\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Protobuf Decode: partial schema, show unknown fields, show types\",\n        input: \"0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801\",\n        expectedOutput: JSON.stringify({\n            \"Test\": {\n                \"Carrot (string)\": [\n                    \"Me\"\n                ],\n                \"Banana (string)\": \"You\",\n                \"Date (int32)\": 43,\n                \"Imbe (Options)\": \"Option1\"\n            },\n            \"Unknown Fields\": {\n                \"field #1: 32-Bit (e.g. fixed32, float)\": 28,\n                \"field #5: L-delim (e.g. string, message)\": {\n                    \"field #1: L-delim (e.g. string, message)\": \"abc123\",\n                    \"field #2: L-delim (e.g. string, message)\": {}\n                },\n                \"field #6: 64-Bit (e.g. fixed64, double)\": 3029774971578\n            }\n        }, null, 4),\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                \"op\": \"Protobuf Decode\",\n                \"args\": [\n                    `message Test {\n                        optional string Banana = 2;\n                        repeated string Carrot = 3;\n                        optional int32 Date = 4;\n                        optional Options Imbe = 7;\n                    }\n                    message subTest {\n                        optional string Fig = 1;\n                        optional subSubTest Grape = 2;\n                    }\n                    message subSubTest {}\n                    enum Options {\n                    Option0 = 0;\n                    Option1 = 1;\n                    Option2 = 2;\n                    }`,\n                    true,\n                    true\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Protobuf Encode\",\n        input: JSON.stringify({\n            \"Apple\": [\n                28\n            ],\n            \"Banana\": \"You\",\n            \"Carrot\": [\n                \"Me\"\n            ],\n            \"Date\": 43,\n            \"Elderberry\": {\n                \"Fig\": \"abc123\",\n                \"Grape\": {}\n            },\n            \"Huckleberry\": [3029774971578],\n            \"Imbe\": 1\n        }, null, 4),\n        expectedOutput: \"0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801\",\n        recipeConfig: [\n            {\n                \"op\": \"Protobuf Encode\",\n                \"args\": [\n                    `message Test {\n                        repeated fixed32 Apple = 1;\n                        optional string Banana = 2;\n                        repeated string Carrot = 3;\n                        optional int32 Date = 4;\n                        optional subTest Elderberry = 5;\n                        repeated fixed64 Huckleberry = 6;\n                        optional Options Imbe = 7;\n                    }\n                    message subTest {\n                        optional string Fig = 1;\n                        optional subSubTest Grape = 2;\n                    }\n                    message subSubTest {}\n                    enum Options {\n                    Option0 = 0;\n                    Option1 = 1;\n                    Option2 = 2;\n                    }`\n                ]\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\n                    \"None\",\n                    0\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Protobuf Encode: incomplete schema\",\n        input: JSON.stringify({\n            \"Apple\": [\n                28\n            ],\n            \"Banana\": \"You\",\n            \"Carrot\": [\n                \"Me\"\n            ],\n            \"Date\": 43,\n            \"Elderberry\": {\n                \"Fig\": \"abc123\",\n                \"Grape\": {}\n            },\n            \"Huckleberry\": [3029774971578],\n            \"Imbe\": 1\n        }, null, 4),\n        expectedOutput: \"1203596f75202b2a0a0a06616263313233120031ba32a96cc1020000\",\n        recipeConfig: [\n            {\n                \"op\": \"Protobuf Encode\",\n                \"args\": [\n                    `message Test {\n                        optional string Banana = 2;\n                        optional int32 Date = 4;\n                        optional subTest Elderberry = 5;\n                        repeated fixed64 Huckleberry = 6;\n                    }\n                    message subTest {\n                        optional string Fig = 1;\n                        optional subSubTest Grape = 2;\n                    }\n                    message subSubTest {}\n                    enum Options {\n                    Option0 = 0;\n                    Option1 = 1;\n                    Option2 = 2;\n                    }`\n                ]\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\n                    \"None\",\n                    0\n                ]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/PubKeyFromCert.mjs",
    "content": "/**\n * Public Key from Certificate\n *\n * @author cplussharp\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst RSA_CERT = `-----BEGIN CERTIFICATE-----\nMIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL\nBQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3\nMTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA\nMEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB\n757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p\npnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP\nBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx\ngmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z\nIA==\n-----END CERTIFICATE-----`;\n\nconst RSA_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM\nYKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==\n-----END PUBLIC KEY-----`;\n\nconst EC_P256_CERT = `-----BEGIN CERTIFICATE-----\nMIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw\nFTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx\nNzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO\nPQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr\nqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM\ngcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD\nVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN\nac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80\nylxt\n-----END CERTIFICATE-----`;\n\nconst EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ\ngusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==\n-----END PUBLIC KEY-----`;\n\nconst DSA_CERT = `-----BEGIN CERTIFICATE-----\nMIIEXzCCBA2gAwIBAgIUYYcPJB8UQLzUnqkGJvs3J4RI0OgwCwYJYIZIAWUDBAMC\nMBMxETAPBgNVBAMMCERTQSBUZXN0MB4XDTIzMTAxNTAwMjEzNVoXDTMzMTAxMjAw\nMjEzNVowEzERMA8GA1UEAwwIRFNBIFRlc3QwggNCMIICNQYHKoZIzjgEATCCAigC\nggEBALoLV+uz7vMYZCIuwXNkgZawvDgZAG1T7IiG030WgqesRNncuoUQOmAJCiuN\nzkjVNSY08rabex/RIkWILvxP91SlzhA9t9+dp87p238ecxGa1sD2re+y35RP7IxN\nT33633NtwGItZ3BqqAhoMmuwwwxau0E8zwYodTTlwTRp4QVPpMH1eJCUBeEzcWP5\nZZ1lRNhR5M2TqzSU3ya5/4c3a9rI86h9VIVgw8yVvw3y6yclzjALm2ntD5riskdM\nZ6mMkfYQwEbIGRTELX6A7LZ0lX1CislenF9ASb2E4g2nGcMQ0uSGzA4W9mf6wwmP\nS6iwX5+Qu/i6jCm5i37fQ1H5HHUCHQDA+UnPHM6PZEgfFen8djZpl/cl05MpWk+d\nnikFAoIBADXOTpBw0WA+UihxDG+6qqM05kxVMYmz6IRZ/06ffZSGVFN6Bx1i0s3v\nkzM5V8GsKpkKkSk7V8fTQnAIIlMmt1Y7ff+ng7+TfYotMrvvEYlolYK06J2WWoUA\n8iKp8+n58vdoky+xZmuGmcvCAojVDbEeU2wEqYE1PzrHCSOoOiKB2P4fOhyuF+qx\nE8nkzURIg2RmSSkqWOkXiWyKyfpUaB+4cEisp4ThENEPmdntE1vLh2r7EOIxpE5D\n0NAy2wFKqe3ljfgE6XsPZKgVAguRDVpzdmL6WDY7DM/BcS726vx+kX55QDkszvec\nraNirnir2QrB/a0JQjF6Y62yGmG7GF8DggEFAAKCAQBpN+w0N0b5IIAspXnlJ9yu\nB6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb0rba\nL5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyrHudV\nXu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp02e8H\nzvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFMMeKN\nK/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9LjKj\no1MwUTAdBgNVHQ4EFgQUE+xZdvgiDIFWKQskMYnNaZ3iPHAwHwYDVR0jBBgwFoAU\nE+xZdvgiDIFWKQskMYnNaZ3iPHAwDwYDVR0TAQH/BAUwAwEB/zALBglghkgBZQME\nAwIDPwAwPAIcZbtf4+bjXEGQqNs6IglLrOgIjYF46q7qCNfXmQIcMKUtH3S6sDJE\n3ds9eL+oC+HPFlfUNfUiU30aDA==\n-----END CERTIFICATE-----`;\n\nconst DSA_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt\nU+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff\nnafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G\nKHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF\nYMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf\nQEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ\nzxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM\nVTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/\np4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs\nBKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI\nrKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi\n+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB\nBQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa\nVS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0\nIejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC\n8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ\nN/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1\nZJMV1HnACFLOjJNdJEn5LohpfS4yow==\n-----END PUBLIC KEY-----`;\n\nconst ED25519_CERT = `-----BEGIN CERTIFICATE-----\nMIIBQjCB9aADAgECAhRjPJhrdNco5LzpsIs0vSLLaZaZ0DAFBgMrZXAwFzEVMBMG\nA1UEAwwMRWQyNTUxOSBUZXN0MB4XDTIzMTAxNTAwMjMwOFoXDTMzMTAxMjAwMjMw\nOFowFzEVMBMGA1UEAwwMRWQyNTUxOSBUZXN0MCowBQYDK2VwAyEAELP6AflXwsuZ\n5q4NDIO0LP2iCdKRvds4nwsUmRhOw3ijUzBRMB0GA1UdDgQWBBRfxS9q0IemWxkH\n4mwAwzr9dQx2xzAfBgNVHSMEGDAWgBRfxS9q0IemWxkH4mwAwzr9dQx2xzAPBgNV\nHRMBAf8EBTADAQH/MAUGAytlcANBAI/+03iVq4yJ+DaLVs61w41cVX2UxKvquSzv\nlllkpkclM9LH5dLrw4ArdTjS9zAjzY/02WkphHhICHXt3KqZTwI=\n-----END CERTIFICATE-----`;\n\n/*\nconst ED25519_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g=\n-----END PUBLIC KEY-----`;\n*/\n\nconst ED448_CERT = `-----BEGIN CERTIFICATE-----\nMIIBijCCAQqgAwIBAgIUZaCS7zEjOnQ7O4KUFym6fJF5vl8wBQYDK2VxMBUxEzAR\nBgNVBAMMCkVkNDQ4IFRlc3QwHhcNMjMxMDE1MDAyMzI1WhcNMzMxMDEyMDAyMzI1\nWjAVMRMwEQYDVQQDDApFZDQ0OCBUZXN0MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/Ov\nBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRlEHQwXsNYLZTtY2Jra6AWhbVYYaEAo1Mw\nUTAdBgNVHQ4EFgQUJFrepAf9YXrmDMSAzrMeYQmosd0wHwYDVR0jBBgwFoAUJFre\npAf9YXrmDMSAzrMeYQmosd0wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXEDcwA+YiZj\npuFr2aogfV1qg/ixk7qLi25BbKVNR6+7PEUjo7+4yBn9qnLbAHUGnHn7E96pSey9\nVkLqpoDNMRcM3Eb6h3AJpQM0oxGj8q9arjDXqJkXgaO2e0tVn8KKVfy7S8qO72Kd\nrWzZowcOjnWKhXm7JgA=\n-----END CERTIFICATE-----`;\n\n/*\nconst ED448_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl\nEHQwXsNYLZTtY2Jra6AWhbVYYaEA\n-----END PUBLIC KEY-----`\n*/\n\nTestRegister.addTests([\n    {\n        name: \"Public Key from Certificate: Missing footer\",\n        input: RSA_CERT.substring(0, RSA_CERT.length / 2),\n        expectedOutput: \"PEM footer '-----END CERTIFICATE-----' not found\",\n        recipeConfig: [\n            {\n                op: \"Public Key from Certificate\",\n                args: [],\n            }\n        ],\n    },\n\n    // test RSA certificate\n    {\n        name: \"Public Key from Certificate: RSA\",\n        input: RSA_CERT,\n        expectedOutput: (RSA_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Certificate\",\n                args: [],\n            }\n        ],\n    },\n\n    // test EC certificate\n    {\n        name: \"Public Key from Certificate: EC\",\n        input: EC_P256_CERT,\n        expectedOutput: (EC_P256_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Certificate\",\n                args: [],\n            }\n        ],\n    },\n\n    // test DSA certificate\n    {\n        name: \"Public Key from Certificate: DSA\",\n        input: DSA_CERT,\n        expectedOutput: (DSA_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Certificate\",\n                args: [],\n            }\n        ],\n    },\n\n    // test EdDSA certificates\n    {\n        name: \"Public Key from Certificate: Ed25519\",\n        input: ED25519_CERT,\n        expectedOutput: \"Unsupported public key type\",\n        recipeConfig: [\n            {\n                op: \"Public Key from Certificate\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"Public Key from Certificate: Ed448\",\n        input: ED448_CERT,\n        expectedOutput: \"Unsupported public key type\",\n        recipeConfig: [\n            {\n                op: \"Public Key from Certificate\",\n                args: [],\n            }\n        ],\n    },\n\n    // test multi-input\n    {\n        name: \"Public Key from Certificate: Multiple certificates\",\n        input: RSA_CERT + \"\\n\" + EC_P256_CERT,\n        expectedOutput: (RSA_PUBKEY + \"\\n\" + EC_P256_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Certificate\",\n                args: [],\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/PubKeyFromPrivKey.mjs",
    "content": "/**\n * Public Key from Private Key\n *\n * @author cplussharp\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst RSA_PRIVKEY_PKCS1 = `-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL\nNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF\nF7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL\nWQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p\n6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf\nRWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw\nNSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o=\n-----END RSA PRIVATE KEY-----`;\n\nconst RSA_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY-----\nMIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp\nruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx\n2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB\nvuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox\n/EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM\ndQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3\nJ0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe\nJzOq+X832g==\n-----END PRIVATE KEY-----`;\n\nconst RSA_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM\nYKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==\n-----END PUBLIC KEY-----`;\n\nconst EC_P256_PRIVKEY_SEC1 = `-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49\nAwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC\na6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==\n-----END EC PRIVATE KEY-----`;\n\nconst EC_P256_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw\nYBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC\n6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p\n-----END PRIVATE KEY-----`;\n\nconst EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ\ngusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==\n-----END PUBLIC KEY-----`;\n\nconst DSA_PRIVKEY_TRAD = `-----BEGIN DSA PRIVATE KEY-----\nMIIDTQIBAAKCAQEAugtX67Pu8xhkIi7Bc2SBlrC8OBkAbVPsiIbTfRaCp6xE2dy6\nhRA6YAkKK43OSNU1JjTytpt7H9EiRYgu/E/3VKXOED23352nzunbfx5zEZrWwPat\n77LflE/sjE1Pffrfc23AYi1ncGqoCGgya7DDDFq7QTzPBih1NOXBNGnhBU+kwfV4\nkJQF4TNxY/llnWVE2FHkzZOrNJTfJrn/hzdr2sjzqH1UhWDDzJW/DfLrJyXOMAub\nae0PmuKyR0xnqYyR9hDARsgZFMQtfoDstnSVfUKKyV6cX0BJvYTiDacZwxDS5IbM\nDhb2Z/rDCY9LqLBfn5C7+LqMKbmLft9DUfkcdQIdAMD5Sc8czo9kSB8V6fx2NmmX\n9yXTkylaT52eKQUCggEANc5OkHDRYD5SKHEMb7qqozTmTFUxibPohFn/Tp99lIZU\nU3oHHWLSze+TMzlXwawqmQqRKTtXx9NCcAgiUya3Vjt9/6eDv5N9ii0yu+8RiWiV\ngrTonZZahQDyIqnz6fny92iTL7Fma4aZy8ICiNUNsR5TbASpgTU/OscJI6g6IoHY\n/h86HK4X6rETyeTNREiDZGZJKSpY6ReJbIrJ+lRoH7hwSKynhOEQ0Q+Z2e0TW8uH\navsQ4jGkTkPQ0DLbAUqp7eWN+ATpew9kqBUCC5ENWnN2YvpYNjsMz8FxLvbq/H6R\nfnlAOSzO95yto2KueKvZCsH9rQlCMXpjrbIaYbsYXwKCAQBpN+w0N0b5IIAspXnl\nJ9yuB6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb\n0rbaL5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyr\nHudVXu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp0\n2e8HzvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFM\nMeKNK/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9\nLjKjAhwpK4MOpkKEu+y308fZ+yZXypZW2m9Y/wOT0L4g\n-----END DSA PRIVATE KEY-----`;\n\nconst DSA_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY-----\nMIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4\nGQBtU+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4Q\nPbffnafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtB\nPM8GKHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOo\nfVSFYMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJ\nXpxfQEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0A\nwPlJzxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqj\nNOZMVTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdW\nO33/p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2x\nHlNsBKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgf\nuHBIrKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1a\nc3Zi+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhf\nBB4CHCkrgw6mQoS77LfTx9n7JlfKllbab1j/A5PQviA=\n-----END PRIVATE KEY-----`;\n\nconst DSA_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt\nU+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff\nnafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G\nKHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF\nYMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf\nQEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ\nzxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM\nVTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/\np4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs\nBKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI\nrKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi\n+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB\nBQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa\nVS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0\nIejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC\n8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ\nN/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1\nZJMV1HnACFLOjJNdJEn5LohpfS4yow==\n-----END PUBLIC KEY-----`;\n\nconst ED25519_PRIVKEY = `-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIC18vtoHINC8Mo9dTIqOrBs3J28ZvHrwzRq57g2kpV98\n-----END PRIVATE KEY-----`;\n\n/*\nconst ED25519_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g=\n-----END PUBLIC KEY-----`;\n*/\n\nconst ED448_PRIVKEY = `-----BEGIN PRIVATE KEY-----\nMEcCAQAwBQYDK2VxBDsEOWdGJ06bDcWznJhBoQqPeTfsCe+AvBv1n7KfIGYzR4tv\n1kcwHnbxlemnCMgqvbrRXaLuFUBysUZThA==\n-----END PRIVATE KEY-----`;\n\n/*\nconst ED448_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl\nEHQwXsNYLZTtY2Jra6AWhbVYYaEA\n-----END PUBLIC KEY-----`;\n*/\n\nTestRegister.addTests([\n    {\n        name: \"Public Key from Private Key: Missing footer\",\n        input: RSA_PRIVKEY_PKCS1.substring(0, RSA_PRIVKEY_PKCS1.length / 2),\n        expectedOutput: \"PEM footer '-----END RSA PRIVATE KEY-----' not found\",\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n\n    // test RSA\n    {\n        name: \"Public Key from Private Key: RSA PKCS#1\",\n        input: RSA_PRIVKEY_PKCS1,\n        expectedOutput: (RSA_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"Public Key from Private Key: RSA PKCS#8\",\n        input: RSA_PRIVKEY_PKCS8,\n        expectedOutput: (RSA_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n\n    // test EC certificate\n    {\n        name: \"Public Key from Private Key: EC SEC1\",\n        input: EC_P256_PRIVKEY_SEC1,\n        expectedOutput: (EC_P256_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"Public Key from Private Key: EC PKCS#8\",\n        input: EC_P256_PRIVKEY_PKCS8,\n        expectedOutput: (EC_P256_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n\n    // test DSA\n    {\n        name: \"Public Key from Private Key: DSA Traditional\",\n        input: DSA_PRIVKEY_TRAD,\n        expectedOutput: (DSA_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"Public Key from Private Key: DSA PKCS#8\",\n        input: DSA_PRIVKEY_PKCS8,\n        expectedOutput: \"DSA Private Key in PKCS#8 is not supported\",\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n\n    // test EdDSA\n    {\n        name: \"Public Key from Private Key: Ed25519\",\n        input: ED25519_PRIVKEY,\n        expectedOutput: \"Unsupported key type: Error: malformed PKCS8 private key(code:004)\",\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n    {\n        name: \"Public Key from Private Key: Ed448\",\n        input: ED448_PRIVKEY,\n        expectedOutput: \"Unsupported key type: Error: malformed PKCS8 private key(code:004)\",\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    },\n\n    // test multi-input\n    {\n        name: \"Public Key from Private Key: Multiple keys\",\n        input: RSA_PRIVKEY_PKCS8 + \"\\n\" + EC_P256_PRIVKEY_PKCS8,\n        expectedOutput: (RSA_PUBKEY + \"\\n\" + EC_P256_PUBKEY + \"\\n\").replace(/\\r/g, \"\").replace(/\\n/g, \"\\r\\n\"),\n        recipeConfig: [\n            {\n                op: \"Public Key from Private Key\",\n                args: [],\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/RAKE.mjs",
    "content": "/**\n * RAKE, Rapid Automatic Keyword Extraction tests.\n *\n * @author sw5678\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"RAKE: Basic Example\",\n        \"input\": \"test1 test2. test2\",\n        \"expectedOutput\": \"Scores: , Keywords: \\n3.5, test1 test2\\n1.5, test2\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"RAKE\",\n                \"args\": [\"\\\\s\", \"\\\\.\\\\s|\\\\n\", \"i,me,my,myself,we,our\"]\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/RC6.mjs",
    "content": "/**\n * RC6 cipher tests.\n *\n * Test vectors from the IETF draft:\n * \"Test Vectors for RC6 and RC5\"\n * https://datatracker.ietf.org/doc/html/draft-krovetz-rc6-rc5-vectors-00\n *\n * @author Medjedtxm\n * @copyright Crown Copyright 2026\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    // ============================================================\n    // IETF TEST VECTORS - RC6-8/12/4\n    // ============================================================\n    {\n        name: \"RC6-8/12/4: IETF vector encrypt\",\n        input: \"00010203\",\n        expectedOutput: \"aefc4612\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"00010203\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 8, 12\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-8/12/4: IETF vector decrypt\",\n        input: \"aefc4612\",\n        expectedOutput: \"00010203\",\n        recipeConfig: [\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"00010203\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 8, 12\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // IETF TEST VECTORS - RC6-16/16/8\n    // ============================================================\n    {\n        name: \"RC6-16/16/8: IETF vector encrypt\",\n        input: \"0001020304050607\",\n        expectedOutput: \"2ff0b68eaeffad5b\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"0001020304050607\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 16, 16\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-16/16/8: IETF vector decrypt\",\n        input: \"2ff0b68eaeffad5b\",\n        expectedOutput: \"0001020304050607\",\n        recipeConfig: [\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"0001020304050607\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 16, 16\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // IETF TEST VECTORS - RC6-32/20/16 (AES standard)\n    // ============================================================\n    {\n        name: \"RC6-32/20/16: IETF vector encrypt (AES standard)\",\n        input: \"000102030405060708090a0b0c0d0e0f\",\n        expectedOutput: \"3a96f9c7f6755cfe46f00e3dcd5d2a3c\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 32, 20\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-32/20/16: IETF vector decrypt (AES standard)\",\n        input: \"3a96f9c7f6755cfe46f00e3dcd5d2a3c\",\n        expectedOutput: \"000102030405060708090a0b0c0d0e0f\",\n        recipeConfig: [\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 32, 20\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // IETF TEST VECTORS - RC6-64/24/24\n    // ============================================================\n    {\n        name: \"RC6-64/24/24: IETF vector encrypt\",\n        input: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\",\n        expectedOutput: \"c002de050bd55e5d36864ab9853338e6dc4a1326c6bdaaeb1bc9e4fd67886617\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f1011121314151617\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 64, 24\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-64/24/24: IETF vector decrypt\",\n        input: \"c002de050bd55e5d36864ab9853338e6dc4a1326c6bdaaeb1bc9e4fd67886617\",\n        expectedOutput: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\",\n        recipeConfig: [\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f1011121314151617\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 64, 24\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // IETF TEST VECTORS - RC6-128/28/32\n    // ============================================================\n    {\n        name: \"RC6-128/28/32: IETF vector encrypt\",\n        input: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f\",\n        expectedOutput: \"4ed87c64baffecd4303ee6a79aafaef575b351c024272be70a70b4a392cfc157dba52d529a79e83845bf43d67545383aed3dbf4f0d23640e44cbf6cdaa034dcb\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 128, 28\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-128/28/32: IETF vector decrypt\",\n        input: \"4ed87c64baffecd4303ee6a79aafaef575b351c024272be70a70b4a392cfc157dba52d529a79e83845bf43d67545383aed3dbf4f0d23640e44cbf6cdaa034dcb\",\n        expectedOutput: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f\",\n        recipeConfig: [\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 128, 28\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // IETF TEST VECTORS - RC6-24/4/0 (non-power-of-2)\n    // ============================================================\n    {\n        name: \"RC6-24/4/0: IETF non-standard vector encrypt (w=24, empty key)\",\n        input: \"000102030405060708090a0b\",\n        expectedOutput: \"0177982579be2ee3303269b9\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 24, 4\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-24/4/0: IETF non-standard vector decrypt (w=24, empty key)\",\n        input: \"0177982579be2ee3303269b9\",\n        expectedOutput: \"000102030405060708090a0b\",\n        recipeConfig: [\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 24, 4\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // IETF TEST VECTORS - RC6-80/4/12 (non-power-of-2)\n    // ============================================================\n    {\n        name: \"RC6-80/4/12: IETF non-standard vector encrypt (w=80)\",\n        input: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627\",\n        expectedOutput: \"26d9d6128601d06dec3817d401f1c0ff715473543875da417c2116d1e87c919a49311b00b4e17962\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 80, 4\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-80/4/12: IETF non-standard vector decrypt (w=80)\",\n        input: \"26d9d6128601d06dec3817d401f1c0ff715473543875da417c2116d1e87c919a49311b00b4e17962\",\n        expectedOutput: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627\",\n        recipeConfig: [\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 80, 4\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // ADDITIONAL KEY SIZE TESTS - RC6-32 (192-bit and 256-bit keys)\n    // ============================================================\n    {\n        name: \"RC6-32/20/24: 192-bit key encrypt\",\n        input: \"000102030405060708090a0b0c0d0e0f\",\n        expectedOutput: \"a68a14ff1342262a2bbd21f7966615eb\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f1011121314151617\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 32, 20\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-32/20/32: 256-bit key encrypt\",\n        input: \"000102030405060708090a0b0c0d0e0f\",\n        expectedOutput: \"921c3ecd43d9426a90089334d67aea2e\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Hex\", \"NO\", 32, 20\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // ROUND-TRIP TESTS - One per word size to verify encrypt/decrypt\n    // ============================================================\n    {\n        name: \"RC6-8 Round-trip: CBC mode\",\n        input: \"Hello World!\",\n        expectedOutput: \"Hello World!\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"mysecret\", option: \"UTF8\" },\n                    { string: \"abcd\", option: \"UTF8\" },\n                    \"CBC\", \"Raw\", \"Hex\", \"PKCS5\", 8, 12\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"mysecret\", option: \"UTF8\" },\n                    { string: \"abcd\", option: \"UTF8\" },\n                    \"CBC\", \"Hex\", \"Raw\", \"PKCS5\", 8, 12\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-16 Round-trip: CBC mode\",\n        input: \"The quick brown fox\",\n        expectedOutput: \"The quick brown fox\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"secretkey1234567\", option: \"UTF8\" },\n                    { string: \"initvec!\", option: \"UTF8\" },\n                    \"CBC\", \"Raw\", \"Hex\", \"PKCS5\", 16, 16\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"secretkey1234567\", option: \"UTF8\" },\n                    { string: \"initvec!\", option: \"UTF8\" },\n                    \"CBC\", \"Hex\", \"Raw\", \"PKCS5\", 16, 16\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-32 Round-trip: CBC mode\",\n        input: \"The quick brown fox jumps over the lazy dog\",\n        expectedOutput: \"The quick brown fox jumps over the lazy dog\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"aabbccddeeff00112233445566778899\", option: \"Hex\" },\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    \"CBC\", \"Raw\", \"Hex\", \"PKCS5\", 32, 20\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"aabbccddeeff00112233445566778899\", option: \"Hex\" },\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    \"CBC\", \"Hex\", \"Raw\", \"PKCS5\", 32, 20\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-64 Round-trip: CBC mode\",\n        input: \"RC6 with 64-bit words is powerful!\",\n        expectedOutput: \"RC6 with 64-bit words is powerful!\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    \"CBC\", \"Raw\", \"Hex\", \"PKCS5\", 64, 24\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    \"CBC\", \"Hex\", \"Raw\", \"PKCS5\", 64, 24\n                ]\n            }\n        ]\n    },\n    {\n        name: \"RC6-128 Round-trip: ECB mode\",\n        input: \"RC6 with 128-bit words provides massive block size for testing purposes!\",\n        expectedOutput: \"RC6 with 128-bit words provides massive block size for testing purposes!\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Raw\", \"Hex\", \"PKCS5\", 128, 28\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Raw\", \"PKCS5\", 128, 28\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // STREAM MODES TEST - Verify CFB/OFB/CTR work correctly\n    // ============================================================\n    {\n        name: \"RC6-32 Round-trip: CTR mode\",\n        input: \"CTR mode test message\",\n        expectedOutput: \"CTR mode test message\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    { string: \"00000000000000000000000000000001\", option: \"Hex\" },\n                    \"CTR\", \"Raw\", \"Hex\", \"PKCS5\", 32, 20\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    { string: \"00000000000000000000000000000001\", option: \"Hex\" },\n                    \"CTR\", \"Hex\", \"Raw\", \"PKCS5\", 32, 20\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // CUSTOM ROUNDS TEST - Verify non-standard round count works\n    // ============================================================\n    {\n        name: \"RC6-32 Round-trip: Custom 8 rounds\",\n        input: \"Testing custom rounds\",\n        expectedOutput: \"Testing custom rounds\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Raw\", \"Hex\", \"PKCS5\", 32, 8\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Raw\", \"PKCS5\", 32, 8\n                ]\n            }\n        ]\n    },\n\n    // ============================================================\n    // EDGE CASE TEST - Padding boundary\n    // ============================================================\n    {\n        name: \"RC6-32 Round-trip: Exact block size input\",\n        input: \"1234567890123456\",\n        expectedOutput: \"1234567890123456\",\n        recipeConfig: [\n            {\n                op: \"RC6 Encrypt\",\n                args: [\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Raw\", \"Hex\", \"PKCS5\", 32, 20\n                ]\n            },\n            {\n                op: \"RC6 Decrypt\",\n                args: [\n                    { string: \"00112233445566778899aabbccddeeff\", option: \"Hex\" },\n                    { string: \"\", option: \"Hex\" },\n                    \"ECB\", \"Hex\", \"Raw\", \"PKCS5\", 32, 20\n                ]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/RSA.mjs",
    "content": "/**\n * RSA tests.\n *\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nimport {ASCII_TEXT, UTF8_TEXT, ALL_BYTES} from \"../../samples/Ciphers.mjs\";\n\nconst PEM_PRIV_2048 = `-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwfaUOpUEutKyU3wkCv6kYunz4MqxzSuTSckRz1IxwZtwIiqq\n+ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vXaYG57HZ8iinXnHgy1vr8i+fWYITB\nRMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53pUmI08ADxXPA+ZymPyZhZyxrj5Jq\n2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6UqBzTky/ohv6EGhjyqnaskTWwLWK6P\ndKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR/KVkXGAHX99J/wh6QTS+bsyJ2/Mw\nDf6NWdh3iP7msLNl/GqL+HunhHjrthvvWlODDwIDAQABAoIBAApKwLvJC3q0UmUO\nqcTxlRxwiJHNf5jA7qxUIH9NP7mju1P8ypy/KFi7Ys+oUKOOIPdU5Pe0E8sqN6pp\ntcH8oL4G9awf72TPapLxZ9UzdTIhR6VQdgbl8XhSO2M1vkoMejmZlX7SOesOaKE9\n1+vwDA43tCx0PF7+UOeN0d549WMphvw3VkSInO/MYpobCGra4YdrhYOhFMyLEGgA\nzCyVUOxi538tyyFtK2EEQdcMtvVA6SECjF4xD/qrme0LelIj/L1Uhiu+SOzYt4y+\nQLHL6zhJVfOejWxjeI7BhodkTV2D53n4svfizRgyYEb6iLPW3nlMYIlAksYaxxB9\nnR3sMHECgYEA9RU+8J5A8RnBcwnlc2X1xEW2PN7+A1MeWPQwFqRwIokgvGbCtwjG\nPwwNUYJCTBhfGhsISeCBOSYrDGTHsNH+tqFW2zlq61BolYl56jb1KgWzMOX8dak4\nsgXIuBbvyuFNk08VMIzwcA76ka/Iuu/nN9ZOM2UYpdpGG+CTOoIFULECgYEAyppm\nI+yAtrUn/BFmwmC8va4vqXlBFjvdkfX/71ywCpHIouLucMV7bILJu0nSCpmL1A7R\nDT6qo0p5g+Dxl/+O2VyC5D89PBvcuT1+HtEZGLOoKZnojbSrwDApGbzQi57GoQR6\n/SRjsdAmoelY8PFz2s2ZLJ4NkrZXYvkT1Tu8/78CgYEA4MAvC/HUlEWORbTZmk3y\nZ5+WU5QbVWkv91tXjiwWOVWPk7aY8ck2JDMlM45ExgvDiuknXLhpSMNbzu3MwraQ\n42JpiHjLOChxAFEmYEct5O99OGZwcmZQ+9CaFVfTZzXeMizfvbpB9EGIP3n4lpXS\ncD4zUKZxSAc3K/FyksERpsECgYEAhQPXeVBltQ68oKaAE6/VWqcIjbiY/dLyBkk+\n7dSpk1bhJefdadaN0NERRtARgXoLrn7Hy21QNILJwsaldwiGrbgqC1Zlipg0Ur3H\nls3rLyeMiTuNzbNHa5dy9H3dYT0t5Tr+0EHa3jvtkTGVfiLX0FhZb0yZVrA2MTmc\nRsvAqxsCgYAgXy4qytgfzo5/bBt306NbtMEW3dWBWF77HAz4N1LynKZRUrAAK4rz\nBVmXFUaNQOg0q8WJG+iFF79u2UnL8iZ5GoPMcpvifsZgef1OHnQnFrfyXSr0fXIm\nxq8eZS0DpLvKGffCW03B9VDRHanE37Tng8lbgOtaufuVzFa1bCuLUA==\n-----END RSA PRIVATE KEY-----`;\n\nconst PEM_PUB_2048 = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwfaUOpUEutKyU3wkCv6k\nYunz4MqxzSuTSckRz1IxwZtwIiqq+ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vX\naYG57HZ8iinXnHgy1vr8i+fWYITBRMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53\npUmI08ADxXPA+ZymPyZhZyxrj5Jq2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6Uq\nBzTky/ohv6EGhjyqnaskTWwLWK6PdKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR\n/KVkXGAHX99J/wh6QTS+bsyJ2/MwDf6NWdh3iP7msLNl/GqL+HunhHjrthvvWlOD\nDwIDAQAB\n-----END PUBLIC KEY-----`;\n\nTestRegister.addTests([\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-1\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-1\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, ASCII\",\n        input: ASCII_TEXT,\n        expectedOutput: ASCII_TEXT,\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-1\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-1\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, UTF-8\",\n        input: UTF8_TEXT.substr(0, 100),\n        expectedOutput: UTF8_TEXT.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-1\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-1\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, All bytes\",\n        input: ALL_BYTES.substr(0, 100),\n        expectedOutput: ALL_BYTES.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-1\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-1\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/MD5, nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"MD5\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"MD5\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/MD5, ASCII\",\n        input: ASCII_TEXT,\n        expectedOutput: ASCII_TEXT,\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"MD5\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"MD5\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/MD5, UTF-8\",\n        input: UTF8_TEXT.substr(0, 100),\n        expectedOutput: UTF8_TEXT.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"MD5\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"MD5\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/MD5, All bytes\",\n        input: ALL_BYTES.substr(0, 100),\n        expectedOutput: ALL_BYTES.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"MD5\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"MD5\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-256\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-256\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, ASCII\",\n        input: ASCII_TEXT,\n        expectedOutput: ASCII_TEXT,\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-256\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-256\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, UTF-8\",\n        input: UTF8_TEXT.substr(0, 100),\n        expectedOutput: UTF8_TEXT.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-256\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-256\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, All bytes\",\n        input: ALL_BYTES.substr(0, 100),\n        expectedOutput: ALL_BYTES.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-256\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-256\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-384\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-384\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, ASCII\",\n        input: ASCII_TEXT,\n        expectedOutput: ASCII_TEXT,\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-384\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-384\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, UTF-8\",\n        input: UTF8_TEXT.substr(0, 80),\n        expectedOutput: UTF8_TEXT.substr(0, 80),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-384\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-384\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, All bytes\",\n        input: ALL_BYTES.substr(0, 100),\n        expectedOutput: ALL_BYTES.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-384\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-384\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-512\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-512\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, ASCII\",\n        input: ASCII_TEXT.substr(0, 100),\n        expectedOutput: ASCII_TEXT.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-512\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-512\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, UTF-8\",\n        input: UTF8_TEXT.substr(0, 60),\n        expectedOutput: UTF8_TEXT.substr(0, 60),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-512\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-512\"]\n            }\n        ]\n    },\n    {\n        name: \"RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, All bytes\",\n        input: ALL_BYTES.substr(0, 100),\n        expectedOutput: ALL_BYTES.substr(0, 100),\n        recipeConfig: [\n            {\n                \"op\": \"RSA Encrypt\",\n                \"args\": [PEM_PUB_2048, \"RSA-OAEP\", \"SHA-512\"]\n            },\n            {\n                \"op\": \"RSA Decrypt\",\n                \"args\": [PEM_PRIV_2048, \"\", \"RSA-OAEP\", \"SHA-512\"]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Rabbit.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Rabbit: RFC Test vector, without IV 1\",\n        input: \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"b15754f036a5d6ecf56b45261c4af70288e8d815c59c0c397b696c4789c68aa7f416a1c3700cd451da68d1881673d696\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: RFC Test vector, without IV 2\",\n        input: \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"3d2df3c83ef627a1e97fc38487e2519cf576cd61f4405b8896bf53aa8554fc19e5547473fbdb43508ae53b20204d4c5e\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"912813292e3d36fe3bfc62f1dc51c3ac\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: RFC Test vector, without IV 3\",\n        input: \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"0cb10dcda041cdac32eb5cfd02d0609b95fc9fca0f17015a7b7092114cff3ead9649e5de8bfc7f3f924147ad3a947428\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"8395741587e0c733e9e9ab01c09b0043\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: RFC Test vector, with IV 1\",\n        input: \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"c6a7275ef85495d87ccd5d376705b7ed5f29a6ac04f5efd47b8f293270dc4a8d2ade822b29de6c1ee52bdb8a47bf8f66\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: RFC Test vector, with IV 2\",\n        input: \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"1fcd4eb9580012e2e0dccc9222017d6da75f4e10d12125017b2499ffed936f2eebc112c393e738392356bdd012029ba7\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"c373f575c1267e59\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: RFC Test vector, with IV 3\",\n        input: \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n        expectedOutput: \"445ad8c805858dbf70b6af23a151104d96c8f27947f42c5baeae67c6acc35b039fcbfc895fa71c17313df034f01551cb\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"a6eb561ad2f41727\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: generated stream should be XORed with the input\",\n        input: \"cedda96c054e3ddd93da7ed05e2a4b7bdb0c00fe214f03502e2708b2c2bfc77aa2311b0b9af8aa78d119f92b26db0a6b\",\n        expectedOutput: \"7f8afd9c33ebeb3166b13bf64260bc7953e4d8ebe4d30f69554e64f54b794ddd5627bac8eaf47e290b7128a330a8dcfd\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: least significant bits should be used for the last block\",\n        input: \"0000000000000000\",\n        expectedOutput: \"f56b45261c4af702\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: invalid key length\",\n        input: \"\",\n        expectedOutput: \"Invalid key length: 8 bytes (expected: 16)\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Rabbit: invalid IV length\",\n        input: \"\",\n        expectedOutput: \"Invalid IV length: 4 bytes (expected: 0 or 8)\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"00000000\"},\n                    \"Big\", \"Hex\", \"Hex\"\n                ]\n            }\n        ]\n    },\n    {\n\t// this testcase is taken from the first example on Crypto++ Wiki\n\t// https://www.cryptopp.com/wiki/Rabbit\n        name: \"Rabbit: little-endian mode (Crypto++ compatible)\",\n        input: \"Rabbit stream cipher test\",\n        expectedOutput: \"1ae2d4edcf9b6063b00fd6fda0b223aded157e77031cf0440b\",\n        recipeConfig: [\n            {\n                \"op\": \"Rabbit\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"23c2731e8b5469fd8dabb5bc592a0f3a\"},\n                    {\"option\": \"Hex\", \"string\": \"712906405ef03201\"},\n                    \"Little\", \"Raw\", \"Hex\"\n                ]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Regex.mjs",
    "content": "/**\n * StrUtils tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Regex: non-HTML op\",\n        input: \"/<>\",\n        expectedOutput: \"/<>\",\n        recipeConfig: [\n            {\n                \"op\": \"Regular expression\",\n                \"args\": [\"User defined\", \"\", true, true, false, false, false, false, \"Highlight matches\"]\n            },\n            {\n                \"op\": \"Remove whitespace\",\n                \"args\": [true, true, true, true, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Regex: Dot matches all\",\n        input: \"Hello\\nWorld\",\n        expectedOutput: \"Hello\\nWorld\",\n        recipeConfig: [\n            {\n                \"op\": \"Regular expression\",\n                \"args\": [\"User defined\", \".+\", true, true, true, false, false, false, \"List matches\"]\n            }\n        ],\n    },\n    {\n        name: \"Regex: Astral off\",\n        input: \"𝌆😆\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Regular expression\",\n                \"args\": [\"User defined\", \"\\\\pS\", true, true, false, false, false, false, \"List matches\"]\n            }\n        ],\n    },\n    {\n        name: \"Regex: Astral on\",\n        input: \"𝌆😆\",\n        expectedOutput: \"𝌆\\n😆\",\n        recipeConfig: [\n            {\n                \"op\": \"Regular expression\",\n                \"args\": [\"User defined\", \"\\\\pS\", true, true, false, false, true, false, \"List matches\"]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Register.mjs",
    "content": "/**\n * Register tests\n *\n * @author tlwr [toby@toby.codes]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Register: RC4 key\",\n        input: \"http://malwarez.biz/beacon.php?key=0e932a5c&data=8db7d5ebe38663a54ecbb334e3db11\",\n        expectedOutput: \"zNu5y53uBoU2rm7qhq9ijjnVHSlJ9PJ/zpp+xL/to8qIBzkDwKzUNQ==\",\n        recipeConfig: [\n            {\n                op: \"Register\",\n                args: [\"key=([\\\\da-f]*)\", true, false]\n            },\n            {\n                op: \"RC4\",\n                args: [\n                    {\n                        \"option\": \"Hex\",\n                        \"string\": \"$R0\"\n                    }, \"Hex\", \"Latin1\"\n                ]\n            },\n            {\n                op: \"To Base64\",\n                args: [\"A-Za-z0-9+/=\"]\n            }\n        ]\n    },\n    {\n        name: \"Register: AES key\",\n        input: \"51e201d463698ef5f717f71f5b4712af20be674b3bff53d38546396ee61daac4908e319ca3fcf7089bfb6b38ea99e781d26e577ba9dd6f311a39420b8978e93014b042d44726caedf5436eaf652429c0df94b521676c7c2ce812097c277273c7c72cd89aec8d9fb4a27586ccf6aa0aee224c34ba3bfdf7aeb1ddd477622b91e72c9e709ab60f8daf731ec0cc85ce0f746ff1554a5a3ec291ca40f9e629a872592d988fdd834534aba79c1ad1676769a7c010bf04739ecdb65d95302371d629d9e37e7b4a361da468f1ed5358922d2ea752dd11c366f3017b14aa011d2af03c44f95579098a15e3cf9b4486f8ffe9c239f34de7151f6ca6500fe4b850c3f1c02e801caf3a24464614e42801615b8ffaa07ac8251493ffda7de5ddf3368880c2b95b030f41f8f15066add071a66cf60e5f46f3a230d397b652963a21a53f\",\n        expectedOutput: `\"You know,\" said Arthur, \"it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young.\"\n\"Why, what did she tell you?\"\n\"I don't know, I didn't listen.\"`,\n        recipeConfig: [\n            {\n                op: \"Register\",\n                args: [\"(.{32})\", true, false]\n            },\n            {\n                op: \"Drop bytes\",\n                args: [0, 32, false]\n            },\n            {\n                op: \"AES Decrypt\",\n                args: [\n                    {\n                        \"option\": \"Hex\",\n                        \"string\": \"1748e7179bd56570d51fa4ba287cc3e5\"\n                    },\n                    {\n                        \"option\": \"Hex\",\n                        \"string\": \"$R0\"\n                    },\n                    \"CTR\", \"Hex\", \"Raw\",\n                    {\n                        \"option\": \"Hex\",\n                        \"string\": \"\"\n                    },\n                    {\n                        \"option\": \"Hex\",\n                        \"string\": \"\"\n                    }\n                ]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/RisonEncodeDecode.mjs",
    "content": "/**\n * @author sg5506844 [sg5506844@gmail.com]\n * @copyright Crown Copyright 2021\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Rison Encode: Encoding example 1\",\n        input: JSON.stringify({ any: \"json\", yes: true }),\n        expectedOutput: \"(any:json,yes:!t)\",\n        recipeConfig: [\n            {\n                op: \"Rison Encode\",\n                args: [\"Encode\"]\n            }\n        ]\n    },\n    {\n        name: \"Rison Encode: Encoding example 2\",\n        input: JSON.stringify({ supportsObjects: true, ints: 435 }),\n        expectedOutput: \"ints:435,supportsObjects:!t\",\n        recipeConfig: [\n            {\n                op: \"Rison Encode\",\n                args: [\"Encode Object\"]\n            }\n        ]\n    },\n    {\n        name: \"Rison Encode: Encoding example 3\",\n        input: JSON.stringify([\"A\", \"B\", { supportsObjects: true }]),\n        expectedOutput: \"A,B,(supportsObjects:!t)\",\n        recipeConfig: [\n            {\n                op: \"Rison Encode\",\n                args: [\"Encode Array\"]\n            }\n        ]\n    },\n    {\n        name: \"Rison Encode: Object for an array\",\n        input: JSON.stringify({ supportsObjects: true, ints: 435 }),\n        expectedOutput: \"Rison Encode - rison.encode_array expects an array argument\",\n        expectedError: \"Rison Encode - rison.encode_array expects an array argument\",\n        recipeConfig: [\n            {\n                op: \"Rison Encode\",\n                args: [\"Encode Array\"]\n            }\n        ]\n    },\n    {\n        name: \"Rison Decode: Decoding example 1\",\n        input: \"(any:json,yes:!t)\",\n        expectedOutput: JSON.stringify({ any: \"json\", yes: true }, null, 4),\n        recipeConfig: [\n            {\n                op: \"Rison Decode\",\n                args: [\"Decode\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Rotate.mjs",
    "content": "/**\n * Rotate tests.\n *\n * @author Matt C [matt@artemisbot.uk]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n\nTestRegister.addTests([\n    {\n        name: \"Rotate left: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            },\n            {\n                op: \"Rotate left\",\n                args: [1, false],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"Rotate left: normal\",\n        input: \"61 62 63 31 32 33\",\n        expectedOutput: \"c2 c4 c6 62 64 66\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            },\n            {\n                op: \"Rotate left\",\n                args: [1, false],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"Rotate left: carry\",\n        input: \"61 62 63 31 32 33\",\n        expectedOutput: \"85 89 8c c4 c8 cd\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            },\n            {\n                op: \"Rotate left\",\n                args: [2, true],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"Rotate right: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            },\n            {\n                op: \"Rotate right\",\n                args: [1, false],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"Rotate right: normal\",\n        input: \"61 62 63 31 32 33\",\n        expectedOutput: \"b0 31 b1 98 19 99\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            },\n            {\n                op: \"Rotate right\",\n                args: [1, false],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"Rotate right: carry\",\n        input: \"61 62 63 31 32 33\",\n        expectedOutput: \"d8 58 98 cc 4c 8c\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"Space\"]\n            },\n            {\n                op: \"Rotate right\",\n                args: [2, true],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"Space\"]\n            }\n        ],\n    },\n    {\n        name: \"ROT13: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [true, true, true, 13]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: no shift amount\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [true, true, true, 0]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: normal\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"Gur Dhvpx Oebja Sbk Whzcrq Bire Gur Ynml Qbt. 3456789012\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [true, true, true, 13]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: negative shift amount\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"Gur Dhvpx Oebja Sbk Whzcrq Bire Gur Ynml Qbt. 7890123456\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [true, true, true, -13]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: full loop\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog. 6789012345\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [true, true, true, 26]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: full loop (negative shift amount)\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog. 4567890123\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [true, true, true, -26]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: lowercase only\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"Tur Qhvpx Bebja Fbk Jhzcrq Oire Tur Lnml Dbt. 0123456789\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [true, false, false, 13]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: uppercase only\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"Ghe Duick Orown Sox Wumped Bver Ghe Yazy Qog. 0123456789\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [false, true, false, 13]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: numbers only\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog. 5678901234\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [false, false, true, 5]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: numbers only (negative shift amount)\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog. 5678901234\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [false, false, true, 5]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: numbers only loop\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [false, false, true, 10]\n            },\n        ],\n    },\n    {\n        name: \"ROT13: numbers only loop (negative shift amount)\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog. 0123456789\",\n        recipeConfig: [\n            {\n                op: \"ROT13\",\n                args: [false, false, true, -10]\n            },\n        ],\n    },\n    {\n        name: \"ROT47: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"ROT47\",\n                args: [47]\n            },\n        ],\n    },\n    {\n        name: \"ROT47: normal\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog.\",\n        expectedOutput: \"%96 \\\"F:4< qC@H? u@I yF>A65 ~G6C %96 {2KJ s@8]\",\n        recipeConfig: [\n            {\n                op: \"ROT47\",\n                args: [47]\n            },\n        ],\n    },\n    {\n        name: \"ROT47: full loop\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog.\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog.\",\n        recipeConfig: [\n            {\n                op: \"ROT47\",\n                args: [94]\n            },\n        ],\n    },\n    {\n        name: \"ROT8000: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"ROT8000\",\n                args: []\n            },\n        ],\n    },\n    {\n        name: \"ROT8000: normal\",\n        input: \"The Quick Brown Fox Jumped Over The Lazy Dog.\",\n        expectedOutput: \"籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷\",\n        recipeConfig: [\n            {\n                op: \"ROT8000\",\n                args: []\n            },\n        ],\n    },\n    {\n        name: \"ROT8000: backward\",\n        input: \"籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷\",\n        expectedOutput: \"The Quick Brown Fox Jumped Over The Lazy Dog.\",\n        recipeConfig: [\n            {\n                op: \"ROT8000\",\n                args: []\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SIGABA.mjs",
    "content": "/**\n * SIGABA machine tests\n *\n * @author hettysymes\n * @copyright hettysymes 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"SIGABA: encrypt test 1\",\n        input: \"HELLO WORLD TESTING THE SIGABA MACHINE\",\n        expectedOutput: \"ULBECJCZJBJFVUDWAVRGRBMPSQHOTTNVQEESKN\",\n        recipeConfig: [\n            {\n                \"op\": \"SIGABA\",\n                \"args\": [\n                    \"BHKWECJDOVAYLFMITUGXRNSPZQ\", true, \"G\",\n                    \"CDTAKGQOZXLVJYHSWMIBPRUNEF\", false, \"L\",\n                    \"WAXHJZMBVDPOLTUYRCQFNSGKEI\", false, \"I\",\n                    \"HUSCWIMJQXDALVGBFTOYZKRPNE\", false, \"T\",\n                    \"RTLSMNKXFVWQUZGCHEJBYDAIPO\", false, \"B\",\n                    \"GHAQBRJWDMNZTSKLOUXYPFIECV\", false, \"N\",\n                    \"VFLGEMTCXZIQDYAKRPBONHWSUJ\", true, \"Q\",\n                    \"ZQCAYHRJNXPFLKIOTBUSVWMGDE\", false, \"B\",\n                    \"EZVSWPCTULGAOFDJNBIYMXKQHR\", false, \"J\",\n                    \"ELKSGDXMVYJUZNCAROQBPWHITF\", false, \"R\",\n                    \"3891625740\", \"3\",\n                    \"6297135408\", \"1\",\n                    \"2389715064\", \"8\",\n                    \"9264351708\", \"6\",\n                    \"9573086142\", \"6\",\n                    \"Encrypt\"\n                ]\n            }\n        ]\n    },\n    {\n        name: \"SIGABA: encrypt test 2\",\n        input: \"PCRPJZWSPNOHMWANBFBEIVZOXDQESPYDEFBNTHXLSICIRPKUATJVDUQFLZOKGHHHDUDIBRKUHVCGAGLBWVGFFXNDHKPFSPSCIIPCXUFRRHNYWIJFEJWQSGMSNJHWSLPKVXHUQUWIURHDIHIUTWGQFIYLTKEZAUESWYEKIWXUSSXWXBEHCXCUDQWKCISVPKXJVPOIJZWTUGKAORBMKBAQUZOPTSUSYZRROWQUYKNCLHVIHEGWCCONGVHEKCEXVYIPNILIXTXDELNGLJGMEQKKQJWZLPNXPOGIOSVAEAJYKWYJXXGKKPLVYAZGDCMNHMPLCYWDQSRBEMVVVZVFYJMRYGHJOTDOEQVRQOVXOGOVYGTXETFHAYELRYVDGWOFVGAOWPMHQYRZMNXVTAHWSKZLJDFVQPZGMHZWFNOBHSZHEDAEXIFCEEJYZDOEFOQWCXTKPJRUEITKHVCITCLKBUFNAFBYXELAYPBRGGGOCCAGLXXJXTSWCJHMHQPVUIBAGBDKAGEEEPKRGGICJQXSYHBNNAKGYODRAUWAEYHWCKHEQIBAONWQJYQCIFKDTOCTJMBJULWKMSNNMPXINHZQWUMJQLQKIPVZVRGYPCJJZMENWTFTUSPCSPRXHMZPCHCNQTUDCOUJHRKYQIUWWVEVVRYFDIYRQISNGPMQLNMCNMVBEWHNCUODHAGEVEUMKVZLEIKYAMPGVVSBYNRJMFCATDXTQCYXIBCXXKYEYHHYERQGQWZTWCEJBFQLRFFCIVVSZUKGLOTLNGLQNTIKTBBWVFMONUFKRLCJASEKUEEDDQDIVQMFRSJRNHYZJODFHSCJSDAIRUXOSDNFUFUFMNZYQIEGRUXKUPCHENUEZHRKYHDRJYSHLZNYRBWVXORMJMJRIRNSAJQRUMPCXUDFYRGKEAXQXJHPEWNIYIDURDGWIFEMSOFYYCFRZGMZXJNTLTJBBSZIULQSOMEVGCTCVXUHTIEHSPOPQYCJLPAJAPQPAQXE\",\n        expectedOutput: \"GMEXPPCMFGKUVGXZHVTCKXRSTJUYWNOKFVELWAHHSJBXGOEXCMLOVSIMCDMGEYMWWTFDUMCDUJEZITNPVVBGQDJEVHJXSKJAAUZWBELMSPUTXCUYPDTJCQXEBGWPWRSQLSNFMASCTJZDSFNKDDTAXLRGUPKCBNXMZPADJSFGGNYKRPYBNTYPTGVPACBEINILNACWFVKMJPGCEZFROEYYKTGYSQYMFSGVDOJJONNYEYSCCIXWLKUSJZDRVAQSNUWHMDJVDNNMPGOYRGQRSBGSPQKGCTFZQWSOXBWSQZDCRQJQAWZDPQEILGMMABIMCDPNSKAFCLPQGIRJCMGQREBEUHBYREXFABFMVZTZBDUMASVNUMHIYRSZLGNZFMVAIABLCUZLJLKKZPWEXDHYZFVSNRLCLNDRKLKSWRHQVQJRTHCNFZXDEXSLAXXOGMFVSGCJGAWOLGDMTLWSFNTCUVCCEACINRZAZZOGLEHHXLPHVKILBBJDPOOCILQKKGODSXOBDPZZDXHJLLBOBVFCHJVMUBUZZIKGCWGCYGXVEHHIJGPEQERWEZLILQNHPHALFKFMGADNELGBKILKIUETGDCBQUEOECWVFNOXTJKUYPWBNEKYSIKMVSAMBZGLIKDAOELRSTKFASEKABTUCPSFEGXXQGDFPSPVOLBHGLZSLLWCABSRKZDQQRKVCKXDGTIHPDNMPDZEXYFYKXZTPJPLYOFNLWAGKJEOHOYLMZELXIDWWNXPKEPUCKNNNHJLFYHPQNHMMCGMUPHSUSYYIVWTIMFKKKTFPGFTLTWWSQBRBMGBTZXPVULKNZIIKVTYLJFISGPTLZFTCLGNZOMVKZOIMUDGXRDDSVFRHRYWBEWHYLCUISYMRWAZZAQPJYXZQQKZLILOSHXUTQJFPTXQSREKSUDZTLGUDLUGOJMQHJRJHXCHQTKJULTWWQOXIRFRQEYBPJPEKXFIRMNATWNFBADOSIJVZYRYDBHDAEDJUVDHLDAU\",\n        recipeConfig: [\n            { \"op\": \"SIGABA\",\n                \"args\": [\n                    \"YCHLQSUGBDIXNZKERPVJTAWFOM\", true, \"A\",\n                    \"INPXBWETGUYSAOCHVLDMQKZJFR\", false, \"B\",\n                    \"WNDRIOZPTAXHFJYQBMSVEKUCGL\", false, \"C\",\n                    \"TZGHOBKRVUXLQDMPNFWCJYEIAS\", false, \"D\",\n                    \"YWTAHRQJVLCEXUNGBIPZMSDFOK\", true, \"E\",\n                    \"QSLRBTEKOGAICFWYVMHJNXZUDP\", false, \"F\",\n                    \"CHJDQIGNBSAKVTUOXFWLEPRMZY\", false, \"G\",\n                    \"CDFAJXTIMNBEQHSUGRYLWZKVPO\", true, \"H\",\n                    \"XHFESZDNRBCGKQIJLTVMUOYAPW\", false, \"I\",\n                    \"EZJQXMOGYTCSFRIUPVNADLHWBK\", false, \"J\",\n                    \"7591482630\", \"0\",\n                    \"3810592764\", \"1\",\n                    \"4086153297\", \"2\",\n                    \"3980526174\", \"3\",\n                    \"6497135280\", \"4\",\n                    \"Encrypt\"]\n            }\n        ]\n    },\n    {\n        name: \"SIGABA: decrypt test\",\n        input: \"AKDHFWAYSLHJDKXEVMJJHGKFTQBZPJPJILOVHMBYOAGBZVLLTQUOIKXFPUFNILBDPCAELMAPSXTLMUEGSDTNUDWGZDADBFELWWHKVPRZNDATDPYEHIDMTGAGPDEZYXFSASVKSBMXVOJQXRMHDBWUNZDTIIIVKHJYPIEUHAJCNBXNLGVFADEWIKXDJZBUTGOQBCQZWYKRVEENWRWWRYDNOAPGMODTPTUJZCLUCRDILJABNTBTWUEIJSJRQBUVCOUJJDWFMNNUHXBDFYXLGUMXQEAWSVHBXQGEOOGPYRVOAJLAIYIOHHEXACDTAWWCBGQRNPERSIKHTXPXKBUNACZLFZTRBMBBDDGKNBIQMFHZROCZZBGNZSJKDRRWPEQHLCFADNPWPWSLPIFNKBWQPMARUERGWUUODXSCOJQECGHIZRFRNRSXWSFWKISHHTUFRVXLHCQWGBMRDHCYDSVNIDDRSTODCGJSSBLUYOBGEWFOVKOZBJTYCAKMZECUGLJGTSZJNBOLTMUZRRSIGGRQHLRPMGLINASSMZOBNACKUMSFNIZAUFCPFXXOOTJQWWLZOFLGZLHJCWZJCRJKVOUDLNMKQATGVTOFHACAEKFLRWRTTMVRXHYGOTYPNBMUSKDAKXFCICUOVSWXGPQOYUUWTWRPQMEQCSDJMMJKELIHGEDYKWOVHVPUAIBFGAODXODXVFIIZIGWRZSBTIGXVHFABMMOPGVMLGHQQXNOEJRDLOBGUOWSELBHERZFSBLUODMOGIBNVGVGQYDBTKLOPNKZZNGLTTGZYYXIBAHZJDCILZXKNSJDHXWTYQLFHTUINTYSBPIXOPLOQHSAHGQPYUWYNPKMRBBBYIICCBBJRKWVLBIDBBEKJCXHLPUBMIGBUFYDPOCSRUNZOKMKJHMYFJZWFNHQZOGGRTNNUVLMRLDSAJIECTYCJKBYVNAXGCMGNVFJEDSATZQDQTYRBPLZKHAXMOVJZEDKINXKBUVWXXHTYUFO\",\n        expectedOutput: \"KTSOYDGMLPMVXEAJIATXCNQFXHBNCBXIJOCQGCQBRQSBYFOOEVPVXACBMIUIRNVMJHREKRHBSXJFSMWCKTTCYXJOFSJCQECXXCHTEGPEYSMYDHCSMODUAVBNLILYUIBBIXJCXXNQPCERRSMJTPQLMOXSKTRPWOFUSWXOYRJLBIJGIOYTEAEJEGGYAGSXNHNQTETANPWEGATHSBFLHCVHVIJUAKDVGQCWUSIFFFVAJYPJAFUYDXSLGPGESOUAYXBQIIOXWTXNOXLNCGWSUKVIBMOUGNHORYLSNVNNJLKKFDUAEISOLBLCXYHMDGVBVVVIKDLTMTDVWWJBXWXROVTJBXXKXLEWTTISKIUMYSACVUGGNANMCGUMFNQUXDLTHJNYTFIQEPKQQQSSROYJOILJYQXICXACWGOHCSHENXJILOMIIFCIOUDXDCINIVKIRJCVHWXSFQXMNRBJJWTPXNJADEOPEJBLKHKXNTORIRVRLXUXXAMKMODBXNLQCVJXVOTBRHXBBVJHPFEQFCRXYRRXHXPTXXSUESUTHUGOWQYQPQFPXQPVGEIRPQNKXXMBHIPECRUWFEWJUTYIKSMJSRQIQAIAMXTGDXSJIABHIGKUPJBCHWMVYTMQNQYGDHCNMBSVTPXNFRELFXXQYIOLCDEXDXDVSINICOXRMNSPICPQMOBIDJCNBJKXFAVMUXOXHERJIBIXLMXXULDXKXXHAQDXEXIWXOEEUGKSUGCMRWJDPYCYKXTPCOXMURAJCPRXKFJAJALERWRHVMFHOGMFHXGSXQDPJCJNXRQFGHKRCYTEBJDHPCMYFEAPWSVVMMBVUJJMCAAYURHUPVQVJYDCSNMQEMNIFEXYXIIXBVRVILXAUCBDXRJHGPKPYXHPPPNVSBBCDRLVVIYPKAKYIXTJVYDGVPHXULWMADBEICNIFKWUOOHEFNANDKOXMCVBVORLQYNXLULOEGVGWNKNMOHYVRSYSOVYGAKCGAWKGAIAQNQR\",\n        recipeConfig: [\n            { \"op\": \"SIGABA\",\n                \"args\": [\n                    \"YCHLQSUGBDIXNZKERPVJTAWFOM\", true, \"A\",\n                    \"INPXBWETGUYSAOCHVLDMQKZJFR\", false, \"B\",\n                    \"WNDRIOZPTAXHFJYQBMSVEKUCGL\", false, \"C\",\n                    \"TZGHOBKRVUXLQDMPNFWCJYEIAS\", false, \"D\",\n                    \"YWTAHRQJVLCEXUNGBIPZMSDFOK\", true, \"E\",\n                    \"QSLRBTEKOGAICFWYVMHJNXZUDP\", false, \"F\",\n                    \"CHJDQIGNBSAKVTUOXFWLEPRMZY\", false, \"G\",\n                    \"CDFAJXTIMNBEQHSUGRYLWZKVPO\", true, \"H\",\n                    \"XHFESZDNRBCGKQIJLTVMUOYAPW\", false, \"I\",\n                    \"EZJQXMOGYTCSFRIUPVNADLHWBK\", false, \"J\",\n                    \"7591482630\", \"0\",\n                    \"3810592764\", \"1\",\n                    \"4086153297\", \"2\",\n                    \"3980526174\", \"3\",\n                    \"6497135280\", \"4\",\n                    \"Decrypt\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/SM2.mjs",
    "content": "/**\n * SM2 Tests\n *\n * @author flakjacket95 [dflack95@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n/* Plaintexts */\n\nconst SMALL_PLAIN = \"I am a small plaintext\";\nconst LARGE_PLAIN = \"I am a larger plaintext, that will require the encryption KDF to generate a much larger key to properly encrypt me\";\n\n/* Test Key Parameters */\nconst PUBLIC_X = \"f7d903cab7925066c31150a92b31e548e63f954f92d01eaa0271fb2a336baef8\";\nconst PUBLIC_Y = \"fb0c45e410ef7a6cdae724e6a78dbff52562e97ede009e762b667d9b14adea6c\";\nconst PRIVATE_K = \"e74a72505084c3269aa9b696d603e3e08c74c6740212c11a31e26cdfe08bdf6a\";\n\nconst CURVE = \"sm2p256v1\";\n\n/* Decryption Test Ciphertext*/\n\nconst CIPHERTEXT_1 = \"9a31bc0adb4677cdc4141479e3949572a55c3e6fb52094721f741c2bd2e179aaa87be6263bc1be602e473be3d5de5dce97f8248948b3a7e15f9f67f64aef21575e0c05e6171870a10ff9ab778dbef24267ad90e1a9d47d68f757d57c4816612e9829f804025dea05a511cda39371c22a2828f976f72e\";\nconst CIPHERTEXT_2 = \"d3647d68568a2e7a4f8e843286be7bf2b4d80256697d19a73df306ae1a7e6d0364d942e23d2340606e7a2502a838b132f9242587b2ea7e4c207e87242eea8cae68f5ff4da2a95a7f6d350608ae5b6777e1d925bf9c560087af84aba7befba713130106ddb4082d803811bca3864594722f3198d58257fe4ba37f4aa540adf4cb0568bddd2d8140ad3030deea0a87e3198655cc4d22bfc3d73b1c4afec2ff15d68c8d1298d97132cace922ee8a4e41ca288a7e748b77ca94aa81dc283439923ae7939e00898e16fe5111fbe1d928d152b216a\";\nconst CIPHERTEXT_3 = \"5f340eeb4398fa8950ee3408d0e3fe34bf7728c9fdb060c94b916891b5c693610274160b52a7132a2bf16ad5cdb57d1e00da2f3ddbd55350729aa9c268b53e40c05ccce9912daa14406e8c132e389484e69757350be25351755dcc6c25c94b3c1a448b2cf8c2017582125eb6cf782055b199a875e966\";\nconst CIPHERTEXT_4 = \"0649bac46c3f9fd7fb3b2be4bff27414d634651efd02ca67d8c802bbc5468e77d035c39b581d6b56227f5d87c0b4efbea5032c0761139295ae194b9f1fce698f2f4b51d89fa5554171a1aad2e61fe9de89831aec472ecc5ab178ebf4d2230c1fb94fca03e536b87b9eba6db71ba9939260a08ffd230ca86cb45cf754854222364231bdb8b873791d63ad57a4b3fa5b6375388dc879373f5f1be9051bc5072a8afbec5b7b034e4907aa5bb4b6b1f50e725d09cb6a02e07ce20263005f6c9157ce05d3ea739d231d4f09396fb72aa680884d78\";\n\n\nTestRegister.addTests([\n    {\n        name: \"SM2 Decrypt: Small Input; Format One\",\n        input: CIPHERTEXT_1,\n        expectedOutput: SMALL_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C3C2\", CURVE]\n            }\n        ]\n    },\n    {\n        name: \"SM2 Decrypt: Large Input; Format One\",\n        input: CIPHERTEXT_2,\n        expectedOutput: LARGE_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C3C2\", CURVE]\n            }\n        ]\n    },\n    {\n        name: \"SM2 Decrypt: Small Input; Format Two\",\n        input: CIPHERTEXT_3,\n        expectedOutput: SMALL_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C2C3\", CURVE]\n            }\n        ]\n    },\n    {\n        name: \"SM2 Decrypt: Large Input; Format Two\",\n        input: CIPHERTEXT_4,\n        expectedOutput: LARGE_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C2C3\", CURVE]\n            }\n        ]\n    },\n    {\n        name: \"SM2 Encrypt And Decrypt: Small Input; Format One\",\n        input: SMALL_PLAIN,\n        expectedOutput: SMALL_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Encrypt\",\n                \"args\": [PUBLIC_X, PUBLIC_Y, \"C1C3C2\", CURVE],\n            },\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C3C2\", CURVE]\n            }\n        ]\n    },\n    {\n        name: \"SM2 Encrypt And Decrypt: Large Input; Format One\",\n        input: LARGE_PLAIN,\n        expectedOutput: LARGE_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Encrypt\",\n                \"args\": [PUBLIC_X, PUBLIC_Y, \"C1C3C2\", CURVE],\n            },\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C3C2\", CURVE]\n            }\n        ]\n    },\n    {\n        name: \"SM2 Encrypt And Decrypt: Small Input; Format Two\",\n        input: SMALL_PLAIN,\n        expectedOutput: SMALL_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Encrypt\",\n                \"args\": [PUBLIC_X, PUBLIC_Y, \"C1C2C3\", CURVE],\n            },\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C2C2\", CURVE]\n            }\n        ]\n    },\n    {\n        name: \"SM2 Encrypt And Decrypt: Large Input; Format Two\",\n        input: LARGE_PLAIN,\n        expectedOutput: LARGE_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM2 Encrypt\",\n                \"args\": [PUBLIC_X, PUBLIC_Y, \"C1C2C3\", CURVE],\n            },\n            {\n                \"op\": \"SM2 Decrypt\",\n                \"args\": [PRIVATE_K, \"C1C2C3\", CURVE]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SM4.mjs",
    "content": "/**\n * SM4 crypto tests.\n *\n * Test data used from IETF draft-ribose-cfrg-sm4-09, see:\n * https://tools.ietf.org/id/draft-ribose-cfrg-sm4-09.html\n *\n * @author swesven\n * @copyright 2021\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n/* Cleartexts */\nconst TWO_BLOCK_PLAIN = \"aa aa aa aa bb bb bb bb cc cc cc cc dd dd dd dd ee ee ee ee ff ff ff ff aa aa aa aa bb bb bb bb\";\nconst FOUR_BLOCK_PLAIN = \"aa aa aa aa aa aa aa aa bb bb bb bb bb bb bb bb cc cc cc cc cc cc cc cc dd dd dd dd dd dd dd dd ee ee ee ee ee ee ee ee ff ff ff ff ff ff ff ff aa aa aa aa aa aa aa aa bb bb bb bb bb bb bb bb\";\n/* Keys */\nconst KEY_1 = \"01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10\";\nconst KEY_2 = \"fe dc ba 98 76 54 32 10 01 23 45 67 89 ab cd ef\";\n/* IV */\nconst IV = \"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\";\n/* Ciphertexts */\nconst ECB_1 = \"5e c8 14 3d e5 09 cf f7 b5 17 9f 8f 47 4b 86 19 2f 1d 30 5a 7f b1 7d f9 85 f8 1c 84 82 19 23 04\";\nconst ECB_2 = \"c5 87 68 97 e4 a5 9b bb a7 2a 10 c8 38 72 24 5b 12 dd 90 bc 2d 20 06 92 b5 29 a4 15 5a c9 e6 00\";\n/* With PKCS#7 padding */\nconst ECB_1P =\"5e c8 14 3d e5 09 cf f7 b5 17 9f 8f 47 4b 86 19 2f 1d 30 5a 7f b1 7d f9 85 f8 1c 84 82 19 23 04 00 2a 8a 4e fa 86 3c ca d0 24 ac 03 00 bb 40 d2\";\nconst ECB_2P= \"c5 87 68 97 e4 a5 9b bb a7 2a 10 c8 38 72 24 5b 12 dd 90 bc 2d 20 06 92 b5 29 a4 15 5a c9 e6 00 a2 51 49 20 93 f8 f6 42 89 b7 8d 6e 8a 28 b1 c6\";\nconst CBC_1 = \"78 eb b1 1c c4 0b 0a 48 31 2a ae b2 04 02 44 cb 4c b7 01 69 51 90 92 26 97 9b 0d 15 dc 6a 8f 6d\";\nconst CBC_2 = \"0d 3a 6d dc 2d 21 c6 98 85 72 15 58 7b 7b b5 9a 91 f2 c1 47 91 1a 41 44 66 5e 1f a1 d4 0b ae 38\";\nconst OFB_1 = \"ac 32 36 cb 86 1d d3 16 e6 41 3b 4e 3c 75 24 b7 1d 01 ac a2 48 7c a5 82 cb f5 46 3e 66 98 53 9b\";\nconst OFB_2 = \"5d cc cd 25 a8 4b a1 65 60 d7 f2 65 88 70 68 49 33 fa 16 bd 5c d9 c8 56 ca ca a1 e1 01 89 7a 97\";\nconst CFB_1 = \"ac 32 36 cb 86 1d d3 16 e6 41 3b 4e 3c 75 24 b7 69 d4 c5 4e d4 33 b9 a0 34 60 09 be b3 7b 2b 3f\";\nconst CFB_2 = \"5d cc cd 25 a8 4b a1 65 60 d7 f2 65 88 70 68 49 0d 9b 86 ff 20 c3 bf e1 15 ff a0 2c a6 19 2c c5\";\nconst CTR_1 = \"ac 32 36 cb 97 0c c2 07 91 36 4c 39 5a 13 42 d1 a3 cb c1 87 8c 6f 30 cd 07 4c ce 38 5c dd 70 c7 f2 34 bc 0e 24 c1 19 80 fd 12 86 31 0c e3 7b 92 6e 02 fc d0 fa a0 ba f3 8b 29 33 85 1d 82 45 14\";\nconst CTR_2 = \"5d cc cd 25 b9 5a b0 74 17 a0 85 12 ee 16 0e 2f 8f 66 15 21 cb ba b4 4c c8 71 38 44 5b c2 9e 5c 0a e0 29 72 05 d6 27 04 17 3b 21 23 9b 88 7f 6c 8c b5 b8 00 91 7a 24 88 28 4b de 9e 16 ea 29 06\";\n\nTestRegister.addTests([\n    {\n        name: \"SM4 Encrypt: ECB 1, No padding\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: ECB_1,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: \"\", option: \"Hex\"}, \"ECB/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: ECB 2, No padding\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: ECB_2,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: \"\", option: \"Hex\"}, \"ECB/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: ECB 1, With padding\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: ECB_1P,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: \"\", option: \"Hex\"}, \"ECB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: ECB 2, With padding\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: ECB_2P,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: \"\", option: \"Hex\"}, \"ECB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: CBC 1\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: CBC_1,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CBC/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: CBC 2\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: CBC_2,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CBC/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: OFB1\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: OFB_1,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"OFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: OFB2\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: OFB_2,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"OFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: CFB1\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: CFB_1,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: CFB2\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: CFB_2,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: CTR1\",\n        input: FOUR_BLOCK_PLAIN,\n        expectedOutput: CTR_1,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CTR\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Encrypt: CTR1\",\n        input: FOUR_BLOCK_PLAIN,\n        expectedOutput: CTR_2,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Encrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CTR\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: ECB 1\",\n        input: ECB_1,\n        expectedOutput: TWO_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: \"\", option: \"Hex\"}, \"ECB/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: ECB 2\",\n        input: ECB_2,\n        expectedOutput: TWO_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: \"\", option: \"Hex\"}, \"ECB/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: CBC 1\",\n        input: CBC_1,\n        expectedOutput: TWO_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CBC/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: CBC 2\",\n        input: CBC_2,\n        expectedOutput: TWO_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CBC/NoPadding\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: OFB1\",\n        input: TWO_BLOCK_PLAIN,\n        expectedOutput: OFB_1,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"OFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: OFB2\",\n        input: OFB_2,\n        expectedOutput: TWO_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"OFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: CFB1\",\n        input: CFB_1,\n        expectedOutput: TWO_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: CFB2\",\n        input: CFB_2,\n        expectedOutput: TWO_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CFB\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: CTR1\",\n        input: CTR_1,\n        expectedOutput: FOUR_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_1, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CTR\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n    {\n        name: \"SM4 Decrypt: CTR1\",\n        input: CTR_2,\n        expectedOutput: FOUR_BLOCK_PLAIN,\n        recipeConfig: [\n            {\n                \"op\": \"SM4 Decrypt\",\n                \"args\": [{string: KEY_2, option: \"Hex\"}, {string: IV, option: \"Hex\"}, \"CTR\", \"Hex\", \"Hex\"]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SQLBeautify.mjs",
    "content": "/**\n * SQLBeautify tests.\n *\n * @author GCHQDeveloper581\n * @copyright Crown Copyright 2026\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"SQL Beautify - basic\",\n        input: \"SELECT MONTH, ID, RAIN_I, TEMP_F FROM STATS;\",\n        expectedOutput:\n`SELECT\n  MONTH,\n  ID,\n  RAIN_I,\n  TEMP_F\nFROM\n  STATS;`,\n        recipeConfig: [\n            {\n                op: \"SQL Beautify\",\n                args: [\"  \"],\n            },\n        ],\n    },\n    {\n        name: \"SQL Beautify - upsert\",\n        input: \"INSERT INTO Table1 SELECT * FROM (SELECT :Bind1 as Field1, :Bind2 as Field2, :id as id) as new_data ON DUPLICATE KEY UPDATE Field1 = new_data.Field1, Field2 = new_data.Field2;\",\n        expectedOutput:\n`INSERT INTO\n  Table1\nSELECT\n  *\nFROM\n  (\n    SELECT\n      :Bind1 as Field1,\n      :Bind2 as Field2,\n      :id as id\n  ) as new_data\nON DUPLICATE KEY UPDATE\n  Field1 = new_data.Field1,\n  Field2 = new_data.Field2;`,\n        recipeConfig: [\n            {\n                op: \"SQL Beautify\",\n                args: [\"  \"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Salsa20.mjs",
    "content": "/**\n * Salsa20 tests.\n *\n * @author joostrijneveld [joost@joostrijneveld.nl]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Salsa20: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes.\n\nSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"Salsa20\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Salsa20: no nonce\",\n        input: \"\",\n        expectedOutput: `Invalid nonce length: 0 bytes.\n\nSalsa20 uses a nonce of 8 bytes (64 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"Salsa20\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Salsa20: ECRYPT Set 1 vector# 0\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"e3 be 8f dd 8b ec a2 e3 ea 8e f9 47 5b 29 a6 e7 00 39 51 e1 09 7a 5c 38 d2 3b 7a 5f ad 9f 68 44 b2 2c 97 55 9e 27 23 c7 cb bd 3f e4 fc 8d 9a 07 44 65 2a 83 e7 2a 9c 46 18 76 af 4d 7e f1 a1 17 8d a2 b7 4e ef 1b 62 83 e7 e2 01 66 ab ca e5 38 e9 71 6e 46 69 e2 81 6b 6b 20 c5 c3 56 80 20 01 cc 14 03 a9 a1 17 d1 2a 26 69 f4 56 36 6d 6e bb 0f 12 46 f1 26 51 50 f7 93 cd b4 b2 53 e3 48 ae 20 3d 89 bc 02 5e 80 2a 7e 0e 00 62 1d 70 aa 36 b7 e0 7c b1 e7 d5 b3 8d 5e 22 2b 8b 0e 4b 84 07 01 42 b1 e2 95 04 76 7d 76 82 48 50 32 0b 53 68 12 9f dd 74 e8 61 b4 98 e3 be 8d 16 f2 d7 d1 69 57 be 81 f4 7b 17 d9 ae 7c 4f f1 54 29 a7 3e 10 ac f2 50 ed 3a 90 a9 3c 71 13 08 a7 4c 62 16 a9 ed 84 cd 12 6d a7 f2 8e 8a bf 8b b6 35 17 e1 ca 98 e7 12 f4 fb 2e 1a 6a ed 9f dc 73 29 1f aa 17 95 82 11 c4 ba 2e bd 58 38 c6 35 ed b8 1f 51 3a 91 a2 94 e1 94 f1 c0 39 ae ec 65 7d ce 40 aa 7e 7c 0a f5 7c ac ef a4 0c 9f 14 b7 1a 4b 34 56 a6 3e 16 2e c7 d8 d1 0b 8f fb 18 10 d7 10 01 b6 18 2f 9f 73 da 53 b8 54 05 c1 1f 7b 2d 89 0f a8 ae 0c 7f 2e 92 6d 8a 98 c7 ec 4e 91 b6 51 20 e9 88 34 96 31 a7 00 c6 fa ce c3 47 1c b0 41 36 56 e7 5e 30 94 56 58 40 84 d7 e1 2c 5b 43 a4 1c 43 ed 9a 04 8a bd 9b 88 0d a6 5f 6a 66 5a 20 fe 7b 77 cd 29 2f e6 2c ae 64 4b 7f 7d f6 9f 32 bd b3 31 90 3e 65 05 ce 44 fd c2 93 92 0c 6a 9e c7 05 7e 23 df 7d ad 29 8f 82 dd f4 ef b7 fd c7 bf c6 22 69 6a fc fd 0c dd cc 83 c7 e7 7f 11 a6 49 d7 9a cd c3 35 4e 96 35 ff 13 7e 92 99 33 a0 bd 6f 53 77 ef a1 05 a3 a4 26 6b 7c 0d 08 9d 08 f1 e8 55 cc 32 b1 5b 93 78 4a 36 e5 6a 76 cc 64 bc 84 77\",\n        recipeConfig: [\n            {\n                \"op\": \"Salsa20\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"80:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00\"},\n                    {\"option\": \"Hex\", \"string\": \"00:00:00:00:00:00:00:00\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"Salsa20: ECRYPT Set 6 vector# 3\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"71 da ee 51 42 d0 72 8b 41 b6 59 79 33 eb f4 67 e4 32 79 e3 09 78 67 70 78 94 16 02 62 9c bf 68 b7 3d 6b d2 c9 5f 11 8d 2b 3e 6e c9 55 da bb 6d c6 1c 41 43 bc 9a 9b 32 b9 9d be 68 66 16 6d c0 86 31 b7 d6 55 30 50 30 3d 72 52 c2 64 d3 a9 0d 26 c8 53 63 48 13 e0 9a d7 54 5a 6c e7 e8 4a 5d fc 75 ec 43 43 12 07 d5 31 99 70 b0 fa ad b0 e1 51 06 25 bb 54 37 2c 85 15 e2 8e 2a cc f0 a9 93 0a d1 5f 43 18 74 92 3d 2a 59 e2 0d 9f 2a 53 67 db a6 05 15 64 f1 50 28 7d eb b1 db 53 6f f9 b0 9a d9 81 f2 5e 50 10 d8 5d 76 ee 0c 30 5f 75 5b 25 e6 f0 93 41 e0 81 2f 95 c9 4f 42 ee ad 34 6e 81 f3 9c 58 c5 fa a2 c8 89 53 dc 0c ac 90 46 9d b2 06 3c b5 cd b2 2c 9e ae 22 af bf 05 06 fc a4 1d c7 10 b8 46 fb df e3 c4 68 83 dd 11 8f 3a 5e 8b 11 b6 af d9 e7 16 80 d8 66 65 57 30 1a 2d aa fb 94 96 c5 59 78 4d 35 a0 35 36 08 85 f9 b1 7b d7 19 19 77 de ea 93 2b 98 1e bd b2 90 57 ae 3c 92 cf ef f5 e6 c5 d0 cb 62 f2 09 ce 34 2d 4e 35 c6 96 46 cc d1 4e 53 35 0e 48 8b b3 10 a3 2f 8b 02 48 e7 0a cc 5b 47 3d f5 37 ce d3 f8 1a 01 4d 40 83 93 2b ed d6 2e d0 e4 47 b6 76 6c d2 60 4b 70 6e 9b 34 6c 44 68 be b4 6a 34 ec f1 61 0e bd 38 33 1d 52 bf 33 34 6a fe c1 5e ef b2 a7 69 9e 87 59 db 5a 1f 63 6a 48 a0 39 68 8e 39 de 34 d9 95 df 9f 27 ed 9e dc 8d d7 95 e3 9e 53 d9 d9 25 b2 78 01 05 65 ff 66 52 69 04 2f 05 09 6d 94 da 34 33 d9 57 ec 13 d2 fd 82 a0 06 62 83 d0 d1 ee b8 1b f0 ef 13 3b 7f d9 02 48 b8 ff b4 99 b2 41 4c d4 fa 00 30 93 ff 08 64 57 5a 43 74 9b f5 96 02 f2 6c 71 7f a9 6b 1d 05 76 97 db 08 eb c3 fa 66 4a 01 6a 67 dc ef 88 07 57 7c c3 a0 93 85 d3\",\n        recipeConfig: [\n            {\n                \"op\": \"Salsa20\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"0F:62:B5:08:5B:AE:01:54:A7:FA:4D:A0:F3:46:99:EC\"},\n                    {\"option\": \"Hex\", \"string\": \"28:8F:F6:5D:C4:2B:92:F9\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SeqUtils.mjs",
    "content": "/**\n * SeqUtils tests.\n *\n * @author Chris van Marle\n * @copyright Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"SeqUtils - Numeric sort photos\",\n        input: \"Photo-1.jpg\\nPhoto-4.jpg\\nPhoto-2.jpg\\nPhoto-3.jpg\",\n        expectedOutput: \"Photo-1.jpg\\nPhoto-2.jpg\\nPhoto-3.jpg\\nPhoto-4.jpg\",\n        recipeConfig: [\n            {\n                \"op\": \"Sort\",\n                \"args\": [\"Line feed\", false, \"Numeric\"]\n            }\n        ],\n    },\n    {\n        name: \"SeqUtils - Numeric sort CVE IDs\",\n        input: \"CVE-2017-1234,CVE-2017-9999,CVE-2017-10000,CVE-2017-10001,CVE-2017-12345,CVE-2016-1234,CVE-2016-4321,CVE-2016-10000,CVE-2016-9999,CVE-2016-10001\",\n        expectedOutput: \"CVE-2017-12345,CVE-2017-10001,CVE-2017-10000,CVE-2017-9999,CVE-2017-1234,CVE-2016-10001,CVE-2016-10000,CVE-2016-9999,CVE-2016-4321,CVE-2016-1234\",\n        recipeConfig: [\n            {\n                \"op\": \"Sort\",\n                \"args\": [\"Comma\", true, \"Numeric\"]\n            }\n        ],\n    },\n    {\n        name: \"SeqUtils - Hexadecimal sort\",\n        input: \"06,08,0a,0d,0f,1,10,11,12,13,14,15,16,17,18,19,1a,1b,1c,1d,1e,1f,2,3,4,5,7,9,b,c,e\",\n        expectedOutput: \"1,2,3,4,5,06,7,08,9,0a,b,c,0d,e,0f,10,11,12,13,14,15,16,17,18,19,1a,1b,1c,1d,1e,1f\",\n        recipeConfig: [\n            {\n                \"op\": \"Sort\",\n                \"args\": [\"Comma\", false, \"Numeric (hexadecimal)\"]\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SetDifference.mjs",
    "content": "/**\n * Set Difference tests.\n *\n * @author d98762625\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Set Difference\",\n        input: \"1 2 3 4 5\\n\\n3 4 5 6 7\",\n        expectedOutput: \"1 2\",\n        recipeConfig: [\n            {\n                op: \"Set Difference\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Set Difference: wrong sample count\",\n        input: \"1 2 3 4 5_3_4 5 6 7\",\n        expectedOutput: \"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\",\n        recipeConfig: [\n            {\n                op: \"Set Difference\",\n                args: [\" \", \"_\"],\n            },\n        ],\n    },\n    {\n        name: \"Set Difference: item delimiter\",\n        input: \"1;2;3;4;5\\n\\n3;4;5;6;7\",\n        expectedOutput: \"1;2\",\n        recipeConfig: [\n            {\n                op: \"Set Difference\",\n                args: [\"\\n\\n\", \";\"],\n            },\n        ],\n    },\n    {\n        name: \"Set Difference: sample delimiter\",\n        input: \"1;2;3;4;5===3;4;5;6;7\",\n        expectedOutput: \"1;2\",\n        recipeConfig: [\n            {\n                op: \"Set Difference\",\n                args: [\"===\", \";\"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SetIntersection.mjs",
    "content": "/**\n * Set Intersection tests.\n *\n * @author d98762625\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Set  Intersection\",\n        input: \"1 2 3 4 5\\n\\n3 4 5 6 7\",\n        expectedOutput: \"3 4 5\",\n        recipeConfig: [\n            {\n                op: \"Set Intersection\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Set Intersection: only one set\",\n        input: \"1 2 3 4 5 6 7 8\",\n        expectedOutput: \"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\",\n        recipeConfig: [\n            {\n                op: \"Set Intersection\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Set Intersection: item delimiter\",\n        input: \"1-2-3-4-5\\n\\n3-4-5-6-7\",\n        expectedOutput: \"3-4-5\",\n        recipeConfig: [\n            {\n                op: \"Set Intersection\",\n                args: [\"\\n\\n\", \"-\"],\n            },\n        ],\n    },\n    {\n        name: \"Set Intersection: sample delimiter\",\n        input: \"1-2-3-4-5z3-4-5-6-7\",\n        expectedOutput: \"3-4-5\",\n        recipeConfig: [\n            {\n                op: \"Set Intersection\",\n                args: [\"z\", \"-\"],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/SetUnion.mjs",
    "content": "/**\n * Set Union tests.\n *\n * @author d98762625\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Set Union: Nothing\",\n        input: \"\\n\\n\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Set Union\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Set Union\",\n        input: \"1 2 3 4 5\\n\\n3 4 5 6 7\",\n        expectedOutput: \"1 2 3 4 5 6 7\",\n        recipeConfig: [\n            {\n                op: \"Set Union\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Set Union: invalid sample number\",\n        input: \"1 2 3 4 5\\n\\n3 4 5 6 7\\n\\n1\",\n        expectedOutput: \"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\",\n        recipeConfig: [\n            {\n                op: \"Set Union\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Set Union: item delimiter\",\n        input: \"1,2,3,4,5\\n\\n3,4,5,6,7\",\n        expectedOutput: \"1,2,3,4,5,6,7\",\n        recipeConfig: [\n            {\n                op: \"Set Union\",\n                args: [\"\\n\\n\", \",\"],\n            },\n        ],\n    },\n    {\n        name: \"Set Union: sample delimiter\",\n        input: \"1 2 3 4 5whatever3 4 5 6 7\",\n        expectedOutput: \"1 2 3 4 5 6 7\",\n        recipeConfig: [\n            {\n                op: \"Set Union\",\n                args: [\"whatever\", \" \"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Shuffle.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"Shuffle empty\",\n        \"input\": \"\",\n        \"expectedOutput\": \"\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Shuffle\",\n                \"args\": [\"Comma\"]\n            }\n        ]\n    },\n    {\n        \"name\": \"Shuffle bytes\",\n        \"input\": \"12345678\",\n        \"expectedOutput\": \"31 32 33 34 35 36 37 38\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Shuffle\",\n                \"args\": [\"Nothing (separate chars)\"]\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\"Space\", 0]\n            },\n            {\n                \"op\": \"Sort\",\n                \"args\": [\"Space\", false, \"Alphabetical (case sensitive)\"]\n            }\n        ]\n    },\n    {\n        \"name\": \"Shuffle lines\",\n        \"input\": \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n9\\na\\nb\\nc\\nd\\ne\\nf\\n\",\n        \"expectedOutput\": \"\\n1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n9\\na\\nb\\nc\\nd\\ne\\nf\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Shuffle\",\n                \"args\": [\"Line feed\"]\n            },\n            {\n                \"op\": \"Sort\",\n                \"args\": [\"Line feed\", false, \"Alphabetical (case sensitive)\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/SplitColourChannels.mjs",
    "content": "/**\n * Colour channel split tests.\n *\n * @author Matt C matt@artemisbot.uk\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n// Base 85 encoded\nconst testCard = \"/9j/4AAQSkZJRgABAQEAYABgAAD/4QCqRXhpZgAATU0AKgAAAAgACQEaAAUAAAABAAAAegEbAAUAAAABAAAAggEoAAMAAAABAAIAAAExAAIAAAAQAAAAigMBAAUAAAABAAAAmgMDAAEAAAABAAAAAFEQAAEAAAABAQAAAFERAAQAAAABAAAOxFESAAQAAAABAAAOxAAAAAAAAXcKAAAD6AABdwoAAAPocGFpbnQubmV0IDQuMS40AAABhqAAALGP/9sAQwACAQECAQECAgICAgICAgMFAwMDAwMGBAQDBQcGBwcHBgcHCAkLCQgICggHBwoNCgoLDAwMDAcJDg8NDA4LDAwM/9sAQwECAgIDAwMGAwMGDAgHCAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAtAFAAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/djwd4X0s+EtL/4lmn/8ekX/AC7J/cHtWh/wielf9AzT/wDwHT/Cm+Df+RR0v/r0i/8AQBWlQBn/APCJ6V/0DNP/APAdP8KP+ET0r/oGaf8A+A6f4VoUUAZ//CJ6V/0DNP8A/AdP8K5j4w+FdLXwBeFdN09SGj5Fun98e1dvXMfGH/kn959Y/wD0MV8F4qSa4Lzdr/oFxH/pqZ3ZZ/vdL/FH80eCf8Izpv8A0D7L/vwv+FN/4RHSf+gXp/8A4DJ/hWhRX+LH1qv/ADv72frnLHsZ/wDwiOk/9AvT/wDwGT/Cj/hEdJ/6Ben/APgMn+FaFFH1qv8Azv72HJHsZ/8AwiOk/wDQL0//AMBk/wAK8F/az8N6fH4z00LY2YH2I8eSv99vavoqvA/2tv8AkddN/wCvI/8AobV/Xv0Fa9SfjHl0ZybXJiN3/wBOKh/MP0wnyeFuOlDR89Hb/r9A8b/4R7T/APnxtP8Avyv+FH/CPaf/AM+Np/35X/CrlFf74ezh2R/jN9arfzv72U/+Ee0//nxtP+/K/wCFH/CPaf8A8+Np/wB+V/wq5RR7OHZB9arfzv72U/8AhHrD/nxtP+/K/wCFeE/EXSbX/hN9S/0W3/17dIxX0FXgvxG/5HjUv+u5r+Pfpne5wtgnDT/aFtp/y7qH1fCWKq/WJ3k/h7vujnf7Itf+fW3/AO/Yo/si1/59bf8A79irFFf5u+3qfzP7z9A+sVf5n97K/wDZFr/z62//AH7FH9kWv/Prb/8AfsVYoo9vU/mf3h9Yq/zP72VDpFqx/wCPa35/6ZirP9nW/wDz7wf9+xSHhWPoKnr/AE2+gN+8yjN/aa/vKW+v2ZH+wn7MX95kOeuev76jvr9iZD/Z1v8A8+8H/fsUf2db/wDPvB/37FTUV/oD7Gn/ACr7j/UL2UOy+4h/s63/AOfeD/v2KP7Ot/8An3g/79ipqKPY0/5V9weyh2X3HBfHrTrdfCFqRbwg/bFHEY/uPXkn2OH/AJ4xf98CvYvj3/yJ9r/1+r/6A9eQ1/ll9LT3OP5qOi9lS/Jn6Vwzh6TwKvFbvoiP7HD/AM8Yv++BR9jh/wCeMX/fAqSiv5m55dz6D6vS/lX3Ij+xw/8APGL/AL4FBs4SP9TF/wB8CpKKOeXcPq9L+Vfcj5b/AGhbeP8A4W7qyiOMKvlYAUf88krifJT+4n/fIruP2hj/AMXe1b/tl/6JSuJr/pY8B8NSl4acPOUU39RwnRf8+KZ/gj4zSceP88S/6DMT/wCnpjfJT+4n/fIo8lP7if8AfIp1Ffq31Sh/IvuR+a+0l3G+Sn9xP++RR5Kf3E/75FOoo+qUP5F9yD2ku5/al4O/5FHTP+vWL/0AVpVm+Dv+RR0z/r1i/wDQBWlX+DZ+1BRRRQAVzHxh/wCSf3v1j/8AQxXT1zHxh/5J/e/WP/0MV8D4rf8AJFZx/wBguI/9MzO7K/8Ae6X+KP5o8Sooor/FE/XgooooAK8D/a2/5HXTf+vI/wDobV75Xgf7W3/I66b/ANeR/wDQ2r+wPoJf8nly3/BiP/TFQ/l/6Yv/ACazHf46H/p6B5PRRRX+/B/i+FFFFABXgvxH/wCR31L/AK7mveq8F+I//I8al/13Nfx19NH/AJJXBf8AYQv/AE3UPrOEf95n/h/VGLRRRX+bB+gBRRRQAxvuN9KnqBvuN9Knr/Tv6AX/ACKM3/6+Uv8A0iZ/sV+zB/5EGe/9fqP/AKRMKKKK/wBBT/UYKKKKAOJ+Pf8AyJ9r/wBfq/8AoD15DXr3x7/5E+1/6/V/9AevIa/yu+lv/wAnAqf9eqX5M/S+GP8AcV6sKKKK/mQ+hCiiigD5d/aI4+L2rf8AbL/0SlcTXbftEf8AJXtW/wC2X/olK4mv+mDwF/5Nnw7/ANgOE/8AUemf4F+NH/JwM8/7DMT/AOnphRRRX6wfmYUUUUAf2peDv+RR0z/r1i/9AFaVZvg7/kUdM/69Yv8A0AVpV/gmftgUUUUAFcx8Yf8Akn979Y//AEMV09cx8Yf+Sf3v1j/9DFfA+K3/ACRWcf8AYLiP/TMzuyv/AHul/ij+aPEqKKK/xRP14KKKKACvA/2tv+R103/ryP8A6G1e+V4H+1t/yOum/wDXkf8A0Nq/sD6CX/J5ct/wYj/0xUP5f+mL/wAmsx3+Oh/6egeT0UUV/vwf4vhRRRQAV4L8R/8Akd9S/wCu5r3qvBfiP/yPGpf9dzX8dfTR/wCSVwX/AGEL/wBN1D6zhH/eZ/4f1Ri0UUV/mwfoAUUUUAMb7jfSp6gb7jfSp6/07+gF/wAijN/+vlL/ANImf7Ffswf+RBnv/X6j/wCkTCiiiv8AQU/1GCiiigDifj3/AMifa/8AX6v/AKA9eQ1698e/+RPtf+v1f/QHryGv8rvpb/8AJwKn/Xql+TP0vhj/AHFerCiiiv5kPoQooooA+Xf2iP8Akr2rf9sv/RKVxNdt+0R/yV7Vv+2X/olK4mv+mDwF/wCTZ8O/9gOE/wDUemf4F+NH/JwM8/7DMT/6emFFFFfrB+ZhRRRQB/al4O/5FHTP+vWL/wBAFaVZvg7/AJFHTP8Ar1i/9AFaVf4Jn7YFFFFABXMfGH/kn979Y/8A0MV09cx8Yf8Akn979Y//AEMV8D4rf8kVnH/YLiP/AEzM7sr/AN7pf4o/mjxKiiiv8UT9eCiiigArwP8Aa2/5HXTf+vI/+htXvleB/tbf8jrpv/Xkf/Q2r+wPoJf8nly3/BiP/TFQ/l/6Yv8AyazHf46H/p6B5PRRRX+/B/i+FFFFABXgvxH/AOR31L/rua96rwX4j/8AI8al/wBdzX8dfTR/5JXBf9hC/wDTdQ+s4R/3mf8Ah/VGLRRRX+bB+gBRRRQAxvuN9KnqBvuN9Knr/Tv6AX/Iozf/AK+Uv/SJn+xX7MH/AJEGe/8AX6j/AOkTCiiiv9BT/UYKKKKAOJ+Pf/In2v8A1+r/AOgPXkNevfHv/kT7X/r9X/0B68hr/K76W/8AycCp/wBeqX5M/S+GP9xXqwooor+ZD6EKKKKAPl39oj/kr2rf9sv/AESlcTXbftEf8le1b/tl/wCiUria/wCmDwF/5Nnw7/2A4T/1Hpn+BfjR/wAnAzz/ALDMT/6emFFFFfrB+ZhRRRQB/al4O/5FHTP+vWL/ANAFaVZvg7/kUdM/69Yv/QBWlX+CZ+2BRRRQAVzHxh/5J/e/WP8A9DFdPXMfGH/kn979Y/8A0MV8D4rf8kVnH/YLiP8A0zM7sr/3ul/ij+aPEqKKK/xRP14KKKKACvA/2tv+R103/ryP/obV75Xgf7W3/I66b/15H/0Nq/sD6CX/ACeXLf8ABiP/AExUP5f+mL/yazHf46H/AKegeT0UUV/vwf4vhRRRQAV4L8R/+R31L/rua96rwX4j/wDI8al/13Nfx19NH/klcF/2EL/03UPrOEf95n/h/VGLRRRX+bB+gBRRRQAxvuN9KnqBvuN9Knr/AE7+gF/yKM3/AOvlL/0iZ/sV+zB/5EGe/wDX6j/6RMKKKK/0FP8AUYKKKKAOJ+Pf/In2v/X6v/oD15DXr3x7/wCRPtf+v1f/AEB68hr/ACu+lv8A8nAqf9eqX5M/S+GP9xXqwooor+ZD6EKKKKAPl39oj/kr2rf9sv8A0SlcTXbftEf8le1b/tl/6JSuJr/pg8Bf+TZ8O/8AYDhP/Uemf4F+NH/JwM8/7DMT/wCnphRRRX6wfmYUUUUAf2peDv8AkUdM/wCvWL/0AVpVm+Dv+RR0z/r1i/8AQBWlX+CZ+2BRRRQAVzHxh/5J/e/WP/0MV09cx8Yf+Sf3v1j/APQxXwPit/yRWcf9guI/9MzO7K/97pf4o/mjxKiiiv8AFE/XgooooAK8D/a2/wCR103/AK8j/wChtXvleB/tbf8AI66b/wBeR/8AQ2r+wPoJf8nly3/BiP8A0xUP5f8Api/8msx3+Oh/6egeT0UUV/vwf4vhRRRQAV4L8R/+R31L/rua96rwX4j/API8al/13Nfx19NH/klcF/2EL/03UPrOEf8AeZ/4f1Ri0UUV/mwfoAUUUUAMb7jfSp6gb7jfSp6/07+gF/yKM3/6+Uv/AEiZ/sV+zB/5EGe/9fqP/pEwooor/QU/1GCiiigDifj3/wAifa/9fq/+gPXkNevfHv8A5E+1/wCv1f8A0B68hr/K76W//JwKn/Xql+TP0vhj/cV6sKKKK/mQ+hCiiigD5d/aI/5K9q3/AGy/9EpXE1237RH/ACV7Vv8Atl/6JSuJr/pg8Bf+TZ8O/wDYDhP/AFHpn+BfjR/ycDPP+wzE/wDp6YUUUV+sH5mFFFFAH9qXg7/kUdM/69Yv/QBWlWb4O/5FHTP+vWL/ANAFaVf4Jn7YFFFFABXMfGH/AJJ/e/WP/wBDFdPXMfGH/kn979Y//QxXwPit/wAkVnH/AGC4j/0zM7sr/wB7pf4o/mjxKiiiv8UT9eCiiigArwP9rb/kddN/68j/AOhtXvleB/tbf8jrpv8A15H/ANDav7A+gl/yeXLf8GI/9MVD+X/pi/8AJrMd/jof+noHk9FFFf78H+L4UUUUAFeC/Eb/AJHfUv8Arsa96rwX4jf8jxqX/Xc1/HP00v8AklcF/wBhC/8ATdQ+s4R/3mf+H9UYtFFFf5sn6AFFFFADG+430qeoG+430qev9O/oBf8AIozf/r5S/wDSJn+xX7MH/kQZ7/1+o/8ApEwooor/AEFP9RgooooA4n49/wDIn2v/AF+r/wCgPXkNevfHv/kT7X/r9X/0B68hr/K76W//ACcCp/16pfkz9L4Y/wBxXqwooor+ZD6EKKKKAPl39oj/AJK9q3/bL/0SlcTXbftEf8le1b/tl/6JSuJr/pg8Bf8Ak2fDv/YDhP8A1Hpn+BfjR/ycDPP+wzE/+nphRRRX6wfmYUUUUAf2peDW/wCKS03/AK9Yv/QBWlX8U8fiXUjBH/xML77o/wCW7en1pf8AhJNR/wCf+9/7/t/jX+f1P6EtecFP+2Fqr/wH/wDLj7h8XxTt7J/+Bf8AAP7V6K/io/4STUf+f+9/7/t/jR/wkmo/8/8Ae/8Af9v8av8A4kir/wDQ4X/gh/8Ay4P9cI/8+n/4F/wD+1euX+MI/wCLf33+9H/6Gtfxof8ACSaj/wA/97/3/b/GvYf2Ddevrj9p3QUkvLt1aK6yGmYg/wCjye9fJcffQTr47hjMcEs6Ufa0K0L/AFdu3NTlG9vbK9r7dT6Pg7O1mef4HLlDl9tWpQ5r3tzzjG9rK9r3tdX7o/pior8ov7SuB/y2l/76NJ/aVx/z3m/77Nf5l/8AFKHF/wDRTR/8JH/80n98f8QNn/0Gr/wX/wDbn6vUV+UP9pXH/Peb/vs0f2lcf895v++zT/4pQ4v/AKKaP/hI/wD5pD/iBs/+g1f+C/8A7c/V6vAv2tjnxtpm0bv9C7f77V8Pf2lcf895v++zX2Z/wTZ/074Y6+0375hqmAZPmwPJT1rKt9Fmr9GmD8YK+ZLMo4L3Pq6pOg5/WP3F/aupW5eX2nNb2b5rW0vdfhH0kvoz1OIOA8TlUcxVNzlS972TlblqRlt7Rb2tuee5P91vyow392vtFbOHvDF/3wKT7HD/AM8Y/wDvkVw/8VOMN/0Tsv8AwqX/AMzn+aP/ABT9xP8A0PI/+E7/APlx8X4b+7Rhv7tfaH2OH/njH/3yKPscP/PGP/vkUf8AFTnDf9E7L/wqX/zOH/FP3E/9DyP/AITv/wCXHxfhv7teC/En5fHGqc/8t2r9SvscP/PGP/vkV+L/AO2q7RftbfEWNWKquvXWFB6fPXynGH0qqXjHg45HTyx4P6vJVeZ1lV5tHDlt7Onb4r3u9rW6n694O/sz8TxBmlbC/wCsMafLT5r/AFVyv70Va31hd+53e76fnRu+n5185ec/95vzo85/7zfnX5z/AKs/9PPw/wCCf0T/AMUi8X/0VMf/AAjf/wA1H0bu+n50bvp+dfOXnP8A3m/Ojzn/ALzfnR/qz/08/D/gh/xSLxf/AEVMf/CN/wDzUfRjcRnPHap9n1rw74TXDt8UvDY3N/yFLbv/ANNVr79+zx/3F/Kv9DfoTVP7JyvNIP3+apTfa1oy9T5LiTiRfQlqQyDFU/7bebJ1lOL+qey9j7nK4tYjn5ue97xta1nfT5z2fWjZ9a+jPs8f9xfyo+zx/wBxfyr+3/8AWb/p3+P/AAD5r/irphP+iXl/4WL/AOZT5z2fWjZ9a+jPs8f9xfyo+zx/3F/Kj/Wb/p3+P/AD/irphP8Aolpf+Fi/+ZT48+PX/IoWv/X4v/oD15Dj/e/Kut/4OFJnsP2O/C0kDNDJ/wAJhbjch2nH2O84yK/HL/hIb/8A5/Lr/v6a/g/6QfBcuIOLpZiq3s704K3LzbJ9br8j+w/Bf9oJh+KOGYZssjlSvOceX6ypfC1rf2Ed/Q/WDH+9+VGP978q/J//AISG/wD+fy6/7+n/ABo/4SG//wCfy6/7+n/Gvw//AIhHP/oKX/gH/wBsfrH/ABOlQ/6FL/8AB6/+VH6wY/3vyox/vflX5P8A/CQ3/wDz+XX/AH9P+NH/AAkN/wD8/l1/39P+NH/EI5/9BS/8A/8Atg/4nSof9Cl/+D1/8qPr/wDaJ+X4v6uOcgxf+ikriM1/TL/wbKaHZ6r/AMERPglPdWltcTPFq+55IwzN/wATq/6kivvH/hF9M/6B1j/4Dp/hX+nHAP0tocNcMZdw68rdX6pQo0ef2/Lz+ypxhzcvspcvNy3td2va73P8/eNcD/b/ABDjs9T9n9arVavLbm5faTlPlvpe17Xsr72R/FTmjNf2rf8ACL6Z/wBA6x/8B0/wo/4RfTP+gdY/+A6f4V9Z/wATvU/+hM//AAo/+4nzP+p7/wCfv/kv/BP4qc0Zr+1b/hF9M/6B1j/4Dp/hR/wi+mf9A6x/8B0/wo/4nep/9CZ/+FH/ANxD/U9/8/f/ACX/AIJ/IFZfsB/He4s4nX4K/FplZAQw8IahhhjjH7qnN+wF8d06/BT4tf8AhIah/wDGq/rx8Nr/AMUppv8A16xf+gCquoKEc/X1r52n9NTN4QUP7Mp6K38SX+Rt/qjTbv7R/cfyLt+wN8dE6/Bf4sf+EjqH/wAaqOT9hD44xfe+DPxWX6+Er/8A+NV/WZqafe9hXPaod272rT/idjN/+hZT/wDBkv8A5EP9Uaf/AD8f3H8qB/Yd+NS9fg/8UFx6+Fb4f+0q9U/Yl/ZF+K/hf9pDRLzU/hj8Q9NtYY7kPNdeHLyGNcwSAZZowOSQK/o11diDXKa4xfdmuXMPpmZpi8LUws8sppTi43VSWl1a/wAJ7XDOVrJ83wubQlzPD1adRReifJJSs3ra9rX6H5vN8IPFife8LeIh9dNm/wDiaif4W+JovveHdbX62Mo/pX3trY5Ncjrig7q/L/8AiP2M/wCgOP8A4G/8j+uv+Jn8f/0AQ/8AA5f/ACJ8Yv8AD7Xoj82h6sv1tJP8KjfwVrEf3tJ1Bfrbv/hX1B4gjUb+K4vXkxu4o/4j9jP+gOP/AIG/8hf8TP4//oAh/wCBy/8AkTwuTw/fRfesbpfrEwr6z/4J8+NdF8DfDfXIda1jTNHmm1LzEjvrqO3Z18pBkByCRkYz7V4br45/M1xGvtkH2zX4748cQVPE7gzE8HYqCw8K7ptzi+Zr2dSNRaOy15bb6XufPcUeP2LzrL5YCpg4wUmndTb2d9rI/SN/jx4HjPzeMvCi/XVrf/4uoX/aI+H8X3vHXg9frrNsP/Z6/K7WzjcK4nXhk/8A1q/z/wD+JJ8s/wChpU/8Fx/+SPy//XCf/Ptff/wD9g5P2mfhrF974heB1+uvWv8A8cqu37VPwvT73xI8BL9fEFp/8cr8UtfGdwri9c43Uf8AEk+Wf9DSp/4Lj/8AJB/rhP8A59r7/wDgH7uSftbfCmI/N8Tvh6v18RWf/wAcr8hf2wviFoHiD9qTx5qFjrmk31jea3cSwXFveRywzIXJDKynBB9Qa+bNe+b8q5DXPmLN3+lfbcD/AEW8Dw3iamJp4+dTnjy2cIq2qd9JeR+heHPjfieEsdUxtHCxqucOSzk1bVO90n2PdG8Y6On3tW01cet1GP61G3j7QU663pIx63kf/wAVXy/rYyxrldYXBPXn0r9M/wCIS0f+gl/+Ar/M/Yf+Jz8y6ZXT/wDBsv8A5A+xpPiX4bi+94h0MfW/i/8AiqYfil4XX/mZNB/8GEP/AMVXwrqy/M1c5qQ+8aP+IS0P+gl/+Ar/ADF/xOfmf/Qrp/8Ag2X/AMgfpN8MPjL4P0/4leH5rjxV4bt4IdStnkkk1OBVjUSrkklsADua+5E/a6+E8jqq/E74eszEAAeI7PJJ6f8ALSv51tQfaePpVXQ33eJNP3f8/Mf/AKEK/ZvC3EPgrD16FD977aUW3LS3Kmul+5/DP0ussh485jl2Y5i3g3g4VIJQ99SU5RlduXLa3L07n9K//C2PCv8A0Mugf+DCL/4qmj4r+GG/5mTQf/BhD/8AFV+fuKsaIM30fua8OX0tcwSv/Z0P/A5f/In30v2MvDCTf+s2I/8ACen/APLD9C7PxdpOof8AHvqum3HtHco38jWxY6Xc6mP9Ht5rjpzGhb+VfNnwM4lT8K+2Pgb8qxe+M1+X8TfT0zfK2+TKacvWrJf+2n4zxd+y1yDJr+zz+tO3ejBf+3n51/8ABeT9nnx98Vf2S/Den+F/A/i7xHqEPiuC5e30zR7i8ljiFpdqXKxoSFBZRk8ZYetfkTdfsQfGiy/13wh+J8P+/wCFr5f/AGlX9fmqDHh9Bz0yOfavBfi6oKTV+H436cGacQ5g8TVyqnC6Ssqknt6xRnkvh7R8MOHllGErPEqMpS5pJQfvO9rLm2P5a7j9k74p2hxL8NfH0Z/2/D12v846h/4Zg+JW7H/CvfHH/giuv/iK/ff4nOS8lclpH+ur7jKvH7F4txUsHFc395/5H4TnX0kcbga8qMcFCVuvO/8AI/DuL9lD4pTfc+G3j5vp4fuz/wC06mX9j74tSD5fhd8RWHt4bvP/AI3X76+Geq13nh7lVr+quAcIuIYqVR+zv21/yPicZ9L3MqF7ZdTf/cSX/wAie8f8G63jnRPgf/wRt+DnhzxprGl+EfEGnx6t9q0zWruOwvLbdq96674pSrrlGVhkDKsD0Ir7D1D9sP4R6Tu+1fFL4c223r5viWzTH5yV+UPxX+S3k5PHTJ6V8b/tAjYJccda/pTh36PuDzOSjLGSj/24n+p9Rwn9JzMM4mozwMIX7Tk/0P6Ebn9vz4E2ZxN8avhLF6b/ABfp65/OWn2v7eXwNv2Ag+M3womJ7J4tsG/lLX8lHxOQfbG9q6D4RIDPF8o7dq/Vqv0OMthhvbrM537ezj/8kf01wlxBPOKsaVSCjzdtT+su0/a7+E9+oMHxO+Hs+7p5fiOzbP5SVa/4ae+Gv/RQfBP/AIPbX/4uv54fguQqxsPQCvYui1+Z5l9HXBYar7OONm/+3F/mf2Zwx9HvB5pg1iqmMnFvooL/ADP3f8OjHhTTP+vSL/0AVU1U/MfrX5xaL/wdh/sa2mg2VvJ4x8WLJBBHG/8AxS151CgH+CmT/wDB1j+xtfy7U8aeKtzkBf8Ailbzqf8AgNfymfy2foFqX8Vc1qpwxr4iu/8Ag5q/ZLn3bfFnivn18LXv/wARWaf+DkH9lXWbxYbfxV4paSQ/KD4YvFzwe+ygD7K1jrXJ6yfv18u6h/wXy/ZtuR8vibxB/wCE/df/ABFZLf8ABcH9nrXbrybXxDrskkmSAdDuV9+pWgD6L1v+lchrnQ/jXiup/wDBXT4JXWdmtaz+OkT/APxNY8f/AAU5+EviedobTVdWaRQWO7S5lGPxFAHqHiA/e/WuN19R831rkdW/bo+Hd0Ds1K+bP/TjJ/hWOv7U3hDxW0gsby6k8vBbdaOuM5x29qANTX1/niuF1zk/nWlq3xd0W6+5PM3/AGxNZK38PiaJ5LNtyxttO4bTnrQBx+udT+NcVroyTXpWq+Db65b93Gh+rgVmf8M/+JfElq81na27R7inzXCKc/ifegDxXxAcbvrXE+ITgn619Cap+yF43ut22xs2yf8An7T/ABqjc/8ABOP4peILZbi10vS2hk5UtqcIz2/vUAfLWt85rkNa6GvrrU/+CWnxguVPl6PpOffV4P8A4qqMn/BFv4+a1aJND4f0No5RuU/23bdP++qAPiPXDjdXLax1r7r1T/ghd+0PdE7fDehn/uO2v/xdM1L/AINz/wBqO5PyeF/Df4+JLT/4ugD88dV+81c3qfRq/Ri+/wCDbH9qy6fEfhbwuWY4GfEtoOf++6Lz/g1b/bGnU7fBvhX/AMKiz/8AiqAPzJvowVaqWgjPiex/6+Y//QhX6HfGb/g2V/a0+C/wo8UeM9e8J+GbfQfCOk3etalKniS0keK2toXmlYIrEsQiMQByTgV+dekajFZ63aTyE+XFOjuQMkAMCaAP1aq1on/H/F9RXibft5fC/wD6GKb/AMFtz/8AEVY039vr4W2d2jt4gnKqe2m3P/xFfyXU4ZzdxdsLU/8AAJf5H+y1TxR4McHbN8L/AOD6X/yZ95fAv/WR/hX2v8DvuQ1+S3wu/wCCs3wR8JtH9s8Raj8uM7dIuW/9kr6Y+GX/AAcB/sx+FvL+2eKdeXb/AHNAum/9kr8G468O+KcU39Xy6tL0pzf6H8v+JnGWQYly+rY6jP8Aw1IS/KTP1D1U/wDEhT+9t4rwf4ufcm+teAX3/Byn+yfcaYsS+L/Em5Rj/kW7v/4ivKfiH/wcBfs06+ri18TeIJN2eugXK/8AstfmXD/hPxpSq3qZViFr1pTX6H8HeKUljMPKOF99/wB3X8rnYfE370n1rk9H/wBdXhfjX/gsP8CdeZvs/iLVPm/vaPcj/wBkrB0//grJ8EbaTLeItRP00m5/+Ir+huHeCOIKU6ftcDVWvWnL/I/zf4o8P+Jq+MnOjl9aSfVU5/5H2R4Z+9+Vd94f+6tfD+if8FjvgLYn954k1Qf9we5/+IrrNK/4Lb/s82YG7xVqo/7gd3/8RX+hXg7VhgYRWMfs/wDF7v52PyrNPCzjGbfJleIfpSn/APIn0h8WObaSvjj9oIf8fHtmt/x5/wAFnf2f9ftWW38UaozMO+i3S/8AslfN/wAXf+CiXwr8Web9i1u9k35xu06dc/mtf3XwPxtw9Qmvb46jH1qQX5s/QPDnw74pwtSLxOXVof4qU1+aPLPid/x+N9a6H4RHNxH+FeT+OP2hvCeuXZa31CZlJ720g/8AZa2Ph3+094L8Pzxm61KaNV6kWkrfyWv6LxHidwc8CoLNsNft7elf/wBLP7w8OcDiMLiacsTBwSt8Sa/M+9/gyq+TH+Fev54r4v8Ahr/wUa+Enh5UF14huo9uM40y5b+SV6IP+CrfwPz/AMjRef8Agou//jdfgudcccOzxDlDH0WvKrB/+3H+o/AfGnD2HyyNOvjqMZaaOrBPbzkfkXVnSBnVbX/rsn8xVarOj/8AIVtf+uyfzr/O0/z4PVNxrV8Et/xVFp/vH/0E1k1reCP+RotP94/+gmgD0YjNbHgQY8SQ/Rv/AEE1j1seBf8AkY4fo3/oJoA9Arovhmf+J7J/1wb/ANCWudrovhr/AMh5/wDri381oA7quw+E4z/aHsI//Zq4+uw+E/TUf92P/wBmoA7Cu2+F4/4lVz/12/8AZRXE123ww/5BNx/12/8AZRQB01d18NDnQZP+u5/ktcLXd/DYY0OT/rsf5LQB0OK9M8Ff8irZ/wC6f5mvM69M8EHPhSz/AN0/+hGgDUr07wuNvh2x/wCuK/yrzGvTvDP/ACLtj/1xWgC9Xq+wV5RXrFAElmg+2Rf74/nXrm6vI7P/AI/If99f5165QB4j/wAFMzn/AIJu/tBf9k18R/8Aprua/h1r+4r/AIKZ/wDKN39oL/smviP/ANNdzX8OtABRRRQAUUUUAFFFFABQDiiigAooooAKCc0UUAGeaKKKADOaKKKACrWjf8hW1/67J/OiigD0/ca1/Az/APFU2n+8f/QTRRQB6RW18P13+JoQf7rfyoooA9CKAGui+GUYbXpP+uJ/9CWiigD0D7Gu3q1dV8Koh5l+OnCf+zUUUAdgsAI6tXffCbT0l0W5Zi3+vI6/7IoooA6aXT0U9W/Ou9+FumxyaBLuLf689/YUUUAdL/ZMX+1+dejeDNNjHhizGW+4T1/22oooA1P7NT+8/wCn+FepeHdIjHh6x+aT/Ur3H+FFFAFs6dGD95vzr1uPSo5B95x9DRRQBNFo0aTRkNJnevceo9q9KoooA8S/4KXDd/wTi/aBH/VNvEf/AKa7mv4dKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9k=\";\nconst testCardSplit = \"iVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAYAAADl5PURAAAhfklEQVR4AezBP49maZon5Ot+zhsRVdXTO03zRyxidxnhIKQRHgjhgMMXwMblS+EigYGBhYMQKwwsLLDQaqRFsAxCC730dE1lRrzn+dHSvejM2YjMyqzMrIiseK6r/lvyZxiIx+LLmtpFu2qbFgQDQeEev0Lh3vvd+7Ki3WubdtHutL/AX+A/8XX5Fj/gou1elkIQZ/+R9p9qb7Wrs+F57SgUgt/gH+K3KIddG9qubZ5HtF8j+C2mr8/lz/Bn3i2+rKlt2q5t3u0NvtHuvV/5sqLda5t2cfY9/oBfofANJsrzilbOot3hHr9BsCMO5WV40K7an2q/1m61q7PheU2Uw5/gV/iVs10b2q5tnke0X2HHN7jiAcGG8m7lZbgEQTkE5eUKguDi08Sn2bRNK2dB8IC3+B6Fe0xcvQylxdm32PE7BNPLUlqcTe2iXXw9fos/9XUIJiYm4hCH6Wxo0/O6BPG0eH7RyqFQiJdvICgUCoVCeTlKi7Nd271M8bSpTWellRbPqzwWX5eJielQWrxsF/+MQrwsQSEoh0J5GTZPmwh2TATBWy/TcDa0W+2qlRbPa3ra0Ib3K88rWjkEQTlMbWjx/IKBDYWBiUIQ7za1eF4X/4wgXo7yZcWnKR+mUL5uQRCUVl6moBBn5eUJymFiojC0OIufR1Aei0OcBRPx8l2CoBCU16V8mmhX7cZZoVAoL1c8rbSplVaI53dBYWrRhrNdm9rwMgwEQWnxWHke5WnlMFAorbRCvGyXQmmF0grx/KZWHgvKy1aIxwrxcpSnxWOFQiGeX/yyFArD1yEIogXl63CJs2jxMpTHSivE85patGjlUNgwtMLARLwM09N2bUe8PNO7BaVt2qaVl2vDcDacDS/H0MpZvHzDsizLJ4iv18VXLj5N+TSbs/JYsGPXgl0bntfUytM2PGBDsDsbntf0bnEoL9OuDZS2Y6JQ2tSGNr0MA+XrNSzLsrxSF1+58jKUpwXxbtPLEE/btR3x2PSyDJRWDruXafPYhuFsOBteluHrNCzLL1Qsy/tdvHLl00TbnZWzQmmFDRPTy1CeNrSBYDobntd0NlEI4jCclRbPK1o5TI9NbWhTG5ZPMSzLsrxSl0K0QiGeXzxWPr/4PC6eVihn8XLF+xXiML088ePi6zYsn8OwLMvyCcrXa1iWZXmlhmVZlldqWJZleaWGZVmWV2pYlmV5pYZlWZZXaliWjxDL8ssxLMuyvFLDsizLKzUsy7K8UsOyLMsrNSzLsrxSw7Isyys1LMuyvFLDsnyEsiy/HMOyLMsrNSzLsrxSw7Isyys1LMuyvFLDsizLKzUsy7K8UsOyLMsrNSzLR4hl+eUYlmVZXqlhWZbllRqWZVleqWFZluWVGpZlWV6pYVmW5ZUalmVZXqlhWT5CWZZfjmFZluWVGpZlWV6pYVmW5ZUalmVZXqlhWZbllRqWZVleqWFZluWVGpblI8Sy/HIMy7Isr9SwLMvySg3Lsiyv1LAsy/JKDcuyLK/UsCzL8koNy7Isr9SwLB+hLMsvx7Asy/JKDcuyLK/UsCzL8koNy7Isr9SwLMvySg3Lsiyv1LAsy/JKDcvyEWJZfjmGZVmWV2pYlmV5pYZlWZZXaliWZXmlhmVZlldqWJZleaWGZVmWV2pYlo9QluWXY1iWZXmlhmVZlldqWJZleaWGZVmWV2pYlmV5pYZlWZZXaliWZXmlhmX5CLEsvxzDsizLKzUsy7K8UsOyLMsrNSzLsrxSw7Isyys1LMuyvFLDsizLKzUsyweKZfllGZZlWV6pYVmW5ZUalmVZXqlhWZbllRqWZVleqWFZluWVGpZlWV6pYVk+UFmWX5ZhWZbllRqWZVleqWFZluWVGpZlWV6pYVmW5ZUalmVZXqlhWZbllboMDK208vPZtF2LtjsrDI/F8yqtnE3tAYWJqRUKG6bnVVppcbZ5v+F5TY8VJiZ2bddutfg8pk+zaUFpw2FqQ5va8HIUrtgxtOnrMPxRfF3K16O0QqF8XaLF1yOI5ecShyC+Hpd73KNQKMTP50bbtHvt1tk9gs3Z8LzutRtn99odCre4RbTdyxAtzsrZdHaD4Op5DRSi3Wkbgl2LdtUuPo/Np9m1zWFiavfaN9qDdutlKBRuseONs3J2q1216XldLF9cfL3Kyxe/XOWstPLylK/P5QY3CAqFeLfh83rQbrV42kRQmFphel63nrZppW0YDtPLsGnT2abF08rLMJ39oN0huPW0B23zaYZPc9U2Z7tWvg7BxEW7atFKe+tlucSy/PLE+0WLTxNfVpzFyxQE8XW5DAyU53Gj7dqds7faN1owMBGU5/VGu9NKu2gThd/jr7w8u6ft2qZ9h+AH7d7LdIeJ77Vo5WxqNz5N+TR3WlDaLW603dmts93zu/fYtyj8tRYv08U/FZTlc5soRCsUplbeL55H+ToU4t3K00orL9twVs6G5xWUs0KhvHz1d8gdpsNEtDgbPq8H7U773tl3CP4KhQ2/wl94GaKVNrXh7H/Hv4p/gIk7TNx7XndaaW+1aH8X/yX+XdziFsFFe+N5lbO/1v5z/Hv4j7W/pU3tjbb5NNOnucem7fgP8Z/hzzGwa5u2a9HK8/or3OG/wRUXDPweE3+i3WvfaVctntflH+EO02EinjZ8Xg/anfbG2S2CB4dfIwjuvV98Wbs2tF3btB0Df63dYOA7BD94v/iy7rTShhZtQ2HHAwrlsHtehaC0HcGOHRftok3tom0+zfTpCgMTQ/vHzjZt1+JluMcdfoMb3Gm/RuFPtHvtG21q8bwuE28Qz+OiPWibtmv32h2Ce2wICt94XtHKuwW32r/mMDE8r6kNZ1Mb+Hv4N5ztCIbnNTztBjf4P7R/pJUWbfg08WkumNi1v9R+h8IPXrYLvsEFNxgI/hYKU5ueVp7XxVektIkdA5vnVX6aIJ5fnEWLQ3msUF628vINxNM2j5UWL8PAwMTUymFow9nwMlz80UC06ed11UqLp+0OwY0PE19WadGilTYxcdUmJoYWrTyPzVlpm8NEULhiOAzP61671Xbtgg2lRbvR7rXpeb11Vg5XP27zvO7xBvcYuNV2BOXswdnmeV38URDPK95vIlp8uPLzKGelFQZKC4Jo8X7xZQ1Pm9rQSiuHeBnilyPaQPy4eF6FG9zi4mxgatHiLJ7XxR9t2B3ix5UWn8dFu2rD2XQoh90hHitf1tB2rbRdu9W+0aa2aVOLVs7Kl3XVLs6Gsys2TBQu2vS8bj3tih3RSnvweZVPE60QlPYG5bE4m55XYWLDhmibNrSp3XpZLv4oWny4+LziLFpQKC2IQzmUFi9PeVohDvHzig8zUZjYLF/CwNDi61AoFAbK1+XijzZMBOVluEUhmHjQBm61oLxfeVpp8WlKu2rfaJuzt9rAhiB+mvLh4v2Gp01tYEdwxZ2zoDytUH5em1YoXBDsWhyGQ7Q4G54WLSgUSotDUJgeG4i24YobLYjD0Ka2OQTR4v0KcRja9LRyKC2IFgxc8Y22Y3N262lBYdfibHO2aw8IvtV2rVAorbzfxR9NTD+PchZtIB4LylmwY0M8NhziLFo8rfx0QZyVdqeVVlo8Vp4WLQ5xVj6PcrgguOLO2XAWhyAoj5UWrXy6iaEVBiYKhcLuMFFa/DTDIQ7x46bDFd+iPK2cxWOFeL94v/K00uKscMUFEwMbJoYfFy0olFZaadGiRSuttCAYWnm/iz+6ojCw+zClxceJpz1opb11tmHHd/gWOzaU9ytn5cu41Up70Da8xffajl3bMHy48vm90TbvdoM7XBzuceuxclber3weE4Ud93jAjl271XZnpcXTpveLHxdPKwzs2g8OG3YMrbRyNh3iUNpwFi1anMVZeb+gcMU3eIMbFKazoLSJ4Ea7aNHKWWk32o2zTYsW7eoszi7+KBgoHy6+jNLibNeCiQ0Tw/vF+xXi3cpZnJU2tdKmVhgY2g2uKEwMz6sQj0UrFAoXh+Aetz5OtPL5FR5QuMONs3gsnk8wsGPDxNB2rbTSSitPK620OIsWbTiLD1PaDSYumLh1mChPK5THyoeJVs4KQbThLM4u3mFgeqwQlA8T73eHYGq793uDDRvix5UfV94tHiuPRSvtVrviDjcOF4f4eHEY3m86lMc2xCFaHHZcUdi0O8THK5/XjoGBHYWBb3DjcO/sO0y89fMoBEMLBh60XXvrsDlcEY+VFi3OLtrU4ixaPG0gKGfRgu+wYWjRLoizIM6CqRUKQbRCedrVoVDO4mw4u/ijwsTUCvG0aNHKIT7e1OJQiDYwHXZEK8ShPBaH8liclbPyftGmFpTDBcHV03aUx0qLVp42nZWzeFpp8bRyGChsDlMrz+uKO0S74AH3uPVuVww/n2hBtOmxW23DjqHF0wpBIZ4WRIuz8rRo0aJFi7bhLUoLytMK5bHC0EorRCvvVs6CaEE5i7PLLX6lBYXSJkqLFq0QDC1aHILhEC0Ope3avXbRfoOJ32PDFb/F/4xv8b1DPLZ5LA6lRYuzDXGIsxvtQbtFEG3HxO/xd/DXKOwo3DjEIVp5WrSLQ7RoQTnEYxPBLYJ7BFftn8Nf4n/Aju/wgCsGvnEWFKIV4jC1aJsWLYjDcIjHNgR/0Ep7i9/hP8DFYccVwbf4d7TpacNZtF170HYU7rUH7S0K99quXVF4iyt2XPGvaL9C4a32gGA6m54W7cH7Te83vd8b/J/4+yjcoVDYcEFwo32jbZi40S7aBYUbFAYKdwjutKFtPs3lv8C/iFtMBNEK0aLF2XQI4hCHYGIiCIKJ4EF7g2BD8FsEf8AVP6Dw5z6/YCIIgqlNBMFEtCC4x8QFExPBRPCA/wn/C4J7BBcEQTARBMGOIJiYCKb2gCCYCCaCYNeCiSAIggcEN5h4g+ABwT+Pv8K/jx13mHir3SCYCCaCiSAIgmAiCIIdQRBMBEEQj8Vhwz0eMPEG3+G/xn+P/877vdWmpw2ttKntKNxioHCLwq224QY32HCHDbcILghu8IP2b+PfxL+FHb9H8AMe8D2ueIOJ/xUTP+CK77HjAcFVmwh2FKb2DQrBQDBQKGwoFAYmyuEBA/8VJv6AHd/iewRvEdxr97jirRYU7jHxgE37BgPf4YJf4xZ/ggt+hRv8KW7wt3CDP8UNfqUNbTi7/Dn+nrMgCCaCOARB4aoFE9GCiUIQTATBRLBp95h4i4lbbUPwBsE/wcT/iImJYGIimAiCYGIimAgmJoKJHUEQ7JjaxEQQTExEu2JHMLFjQ3CDqU18j+AWE28QBEEwMTGxIwgmdkwEQRBMBBMTExPBRDARBBPTobBjYuIBE/8AO/4CO75HsCO4RzAxMRFMbSK4IlowEQQTE0EQBNEmJqIFQfAdCr/HxG9QWhzuEOyYCP5l/H2f5h432HHBjg0PuMHEwBvcYccFE8NhYuABb/H/YMc/xBW/wwP+L9zj/8UD/jdc8Ttc8Y9xxT/BjgctWmEgKOw+j0IwMLFhd9iw4wYPGJgoBIWgENwg+DVu8Ftc8Ldxgw23+Ndxh38BF/xdfIO/jQt+o5XHglEo7xeHIFp8uHi/aPFYtHi/0sq7lUOhUCgUSiuUViiHQjmUHxc/TaFQWqFQKJ9HvF+hMFAoH66cBUEQX0Y8Fo+VLy9aeb+JYCII4mnlEIcgiBY/n2jRopVWfpqBgQ0bBjYMbB4rH+eyYUO0wtSCYCJaEASFQhAUogWFQlAIgqAQvEGwaRcEt4jDBcG/hIm32oZgIpgIJoIgmJgIJiaiTewIgomJiWBiIpgIJiaC0oKrtiN4wEQhCIIrJgamFq1QKGeFQiEolFYICoVCaaWVQ6HwRivs2BDcYWJi4g8IdgQ3CAaCiYmJYCIIJm4QBMFEtImJIAiCIAgmogVBUNoFE1ftiuBWu3d2wY3D1KKVVtpEYcfApt1qF6200kobKESLdsUFF21i4g2uuMPAd7jiOzzgLW7wHa74g7MH7IizYHcYKBQGrhiYGJgYCApXFIKB6awQh4EdQ9twxYaJaHE2MXGP4KoFExsG3mo7ymFgQ3najmD4p8rTCoXSCoVyKIdCOQTxYaIF0eIQxKH8NIVyVg7l3cpZnEUrh2hxFh8niEN8WYUgDuUQPy5aoVAoFMqhtEJ5rFA+TBAtiOcTrZxFK2eFQjkrrbTSosWXFR+mnJVWfpogCIIgCOKsUA7xbtEu97jXgg1TizYxtSCINrWJINrE1IKJaEEwcUFwg4kdA/daIfgewQ2CP0XwfyOYCCaCiSAIJoKJYCIIJiYmgmDHRDAxEUwEExPBxA0mNkSbeMCOHaUFV+zYtDjEobSgUFqhMBFPKwSFOATR/kS7YscDgrcIrgjeILjRgiAIgmhxVohDtEJQiA9XiHZFEO0HBNOhHL5DULj1WHlaOQsKb3CDe9ziijvs2FDaRbvRLtoVhaFF+xYb7nHBBdEKb/GgRQsKhQsKV4dCMBAUpjYRTG1qU5sOhWi32DFwxYYdAxNTe6vdI3jQvsEVA/e4YEehEARX7YqBiTgEwcSOB+1e27SpTW34G8pZOZSnlaeVz6N8PvFh4vMI4seVd4vHosWXU1oQP11ppQVBMJ2VQ3la+XFxKGdBED+P8n5xFsSnKT+fIN4tPk75ePG0aOXdLje4cShMZxPRgoloE8FEHCZKCybiMDExELzFxMDEVbug8GtM/DWCv0TwLYJCUAgKQRAUCkFQmFpphaBQKAQDExMDE4XCWwQTE7sW7HiLiUIwEGzajmBHMBEEEzuCiYlgYmrBxMREMBFMTAQTE8HERHCvTUxMBMHEBUEh2DDxgOCKYGJqE0EQBMHUgiCYCCaCIAiCiSCIFkwEE8EdgsLEQOEHbXP2A37nMLxfOQyHG2y4wYbdWVDYseEBN7jHLb7RJgpv8YAdE29wxRtcsWHHjfZXuOJ7POAeV1yxIw7RdmfRdm1qU5tatGjBgza1XZtaIbjgigvucYMHvHF2xdQmgmDDhjvc4Fvc4Rvc4g63uMENBjZsKAxtc3bxI4IgWhDEh4vPo7xbIZ4WP64QjxWiFYLSyoeJDxOPlVYoBIXS4udRWqFQfppCaYXpp4sWxMeLL698nPhliJ8mzgqFQqFQKJRPd4lWmAiixSEIgmCiEARBtCBatCAIgmhXBEOLdqNdMTER3CDYEEwtCIJoQRBEmwiCaMFEEARBEExMBMGOieAGQRyCqd1g0yZ2TK0wMLFhYEcQhzibGAiGQ2FqEwOF6RAEhUJholAYmJgoTEQLHjARBBuCQiEoTARBEG06BAPRgiAIogVBtGiFicJ0CK4ICsGDs4HvMD1taEFhahPBQDkMbWgDhR0bokW7aBMDA8HAhmCgtA1BEBQKv8IVf8CmBRPxWGFgojC9WyEoDAxcMRAMTEwMTI9NbWpX7UErBDd4wAVXbeAGF1y1N9hxr10xsGHDPSauKGzarm3Ohj8q7xdn0eJ5xfMqj5Xl/xc/j0L5ZSiUFkt8vPLhLuX5xPOLVgiCOBRKKwSFQrxf+TIK8dPEWXzdCtEK0UqL9xver7ThUChtx0A8rbSLdtGGVg6FCwqFoFAIgh07rrjiiiuCoFAYCOIs2LWgMLCjEBSCaMGOXdu16TC1aIXggitu8BZ3uMcN3iLag/agxdmGDRdccItbvEXwgMLExMDw2O5sWJ4UlENp5ax8mFh+DoXy48rycyhnpZWfJs4KhfLTDH9DWf6m+DiF8uPKL1sQP69yVn4etyhs2tDK2VW7arunFQqFQqE8NjEdCoULLrjBDS64YGDThkOwa9Hiw11QuGgXbXN21XbsuEdwo92isGmF0uIQBDt2fIfv8C2+wx3ucMHFYWBgYGBgYFi+mPJ84iwei0Ocla9f+ToFhfJ1Kj+faEF8vItFEEQr7xZn5acrX0a0eFp8mPL1iJ9m935BYWo7CoVbvMUNrrjBA+5wr03cYMcFE9PhLW604AFXTAQDAxftgolbFK64YseOHTuu2DE9rVCYGJgOhWCgUNrAxMDEwL121e61B+1B27UHbdeu2hXBrkW7ajsKO3bs2HGv3WtvMfHgaeVseOXKWTnETxM/rvw0pRXKWSwfozy/QjnEoSwfKwiC+HGXQhCUw8AVA0G0ICgtnhaHIJgY2LFhIg6FOAQbrigEQSEYmJgOhSDOSistiFYoFOJQiBZnEwMThSAICsFEYWJgaoUrLrg6xFmhsKMQFIKBB5QWh3JWCAqFoBCUVlpQDnEWBMH0WDxWCAqFaKXFWbSB3fsVJgpBIZjawAMGJiY2RNt8mE3bUCjtBgO32kW7wQVB4Ua7wXC4YDhszq64YseOB+zYccUNChcEAxuCeNpEOUxn0aazgYmBiYFCMDBxwRU3eMANHrBhx8B0uOABGyYKcdiwYcPmLCgUNmy4wQ02bB6LNrURFAYKpQWlFUorlBYEE0EcymFgw4aBYOKKQqG0QqFQuGJqAwOlTcShtEKhEAQTQRAUhncrFAYKA4XSCvFuhdI2DAyUw45CoVAoZxPRCoXhMBHEWaEQhyDOJiaCiSCYmBgOhYGBwvBYOZuYmJiYCIIgCIIgWrCjUCiUQ6G0gQuuiHbRpkNhQ2nl48XZ1KKVtmmllVbOylmhHAYGCqUFQVAoFMqHi48XLdpEaeVsaldtanFWKO9WKBQGylmhHIIgiB932RFtahNBIVoQBIWJHcNZtGjBRDBR/r/24GbHjus6A+jap6r5Y8eGBWSSx8xbZZxZHifTBEYSyrIp8tb5gmAjOLi+l81uUqJIqdYiKGw4tGBiIpgIdkwtiFaIJQiCIAgGgtImJoKJYCIIgiAIgosWTASFiYkgCIIgCIJoQbRCIQiCIK7tKESbiBaUNhBtaNGCgaAwUJYgKExMlBYE0xIEQWmF0goDQSEoTC2YWjC0aMHERBAEQRAEU5u44BXe4y1+j6kVohWC+DSFskxtYsPEwMRAUAgKEwNB4YIHHAje4cAFF1xwQRBsCF5ohcLAhg0ThUJ82MRAYWLgwMBEISgE0WKZ2uHawIENFzzgHTYc2HDgPYL3WlzbseMBOzbs2LDhwMTEgULhAS8sUztQmNpeWqFQ2sTABcHERGkT0QqxBEGwaQcKhYFCYSDa0GKZWhAEhQ2FIChMbaK095gYKJRbhaAQFApBYWgHgolCfNzQgmhBIRgIJgqFWIJgIpiWgQPB1IKJiaAwEUwcmAiCYYklmG7FUpgIokULYgmCYCIIphZMRDswMXzYwIENByYK/4D3lkIQbaIwEC2ulVZaUJgolPvKtdJK27TSNhQKhYFgYEOhUCjXCoWBgfI8hUKhUCgMBIUgPl20uFbahgs2HJYNwYYNhbJEGyjLxMSBw32x7IWLFpR2oDBQOFAYWmlTGwiiDQSFDYWgMBFMTC2IpRBtQ7QLJoKLFgQTQRAEG8oSlFaWQlCIVlppE4VowUAwUSgMTEthYmiHFteCIJgIBgqFTQumViiUa2UpDAQDhUObWhAtlguiBUEsAxMDEwOxXFwrlGVDEESL5UC0IAiC4AUOvMZfUSj8iAccKNcKA0E8XVAISnvQhmsHNhzY8RYv8RYvccEDSpsoTExMTEtZpuUdDhQKr/GA7zAxtYlCobRCtImBiaEVBgrBwIGBicKPCF7iPR7wDg94jx1v8RI/4gV2bPgR0TZMFKZWKG1iYiK4YOAFXmBgs7xH4aJNbdM2bf9XfIegUNrEhgMDB4INQbBjIG4FQRAEwYYLXmilRQuCaNEmgkPbMS1BtCDagYmJaA/YMFEIgmAimNpEULjgNQ4MBAeCIJgIgiAIgpfYUdrEoQUbgiDagYGBl5gILtrARLRgIggmCjuiFQ7tQCxBEAQX7NgRTEtwIJiIFgTBay0IosUSLQiiFXZLLEEw8Q4vtcI7/B7/gjdaUJgIgjf43rVope0IDu2wDLzFjr9iww8YeIMdP2LD99jwF2x4h4FC4Z32PYL/QfCfuODPuOA/cOANDvw7gr/hwPeIr88P2g/ue6dFO7R3GPgbNvwF7/AKO/4bD3iFHRMPCHa81aa2abu2/zMKQSEoBKUNTG1govAamxaUW6UFExsKf8QLPLgVS2FgolB4gT9iQ7RopcUSrfASf8BLBJsWLVq0oFAI/hGFgcJmCWIJNkwMbNixoRA8+LBCELzEdxgoTAwEQSyxvMJL/B6bNnBgeFzwgN/hlTZRKASbFi2W4J98XLSBIBh4wB8QS2lBMLSgtAs2/BveYCIYiBa8wZ/cV9pAEBSmawOFw32lDe1wrVyLazsmSjtcKy1uFYYW16LF05QPKwzEEgRxq7AjuGi7dmjR/kv7s2u7dtEetPeWYZnarj1o+9DiVrSJaEG0gd9pQbk1sWkTpb3GK0yPCzZMlDYwMCxBuRYMLQgGNmw4cGjlWqFwoBAEO4ZWOLSylFYIJiamNjEQDC1uBYVgIpjaBbsWrVCWwg8IXmnRDmyIW4VoP2LDgxYUChNTi1auvdfivoEDhYGJic1SbhVKCwpBYdcmgri24cDh4+LzRIvHxX1TC8rTlRa3CvF5ohWC6Vp8WBC3CoX4fOVx0fZpiRb3FaYW7JZy30AQlGXTyocFA7EUgs21cqsQrVB4jw2HVj4sKJRWliAot+LaQLAhGFohPm4gKK2woRCUJZZgR2FohYlNK/eVNvCAWKIV4lpcmx43ERQOrRAU4nHlVlAYiGVqh4+LFkvcmp5mui8eNz0u7osWt8rzxX3R4r6yxHJx7eJ5Lq69dy2IWxftog0/s7gvPq6cgvh1K6cvKYgvK75Ou88UX0b5ZZSvR1C+LeV5yvPE6VPFzy++bsMXFC2+rPj2xS+r3IrPV1qhLIVyOv28hmcqvz1B/LTKtyt+WuWnE6fT0w3fqCCeLn46hfLbUCin06/T8AXEEt+2cvo/8XWJ0+n5hi8kPl359Sinvxen0y9jeIJoQWnl6QqlleeLz1NaadPTBYXSYonnC4J4ukJp8WnKMjxPUJZC/DSC0oLSpucpt+J0etxwOp1Ov1HD6VcvTqfTPcPpq1ZOp9PPZTj9ZgRxOp3+33D6ZsXTxel0+nvD6VctTqfThwynr145nU4/h+ELCGIpFMrTlFaY2BDPFxQKQWmFQqFQKK0QrRBM9xXKtdIKpRUKQaFQ7itMlLYhnm4gGAgKQTxNeVyh3CqUpVAoFAqlFSYKQXm+WKINp9Pjdt+AWMpSKC2ulSWulWvxYYWg3Be3YgmiBdFKixb3TQRBEM8TRAuCINrweab74loQS7RNK5RWTqcvY/eZ4nHlVrT4uKDcKpSlfFhZSisUgnJfUJbSSitEK0u5b2BoA2WJxxUGBqKVjwsGBgYGgqBweFwhCKYlKGweN1yLFm2iEC0oHDh8mnI6Pc3uZxaUJVo8X7RCobR4mtLKsnlcUIhWWiEYlnJfUNiwawNBLHFfsGHDjmiF+LgdO4KhBYUHjxt4jVc4LMHAYYmltM21cm2gEAxEu+CVpwnKrXI6Pe5/AXt+L94J8N2HAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAYAAADl5PURAAAjzUlEQVR4AezBwY5maZ4f5Of/ni8iq7qY7vFoLDRIlj1CXAASO18C98FlcCVsESztC0Cy8cJbhFjakr0ALEaA7J7uroz4zvtDPX+1DscRGZVZWVkRWd/7POV/En+NgXgqfh5TG1q0QhAEGx7wiML0sgc/j02bWrQ77Vf41/hzhx3ldQ1ncfYN/gabFq20eH3B0C7aP9P+O23TLlppu9dVKEwE3+Lf4D9BIdrQopUWryPa9wgKE4VgolBatNLiTbj4a/y1D4ufx9SGFm1oE8HA93iv7V5Wfh6bNrVoU/sGf4vfoLSJ8rrKWZwVHnBBEATlbYhW2kX7jfZn2qZdtNJ2r2ugsGvf4Dt8p0UbWrTS4nVE27BrVxSCiUJp0UqLN+EiCMohKG9LOQQTwcXnKZ9n10obzjYE97jgDoXCxOZ1RYtWWmn3+B4XRAtKi7chzi7a1HbtQYtWXl8hiPYHDGdDi7chWmFix9VhauV50cqrugjiefH64qlCId6+gaBQKBQKhfK2lFbajh3ledPri6emNrU4i1beliCYKIdo8TZEK0xMTBQGylfh4j9WiLclWjBQKAzEy8qXVVpp8WGFQqG06XVFi1ZaaUOb2tDibRjOJgqbNrSplTa18rpKCwobNtyhMLWhxdsRbWBiYKBQCOKsvCkX/7Eg3o7S4qwQPyy+rGjR4nmFwkBpA/H1CII4lNcVTwVBIVqcxdtQCKIFExOFaNHi5xWUp+IQh2gTQZyVN+UiCApB+ToUCvGy8rL4acXZru3YsaNQmCivaziLVg7RCkG0YHhdO8ohDnHYtKFFi7ejfLryZZXnlRYUCoVCoVCIs6FNrbyqi0JphdIK8fqm5wVBedsK8VShUF5XeV5p8VRp5W0IytevUCgUBnZv20QQxFfnIs6ixdtQDtEKhUJ5XaWVNrTpbKA8Vd6GeN7AhoFgICgtXtdAIdpEaUG0aFOLt6G0IJgIgoloU5vOhtcVFOIwtTiUFq20eFXDsizL54hDfFUubl18GaWVs4HC0Havq7Q4K60wUQiCOMTrGiiHQmlxiLNo5XUNbaIwMFAolDY8r7yuIJ4qrZyVFq28qmFZluVGXbwV5XWUzzO1aPGyaEEwvK7S4qy0gYHSSitvw45yiDYRlFZaeVumFgQTExOFaNHibQqC0oL4sHgThmX5mpUPi2V50cVrKy1eR3ye0korL4sWBOV1RYsWrbSBIIin4nUNLQ6FQiGeF29boRxK25xNb0OhUM5KixYt2vCqhmVZlht1UYhWKMTPJ1q04SxaOSuUz1d+GlOLVloQh4FCEJS3pbTyYUFQKK8rKMQhDtGGFq20eBsKhUI5RIsWb0uhEASlBXEY2tRKi1c1LMuyfI7y1RqWZVlu1LAsy3KjhmVZlp9SfDWGZVmWLyHevGFZluVGDcuyLD9W+aoNy/IpYlkO8WHlzRuWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWZblRg3Lsiw3aliWZblRw7J8irIsvxjDsizLjRqWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWZblRg3Lsiw3aliWTxHL8osxLMuy3KhhWZblRg3Lsiw3aliWZblRw7Isy40almVZbtSwLMtyo4Zl+RRlWX4xhmVZlhs1LMuy3KhhWZblRg3Lsiw3aliWZblRw7Isy40almVZbtSwLJ8iluUXY1iWZblRw7Isy40almVZbtSwLMtyo4ZlWZYbNSzLstyoYVmW5UYNy/IpyrL8YgzLsiw3aliWZblRw7Isy40almVZbtSwLMtyo4ZlWZYbNSzLstyoYVk+RSzLL8awLMtyo4ZlWZYbNSzLstyoYVmW5UYNy7IsN2pYlmW5UcOyLMuNGpblU5Rl+cUYlmVZbtSwLMtyo4ZlWZYbNSzLstyoYVmW5UYNy7IsN2pYlmW5UcOyfIpYll+MYVmW5UYNy7IsN2pYlmW5UcOyLMuNGpZlWW7UsCzLcqOGZVmWG3Wx3Lb4eNHKsvwiDMuyLDdqWJZluVHDsizLjRqWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWT5WabEsvwjDsizLjRqWZVlu1LAsy3KjhmVZlhs1LMuy3KhhWZblRg3Lsiw36mJiolAYiBZEK1/WcFbOJgau2LVCvK6pRYt20TYUCoVHFC4YPl/8NHbt4mwgCKIVLgiuXldQzgqFwtCGdtWilc9TPs+uDQRBEASbdtU2bWrD6wp2DATRBgrRdq20oU2v6uKPgvL1KF+P0gqFclZeFq8rWpwF8TYFsfxc4hBflYs73KEcys8n2tQ2bWrRNm3DxHvE67vXdmdDu6KwY8edFi1e16aVs6kFwcUheEBw53VdtdJ2LQjK2UV7r20+T3yeTZtaobBp5WXT2xAE9wh2BNGiRYtWXtXF8uXF16u8fUH5ZSqttNLK21AoBKWVr8bFxEShUIgPi1ZafJ6hlbNd25wFwUC8vgdt16K9cyhM7M6C8rL4sh600uKsMFAICoUdwfS6LlpQ2BAEwe5577T4PPF5hhaUQ2lDG9pF27V4fQMTwYOzcvaN9uBNuIjlSypPBUF8/crbFC2eFy0+T3xZ0aJFixZvQxBn5c27+JOpDQTlZfHT2LWh7c6G9ojCBQOlxevatKFdnQWFC+6wawOFeFn5su61XRva1DZseERwh+AO0+sLgg0D7zFx0S7a1HZtaLvX9ahdUNix43vtXpvag7ZrF6/rgolHbWgPKFy0TXvwplz8SVCWn9pEIVqhsGnxsviySiuttHKIp6LF6xpaeV5ppZVWWnnbhhattGjD6yqUs0KhvHkX/zXeYdeCiU2LFi3a0OLzbNpVu9eiXRHcozDxHQYe8a1D+flt2tQu2lUbGPgL/Ff4bzAxMXHvdV21oQ3tov0N/in+e9xjR7Brw+sqZ3faf45/jP9Be9BKu9Omz1M+T2HDFTv+Ff5b/B8Y+F6LVtrwNlzxDv8brggGLpiYWmm7NrwJF/873mHXgonNWbRoQ4vPs2lX7V6L9ojgGy34Dd4hflh8WZs2tYt21YKBbzBxh4FCMLwsvqyrs6FdtA2FHY/YUbhqm9dVCEobCHbsuGhTK+2iTZ+nfJ4NhYGJgd/hb3093uHPcYeh3aEwtdJ2bXgTLv4omFoQBMHQ4mxq5fN8r5X2B23Ton2P4IIdQeHf+7BCfFlxVtpVuyD4NR7wB+0OOwrleYX4sjbtUbtoV+0Rf8DfYNMKG4LpdU2ttCD4h3jE39M27aqVFp+nfJ4rJu4x8Gt851BaaVMrLV7fhnvcoxCUNrWL9qi98yZc/EkhzsrrC0orBBM7hpcF5edVWmmFaAOFiYlo0cqhEMSXFS1anBXKWaFQmF5Xeaq08vYNxPMKpZWz0uL1DUxMbCiH4Wxow5tw8UeFOCsE0eKstOnz3GnRokUbKOxaobChsHvZ9GWVs6mVtqEwMHDBxAXB1MpZafFl7Vq0qZW2YyIIBgq7Nr2ui7ZrmxZMxNmuvdPidQUXbFoQLYg2nMXbUFpQuGoDwebsnTflIgiiBRNBHOJ58XmiRYuzQjARDMShPBWH8mWVlxUGCkEQRIvnRYsva2pTK62cFQrlEG9DfL0mCkEQDF+Pwh3uMTAdBqKVN+niT0oLCkGwaXFWWvk8F+1RG9p0tmmFgSsGyiFaOZQvK1ppQyvtPQoPeMCGDTuCclbOype1aZsWZxsG7rAhKDxow+t61IZW2sCG0q7acBafJz7PnbYjmNg9NZ1FG17XxBXvEVy0IJjaRdu1aMOruvijaNHiEC2+jGhxFm2iUFohWjAcSou3o1AoZ8HEBXGI1xEtnjdRCDbLT2Ui2sBA+XjxugqFwkBp01fhIthwxY6BQlCIs2ilxeeJFmc7Cht23GPgATs27JieikN5Xmnx09i0R23TJgbeY8MjLigMxKcrHy9eVtrUytlAcK9dsSPYEJTnFSbKIQ7lqfg0E4WpBRNBMBGUdo+JRwxEK62cTc8rbaIQFApxVlpQ2sTQNgQPmHjEnXbVopUWLc7K84LSgtLi8xV2DAQ7hrOJOAytUJ6anjecTWeFwtTiRReFiempieGHBYWgvKycDW0gKGdB4YJHLXjEBfHUcIizaPG88uMF0aLt2jtMlFY+rDwvWhzirPw0SitcEFwxMPCIR9w7i0MwUZ4qBNHKj1O44BE7vsEFhYGJQqEQh+nzDS2IQ7RyKBQmgolNe8B3KIfC0HYfp7TSpkP8sHIWP+yKCyYGNlwxEC1aOYtWzoYWLVq00spZEMRHuSgEAwNBMBAMlKdKi5fFy6ZDIdqdsx2PGAgGLnh0Vs7K2fDxCvGyXYt2p23aO7zH0IIrBjZctTgrbTorZ8OHBfGyTQuCoZX2PR7xgB1Du8e3+K2XDZSnopVDeSpetiG4R+EeD3jEjk0LgkLhHhNTK62c7Vq0clba5lAoFK4YiLZhw0Thiis2PGh32oOn4mXT54mPU1pQ2LHhAXe481ScBROFQhAfFpQ2tCsKpW3a0KazcnKxY6BQ2DExtCCYzoY2nZWPE4dCOSvtgit2BMHEholyCOIsXrYhnheUszjbUZjOymFg4A53uGJgIg5xiFZeFof4OHEoRCuHaBcUBu5QeETwB5RWnhfEIVq04RBPlaeCQrDhEQOFRxTe4Q6lBdEKA0F8nnKIFgSFcgiCYEdhaN9gYmDHox+ntHhZPK8QP6y0DRMXTNw7TJTnFQoTcRjO4iza1DZnhSDa8KKLgUIcCgPTYWhBUNrwsnheaVcEU9u0oT0g2PANCncoPGI4K2flLM6ml01PlcM9gqFdEYeJbxBM/AGFgQfc+3RxGF42HcqHlXZxtmNHsOMBhe+wo7R43nAIyln5sKA8VVohuMeOB1ww8B6/R2mbFkw8YOJOK88rZ4VyCAqFQjwvCKZWGLjiDleHe21gOiuttGjR4uOUFmfxcaY2ccEDNm1HYXhqIg4DE3EoLVqhtOmsHEorHxYnF4XCFbs2EOwY2tSiTa0c4qmpledNLQ6FoQUT0a4IdgzEoRBncShPxVk5K2flbHc2UQ4XXHHVNgQThR3lqdKiledNZ+UsnldaPK+0DQOFC3ZccdWms0Jpwe6sfJp42Y53iHbBIx5wj2Ag2sDEFcMPixaHOFy0oU0tiEMQBNGmVhiYuMcVA9NThUJpE0EhPl5QDuUsPt57FAo7Ni0oh8JwFgyH0grRymEgKB8WLShncXJxh++wIxgobWJoU4tDsGnR4hAMh2hxKGf32tR+h0Jh4Ff4+/gO32JziKc2T8WhtGhxtiEOcRZtaI8INq0w8Vf4S1xReERhOsQhWnletItDtGhBOcRTFwQ7gguCoT3iG/wVdu0R9xjYnRUK0QpxKC3apkUL4jAc4qmJYGib9g0m/i0u2LV7XDHxLf6tVp4XZ0Mb2qZtKFy0i7ahsGl32obCBVd8iyv+PS74DQpXbSAoZ3/mbNMGSosWFEorvNdKKy3a7qycXfAb/JmPU86i7dqOwkThisKO4OpQeMDA1N4juKKwa9Hi5OJ/xN/HhqlFK4epRYsWhyAOcQgmJoIgmAimdkGwI/gDgg1XbCj8K+y4IAiCIIh2RRBMTARBsCMIgokgCKY2EQQT0d4jKEy8x8RAMBB8i/8V7xA8INgRBMFEEAQ7gmBiIpjaI4JgIpgIgl0LJoIgCArBFRMDQSH4f/Bb/J/YsWNi0x4QTEQLJoIgCIJCEMSX87/gf8Z/iQuuCL7BVftz/COtPC9aaaUNFH6NgXe4x3e4x7fY8Bvc4R7v8B3ucY8LfoU7h1/jn+D/xo7fIZh4xI4rdkxcMHHFFTt2TARTC4JCobSBQmFgYKBQGChtoFAobcfAP8fE/4sdA7/De7zH3+IBv8cDfof3+B7BAx7wBzziqg0M7YKBe1xwwcAdNtxh4g6FO2xatDi5+C/wDxGHoBBMBLtDEBR2LZiIFkwUgmAiCCaCXRuYGJh41K6IFlww8Q8wMRFMTAQTQRBMTAQTwcREMLEjCIIdU5uYCIKJiSC44oLgETsmgkdMBBP/DsEjJoIgCIKJiYkdQTCxYyIIgmAimJiYmAgmgokgmJjYtR07gomBiQ07/jPsuEMwEBSCiYmJYGoTwUS0YCIIJiaCIAiiTUxEC4LgexTuMPE9Cjv+A/4lgomgMLW/wr/QyvOmVigUBu5xwV/iDt/hHn+Bd/h7uMdf4g5/jnf4C9zjO9zhW9zjV9jwK2z4T7FjwxUDj7jgATsecYcrJq4YuGo7SgsKhYGBwkChMLSBiYEdQyutUBi4IniPR/wKD/gt7vF/YcPExO/xgN/iAf8OO36HB/wWV3yP4Io77Lhodyjc4xHvMHHBjm8wcfFUOYShUF4WhyBafLx4WbR4Klq8rLTyYeVQKBQKhdIKpRXKoVAO5YfFj1MolFYoFMpPI15WKAwUyscrZ0EQxJcRh2jxVPnyopXnBRMTExNBEM8rhzgEQbT48oIdOyaiBROFQjlEi+eVw8DAhg0DGwY2T5VPchEE0QpBEATxvEIQBBPRgolCMBEEwUQwEExEC64IgolC8HtMBFMLJoKJYCIIgmAimJiIFgRBEARBMDERTAQTE6VNBBNxuGCiELxHMLQdQTARBEEwEQRBEEwtCIKJIAiCIAiCIAiCixbseI/CAyYumPgGQRA8IAiCiYmJaEEwMREEwUQQTEwEQRAEQTARLQiCzdnQCsGu3WtXDFxxxdBKKy3OCoWBgYGBiYkgDkFQKK0cBjZccMEjJu5Q2BBM7JjYMTARbccVhWBgYGhDC0obKBQeUSgMbSAYmAgKpRVK27QgKJQWBMHExBVXPOIRG4JCobRCMDC0gaAcNmzawNQm4lBaaYUw/El5XqFQWqFQDuVQKIcgPk60IFocgjiUH6dQzsqhfFg5i7No5RAtzuLTBHGIL6sQxKEc4odFKxQKhUI5lFYoTxXKxwmiBUEQP71C+bBo5RBEKxQKhUKhnJVWWmnR4suK520YKAwUyqG0QnleOSuHIAiCIAjirFAO8WHxdy4KhUJQDkFQCMoh2nAoxFPB1ApBYWJHsGOiEGza1DYMLfhbBO+00korZ4WBqRUGgkKhHAqlFUoLCoVCtCsmHhFsmNixoxAEwcR0iEMcSgsKpRUKE/G8QlCIQxDtvcPusCEoFAaCq1YoxFkhCIJoQRBMxKEQH68Qh2Ai2BCHHYWpPTobnldaaUMrFAY23OEOd7jDwEChMBFcMfCoPaDwa1y0oRWueEThig07JoJCOQt27Ah2h4nSgkI5FApxViittKEVHrFjYsfE7rBhwwUTd5i4w8SOK3bsmJgOA0MbmBiYKAwMBAMDhYENm7PS4u8M/3/lrBzK88rzyk+j/HTi48RPI4gfVj4snooWX05pQfx4pZUWBMF0Vg7leeWHxaG8rvKyaKUF8XnKzyeI5xXi05RPF8+LVj7oYseOoQXTU1MLJuIQTMRhYmrBRBwmJoLggomJYGjR3mPiTvs1gu8RBEEQBEEQBEEQBFMLJoIgCII4RAuCYMNAtDttonDBRBBcEWza1EoLSisU4lAoBKUVCqUVBiZKK5RWKO1OmyhMRJt4RPCI4IqpFQaCQiEoBEEQBOUQBIWgEARBMDARBNGCQjC1HcHExBWFTbs6e4dv8XtnpUWbKE9N7NgxcMVAEBQKhcIFF9zhDu9wjwcUvkVhYKIwMBBsWjC1iXcoh6EF5TBQKAwUChsKhYGJgWBgYiAorbTCpm0ICsNZEEzs2HHFFRdtwwUbgg2lDUxtxxU7Bh4RRJsY2DGwoxBtaru/c/FDgiBaEEQrPyx+GuXDCvG8+GGFeKoQrRCUVj5OED8sniqtUAgKpcXPo7RC+fEKpRXix4sWxKcJ4ssrT5UPi69DaYXSogXB9OnirFAoFAqFQvlsF4WBwtRKCwpDC4LCRCFaOQSFgWBgakG0oQVBHKa2YWrBRDARFAoThdIGplbawK4VSiuttEKhEGeF6bAjKBQK0QZ2rTAxMBGHoDCw+2HR4qwQLVohzoI4TATRgomgtCAYWrSJIAiiBUEQBEEcgiAIgiAIgiAIogXRopW2YWAgWjCcXXHFcFba1AYKpQ0UChsmJkoLgiBatEIQTOwYDhOFaEMLJiYuKEwED7hiw8QFhUJQWrQgDsHAxEQhmCgEE/HUQDARZwMDA8Nhw4YLdi244oodV+xasDtccNEGBgauCDYMBANTK2ebvzP8UXlZnEWL1xWvqzxVlj+Jn0eh/DIUSotfpvLx4tOVj3ZRXk+8vmiFIIhDobRCUCjEy8qXUYgfJ/9fe3C2LFl2ZQV0zH08spFQYYXxwmfyVzzzxkeByWSlKmVcP3tiaIEdvO7NaLKJbORjeFS/bUGNoEaM+rBtxKhH9WEHDiwsBHEpio2NEyc2Nopie23jMBYWFhaCjWAhCDa2sRGPFoIguKNGsFCXIDiwUCxj4W4EcdnYuOOOO+54wQve4z0ObNxQHCiWcUdRFEWxsfE1vkLwDsENJ4Iay6gHy9PbirjEiEfxaerpSwji4+LpS4hHMeKHqUdBED/Izf8vqKf/pz5PEB8X1O9XUV9WUJegfrj6NO9R3BGcOLGxXYJgYeHAgSBe29hYKIqiKIoiHhVFsRGXuATBgYUiWFjYCIJ4LQg2NjY2NjaKIgiChQMHbrjhBS+4446NjW3E24Lgb9h4QbGMEws14k3L088nfjn1qF6rSz2K3774bSqC+G2KL6dGUZ/t5omiqBHfrx7FDxc/jxr1tvo08dtRP8zhw4ogxoHgwIF3eIcbbrjhhhtuOHDgwIGFhSA4cOBAEcRYKIqNjY2NYuOG5RIEQRGXIIjL9mgbG8FGjIViGQvvsHA3Dmwc2DhwIAiChSAIgoWFm7GNEwc2lrEQBDccuOGGAzcUN2wjxunB8o8uHsWlfpj6uPhhYgTxqJ4+R/zygrjUJZ4+V1EU9VE3QVHEZeGOhaJGUcSot9WlKDYWThzYqEtQl+LAHUFRBMXCxnYJinoUI0ZRIwiCugQ16tHGwkZQFEVQbAQbC9sI7rjh7lKPguBEUATFwgti1CUeBUUQFEERI0YRl3pUFMX2Wr0WFEFQI0Y9qrFw+rBgIyiCYhsLL1jY2DhQYxtBXWrUOIwTwcaJelRsnDhx4sCJO14QvCB4MRaKhRMLdTlQLNxQbJw4EZxYHi2XoEaNGMFCsFwWgmIZy1i4Y+PEHRtFsVEUcSmKYmNjY+PEiRPFHTWCjdOo14qiKLaxjBjxdzdFEJeiiBGjCGIUxUZRxAhqLARBUGzccbgUQVzu2DiwPNqoS1AEMTaKGkURLGxvixFsLBQxgvp+cTkQFEWNE3EJgrpsFEEQLJeNoh7F2C5FPdooio2i2CgWthEsY2NhexTUZRsbxUZRoyiKokaxEY9qxCgWDvwbbsYNd2yX4EBQBPVaUY9qFHHZKGoECwduCIIgCBYWgiCIEcRlYSGIURTFQhDExxVFEW8LgqAuNWpsBEEQY2Nj444TG0WNYCGI7xfEWKhHQVyKoqiPujlRYxsbRVCjKIpg48TyqEaNYqPYCIrgwGkUGxvFRnHDNooaQV2KoiiKYqGIsbFRbBQbRVEURVHcjWKjCDY2iqIoiqIoahQ1gqAoiqIe3RDU2KhRxFiosYwaxUIRLMSlKIKNjRhFsV2KoogRxAgWiqAItlFso1hGjWJjoyiKoiiKbWzc8Q1e8Df8EdsIagRFvS0IinoUBEEQrxVFUSzEJUZRBMFCsVAUxcbGiRPFRrERFEVRbCyjiEsQBMEdQVAERRAsxFgoFhaC77CNIFhYWAhiFBsnTpxYWAiCGqdxNxZObBQLCyeCeC1GsYyFIP7uJkYQxNhYuKPY2IixUSOoS1EUh3EiCBaCYKHGMuqyjaIoggNBUQTb2Ijxgo2FIF4LiqAIgiJYxoliI6iPW0ZRowiKhWIjCOpSFBvFdlk4UWyj2Ngogo1i48RGUSyXuhTba3UJNooaNYq6FEWxURTbKDZqnNhYvt/CiQMnNoL/gBeXoKixESxsI0aMGjWCIAiK7bV4FATBwoEbbrjhwEIQLBTFgSII4lEQLCO+XxAjRhAEQRAsBEFdtrGNoEYQj4KiKOoSBAc2DmwsFAeKA4cRlxoLcdnYOHEiXivi726Cu1HEOBEsBCeCZcTYxkJRY6EIDgRFsFFsbKOoS1DjQI07Noq7URQbRVEUB+JSxIhLUAQ1YsTYCGoUC8VGECxsl2BjGadRj4qi2CgWguAwim0EQTyKS7BQLASnsY2iRl3uqFEUdVnYWNhYqMvdoyAuB4qiRl1O1CiKoii+wolv8a8Igu/wDifiUbBQvPO2uhQxllEUJ+64Y+EFwQuCFxQvWDhx4sTC33BgIzhwIjixsHHHC4r3KF7wFU5svKC4o9hYRhAjKIKbESwEC0WwEJxYKJYRLBTv8YL3eI/3eMGJO+6444477rjjBS+4446NE9vYOBFsBAvBge9wosYNNyx8hdNYRo27v7v57/hnFEGMjQMnFk4UB4rihoV6rSiKoigO3PGVEaNGUdSosVGcxg3bpahR1DixsVHjHQ5sBEVRbBTb2CiCO77FiYXiRFEUG0VRFEXxNW6IsXEaxYGiqHFiYeFrbBR3Y2GjRrFRFBvBDTWC0zhRl6IoijtuuKHYLsWJYqNGURTfGkVRoy41iqJGcHOpS1FsvMfXRvAef8R/w1+MItgoir/g7lGNGkFdlnHDwl9xw59x4H9h4U+44T/hwJ9w4E848Acs/AHB18YNRVAUd7zDHQsnghP/ETVOLGwsFAeKGDW2sRAjRlDjQHAiqEtcvsOJf8Udf8WJP+M9/ow7/ow7/or3+Bec+Bec+A4nXhC8x0JxYOM9ihve4x2+ww3BO9xxwzaWUQ9u/iuCIiiCIsbCNhY2gm9xGEW8FqPYOBD8E77CO6/VJVjYCIKv8E84UKNGjLrUCL7Gn/A1isOoUaNGEQTFf0awEBwuRV2KAxsLB244EBTvfL+gKL7GP2Mh2FgoirrU5Rt8jT/iMBZOLB9WvMMf8I2xEQTFYdSoS/FffFyNhaJYeIc/oS4ximIZRYw7DvwP/AUbxUKN4i/YKOJRfdhC8D89WkYR1KhHMWrEiLH9MmLE2H6cAxvLOD1aKOpRjODEwkZxxzYW3uPEd1jGxsL2QTfLqNdqbNQoaiz8wSjitY3D2IjxLb7B9mHFgY0YCwvLpYhHxTKKYuHAgROnEY+C4ERQFDcsIziNuMQIio2NbWwsFMuo14qg2Ci2ccfNqBHEJfgrim+MGicO1GtBje9w4J1RBMHGNmrEoxej3rZwIljY2Dhc4rUgRhEUwc3YKOrRgROnzxcfFqNGfZr6PEH99GrUT2MbQb1W1GtBsF3ibXWJEZ/kZrvUqLcF2yhuLvG2haKIy2HE9ysW6hIUh0fxWlAjCF5w4DTi+xVBjLgURbxWjxaKA8Uygvq4hSJGcCAo4lKX4oZgGcHGYcTbYiy8Q11qBPWoHm0ftlEEpxEUQX1YvFYEC3XZxum1+jQ16m2nS32/elt9mvrh4lJfxol6rd62PTq9bXt0utRHLT+3elt9XDwV9fsWT19SUV9W/Srd/Fj1ZcQvI349ivhtic8Tn6eefqj6+dWv2vIl1agvq3776pcVr9WPFyOISxBPTz+r5XPFP56iflrx21U/rfjp1NPTJ1t+q4r6dPXTCeIfQxBPT79Ly5dQl/pti6f/o35d6unpsy1fSv1w8fsRT/9ePT39IpZPUaOIEZ8uiBGfr36cGDG2T1cEMepSn68o6tMFMeqHicvyeYq4BPXTKGIUMbbPE6/V09MHLU9PT0//oJan3796enp6w/L06xZPT08/k+XpH0dRT09P/9fy9NtVn66enp7+neXp962enp6+x/L06xdPT08/g+VLKOoSBPFpYgQbB+rzFUFQxAiCIAhiBDWCYntbEI9iBDGCoAiCeFuwEeNAfbqFYqEIivo08WFBvBbEJQiCIIgRbARFfL661Fienj7o5regLnEJYtSjuNSjeFTfLyjibfVaXYoaRY0YNeptG0VR1OcpahRFUWP5cba31aOiLjUOI4gRT09fxM2PVR8Wr9WojyvitSAu8f3iEiMIinhbEZcYMYIacYm3LSxjIS71YcHCQo34uGJhYWGhKILThwVFsV2K4PBhy6MaNTaCGkVw4vTDxNPTJ7n5uRVxqVGfr0YQxKhPEyMuhw8rghoxgmK5xNuK4MDNWCjqUm8rDhy4oUZQH3fDDcUyiuCdD1v4Ft/gdCkWTpe6xDg8ikcLQbFQ445vfJoiXounpw/63wTdhS07SlnhAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAYAAADl5PURAAAhNElEQVR4AezBy4pl25oQ4O8fc66IyMxzrcJDFSVogSCICII9e76B+Cp2fBMbYtOWL2DDTkF17HhpCWohlqIcL3U5e+/MWGvO8WvVj2eeWSsi8rYzV8RZ4/uCf5n8LhrSufRlhbIqB2W1STQkZhzx17wsfxv/Cf9BOWHF0WUt9g5KU/4r/if+gb2DsrisGWmzKH9f+cf2vlO6cnBZicABiX+K/4x/4mX5OToSiSMCizIrR2X2HMz8Ln7X49KXFcqqTEoq4dwJP1TeeFr4smZlUWZlUe6V38I3+JmyoOPosk72DkpTvsERP0BiQuJGWV3WpKRyr/xY+aG9SenKweUFZuWHeIODclAWe7OyuIxUTsodFiRSCTRltjd7DmYSibBJhOcnEVixIPE/PC/NXkPiO3yDewQSHa9c1g887KSs6PgOifQ8hb1UbpWm3CnheXiHwIzEhGazKKuHpctI5UdY8EMkQkkllaZ0pSnpkmYS6WHp8lIJm0AgPX8NiUAgEEogPE+hrFjRPW9prytdaUpXJiVdVleakkh0hE3aSyVdRtp0pWNSQknP2exMIF1et0lMSAQSK9Lz0+2dkEjlgFBWNM9TKoHwsoRNszfZC5d1pzRlxmzTlW4vlO5yEr9A4h1W3CGURLMX9sIlzc4k0vMRHhZIL0cgEDbN8CUkEoH0/KVNoqMjPC6VdFlpL5VE2kxKV8JzMJNIBBLheepIpWNBd3k3ymKvK4FAIOwFustalVQmJZUDZhyUFYkDEqvLSgSaksqkdGVSTkpTwmU1D0uklyORCAQmJe2FEp6DmUAogVAC6fK6EvYS6fkLpMeFywp7oYSSzgUCgdXlpV8fgVAaupchkUgvyUzaSyV9HelpoSS6kggE0vPQPWxVFpywIJSO5rJmJZVQVqUjsSKRyr3nZbEJdCSavYPnbUVX0uMmpbusGR236Jjthb2wFy6pGYZh+CzppZq9GM1eR0dzWUcfZsKE2aYhPA9hr9nrXo5U0vPXleZhTeke1lxWItHRkUogkUpTTvaaS2qGYRiu1OzZ6MqkNKV7WFO6521CYsZsL1zeaq8pXWlomJBIJJqSLisRNoFAoKErk7Iq4XlqaGgIpIetSndZN8qMjrAJ5w5KeA6aYXjRwuPSMDxldnHd05qSSkPzcqzKEUccEUh0HFxWsxdKs5dIpNK9DM1eKM3zFAg0BFLp9rrnYcWKEzpSCSSaEh6WLqkZhmG4UjOBVAKB9PU0ZVJWpSmhrOiY0bAivTwzAoE0fF/CJrGgY1JWZVK60jwvK1YsCITnbVUO6AilKal0pSnpOWiGYRiuVDMMw3ClmmEYhivVDMMwfK/SS9EMwzBcqWYYhuFKNcMwDFeqGYZhuFLNMAzDlWqGYRiuVDMMw3ClmmEYhivVDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaoZhGK5UMwzDcKWaYRiGK9UMwzBcqWYYhuFKNcMwDFeqGYZhuFLNMAzDlWqGYRiuVDMMw3ClmmEYhivVDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaoZhGK5UMwzDcKWaYRiGK9UMwzBcqWYYhuFKNcMwDFeqGYZhuFLNMAzDlWqGYRiuVDMMw3ClmmEYhivVDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaoZhGK5UMwzDcKWaYRiGK9UMwzBcqWYYhuFKNcMwDFdqNly5UNJeVzq6kobh10kzDMNwpZphGIYr1QzDMFypZhiG4Uo1wzAMV6oZhmG4Us0wDMOVaobhg4Vh+HXSDMMwXKlmGIbhSjXDMAxXqhmGYbhSzTAMw5VqhmEYrlQzDMNwpWbuMCERNuHrWJXJ0wIH5RbpebhRjsqtclK6csQRTblXbnye8HlWZVJSOSgL7nFA4uj5CZtA4DW6c2+VV56nhqYk0l5TuufhFRYEGsJeKKGkEp6D2fCVBMK58LR0WamklyORhq8lvVQz91ixIhBIm6Z0X0az15WuHJRv0fAKJ9wicXRZR3v39m4RaGg2ByVd1r1yq0z2bnCDxV5DIj0PqaSyIG0m5cbz1BE2B+WkhNKUULrLequcsGJB4qDMyqy8U26V7pJmw1eQXq7w/KVff6GEvXBZaRNKeClmv3RAeNxkL5XweVI5KbMSykn5kU3Dvefl1t5JuVcawiaQ6J6WntZ8njsPW2xCSc9PKOlhqYTSPE8rAh0dYa8r3fM0KT/wtK40pbmkmTRcQiKRhi8hlVRCSSUM36dEemlm3uIt7pRZWe01e4sy+zyhzErYOymBwBEnBNLlvVLeKk3pSkPgBrdYlFnpnha+rLD3TrlV3uItQpmQCHSEy+r2GjpSuVdulWZvdVmT0hFoaJiVo71J6Uq6rFscbd4pBwSaclTe2EuXNPulRBi+bx2BVAJh0zwvoYRNOpdKet5CCQ8LL0uz111WIOwFAuG5m/ltZbYXSirhyzgpTenKQXmtHBF4hVf47zjhZBO+vqNyo3yrvFEOaPhX+Dv4bXQ0dLz2PByVV8qfKn8Tv4cf4Qb/y164rElJm4Y/wt/FnbLaSyVc1jeY8Er5W/jnSDT8ROnKrLxVust6jZ/iLRZMaHiHjlReKf9H6crBJc38N9zijXJAx2JvVkK5V2afZ1Ga0pUb5YDEd8otfow/RqJ7WvqyjspJ+dZeQ8N36Dig4YDEraelr2NSXiknZVJWnJxLl7V62IrVZvI83SBsGr7FWy9Dxy1+4MO88pzM/CZu8FNlVjoSq3Jj76jMPk+zl0rYe4dE4A5/XekeF0hfVlNWpSuT0pQ/wG/gfysrVqTHBdLXEUra+7HyrectlBmJQCCVRTnYS5c1o9tMnnarHD0PJ6xYMGNBYkHgTjkqN8qiNJc0+6VA2gvPSyB9uET4OkIJJZxrCHR0pKelryc9LLw8oYTnryF9uFBCSZfV0NBtAmGv2WtKc0kzK7oSNoH0fuHzdOWt8trePQJ3yqr8MQI/dlnvlFmZ7S3omDHjhI70tFDSlzUpTTkpoRyUW0x4i4ZJWVxWV1JZlY4V3cNOSnNZgbBpaB53r6TSXFbHd1iRmJXZ3qLM9rpLmkkk0l4ikR6WSvo8qaSSStikTcfk+QglPCzQEEgk0vulr6N7WNoLBMImDZ+rI2wSzYdLl3fADcLj0l56DmYCgbDXkJg9bFaa78et0uzNSioLDviJc+lc+LJu7Z2USZmUFSu60pBIe+EyViWUVJryzl5X0mXNyqJ0ZUW3OdhbldllrUibRMeMQFdW5UY5KunyTggllVBSuVNWZVLSJc3+XCrpXHpY+n6lh3UEQgmPC89X2EskAmmTLisNX1NHYlYaAunlCIQSSnoJZhITGlabRPjy3iqvlJNyUBo6Tmjo3i9twpe1KpNyUP5U+ZFywCubVNLHCx8ufZo3yltlVhYlMWH1uEAibNImnEsfJxF4rXynvMM7TMqizOi4U9Je97RmL5xLm/C0hqaccMI7dHsH5d5eQyI9LRDoPk4oDYGGRFdWvMZ3eKN0NGVVEmnTbQJNCSWV5nGJUFIJH2Mm0NGd6768Zi/sJQITTj5MeFx6Wvh0iVDS3q0PFx6WSvryQgklsdpbEfbSJpX0sPT5AjM6ViUQaDaBQNpLe2kvfJxUUgkPSwRCOeGAsAmEEh6WPlz6dKGkvcCCGYlAQ0dDKl0Je6kkAqGEh6WHhZL2wlNmOhasaDYdiQWB2d6qzJ6WnnawN3tYYMZJOeIGq73wtLCX9tImkJ422YTNj5XEPb7DO8zK4nHp65uRWJXvlO+U7lw4F/YC4XFpE86lpy1Y7CW6vckmbJq95tMsSkcgEDihodlLZUJisjlgxgmhzEoos9KVrqS92V5XunJQutKVVFJZPC5xwi1WTGjK7OOkvbBJhL0VgVC6MindU2ZWNATCuUQ6l0q3Fz5M2oTHzViVRCoTOsImkfbS0xrSwxJhLz2t20s0NBxwwOL5CKQSNqnMSsOEFauSaEp4WCJt0l7YpHPhXCKQHneLg68nkEoiEQibVBIdgVlpSsOKkxJKKKGEh4W9tJf2QgklkD7chI4ZHZPPEx4Xzk32AolUmqfM3GFGQ7Np6Jg9bFLSXtpLTwt0zEoqi01iwoRZmXwdaS8Q9hKhNGVVfoE7hHKPwB2O6D5PeFp6WiqLvVRSmRA44AYLVnSfJ32/Ag0zAqvSlUAg0DF5WjoXNisC3blEQ9g0m6YETsoJgcCEVVmQSCWV1dO6Ekook3L0tAlp05SurLhDoikdgUA6l2g2iVQCgY5UAqGkvbAJJbB4WvNnZgKBFStulMTqcV1pNulcV8LD0rlAs+k2i720CefSJpxLe2EvPG1VJg+bsWBRJiQ6wudLX9akBGZlweJ5mnHCETdINJuGjhXN+6VzadOUpnQlkTaJRCKR6JiUho4bLJiwIpVUAoHmw6VziXAulFRSSSWVVCbcI5SO5mGhhL1A2GtIJWwCifCwRCKRCA9Lf2bmn+ENfojELQILuk1TQrlH4rWSStokmk0qabMqr5TFXkPHHSYs+Ble4xWONunc5FzahJJK2puQNmnvZO81Et8o79DxDX6Ov4fADxE42aRNKuFhqcw2qaSSCJt0bkaiIXFEIpXfwM/xl7AicMIJDc25QCqBdC6VSUklkTbNJp1rSMzKjfJT3OIPMOMb5YAFE17hP9oLe2kv7S3KCYF75aTcI3BUVmVBoGPBhAX/Fv/audXe6sOkkvZWD0tlUg7KnXKrTMpv4Me4U5q98OnCw8LjJh9j5h8aflUikUh0pSOR6EjlWyQSHXfoOCBxj8R3+Hf4F8oBicXLsGBFR8fJ8/af8Xv4fcz4BRK3WHDAT/A79sJeKqF0m8AtGhI3yo0y4YADJtxiwg0SM2YcsCqv8Tfwh1jxDokDTlixINDxDh2BBR0rEmnTkLhB4Eb5GQIzGt6g4RUCrxG4Q8MtArPyBg3/Bh3fYsUrfIsF91hwROKIBfc4IJUjOk4IpaGhY1ZuEJhxhwNmHLDioBwQSirhV838e/wV3CgTEisSHYmwSSwIHJVERyqJjkAi0ZFIdCROSqBjRcekJBKJxAEdf4SOWyQ6OhIdiUSioyPRkejoSHSsSCQSK7rS0ZFIdHQkDljwCyQCKw5InNBxQMc/QiLQMSORSCQ6OjpWJBIdKzoSiUSiI9HR0dGR6Eh0JBIdHU1ZseKIjhVdWfFDrHiHREPiHomOjo5EVzoSHakkOhKJjo5EIpFIpaMjlUQiEQgkOo4IBL7B7yPxc6TNAb+N/6I0D0sl7c2YcY8DTrjBPW5xjwkrDjghkJix4ICTcosJRyw2gcQrnHCHIwIn3GLBhAUHLEisaMoNAj9AwxsE/ioCv4mGGzT8Fhp+Bw1/GYGfIfAjNLx2rqPZHHGDBTNOaDjiFToCR9xgwQGpBBbMeIcDVtzgLW6RmJAIH6oRCE9Lm0Qq6cOlp6WSzqWSnhZKeFzYBAKBQCCUQCiBsAmETXi/9GkCgVACgUD4fqSnBQINgfDhwl4ikUhfRtqkks6F56Ojo3u/sEmbRCKV9HWk0u2lEvZSSQ8Lm1AmpWFC87DwMWa/tCgrupJIdHRlRWJFYEIilVRSCaSSSKSSeIPECYmGVBJHdNwjkej4CTp+gURHoiPRkUgkOjoSHR2pdKxIJDo6OhIdHYmOREfHO6xIJO7QMSHREfhTJH6CxJ8iEEglkfYSibSXzqWP962SWHFA4AYdCzr+CIkTErdINKS9RCCRNolEKolAIJBIJBKJRCCQSiKR+E4JdExKIvFH9m6Q+Cl+ij/0aRKJxAmJRVmVVTkpqSzKqjSbGxwRWBFIvFW+sfdOOSkdabMqRwS60hCYEZjRcEDDAQ0NzSYQaGhKR8OKhlQSgYMyKzfKa6VjxoTApIRNKKGkcofwfqmkEkjNL4WHBQKhBAJhEzaBsEmkD5NKIpW0SaRN+DSBsBc24XFhL+2lEjappL30cRJpk76sQCJtwia9XyqBQCAQCJtQAuFcIHyYRCqJ9GWFzxMIBAKBsBcuKz2uIdCU8HHCXnhcIpE+T/ozM29wZ5PoSqKhoysrEpOy2gTSwxJpE0ibZhNYlQmJGYkFiQmJWyQ6Eh2JjkQi0ZHoSHQkEh0dHYnEio5ER0eiI9HRcYOOGR0LEr9Axy0Sb5DoSPwMK/5E6WhIdDSbRFcCiVA6uk3aBDoCXUl0BBpeK4kVDakkAolE4g4diY5UAoGGbhMIdJtEINARSB8ukMobJI5oaEhMyrf2jsof4zW6T7Mila50exNW3OGIGyw4IHGLP8GKVZmwYrY3YcENTko6N2FxriNsGsImEGhoaGg2XVnR0G3C3orAghvc4xYLZhzRcI83mJTm/RoCs3KPW6yYsWDGEQcsStoLf6Z5UtiEh4WHhe9H+P6kD5O+H4n0fuFx6Vwq6csJJZE+XSihJBKJbi9swsPC+6X3S19H+Djp84WvKz0skL689LBUwmNmv9SVjo6mJDoWJdGRStgLJT0ukMq3SEzoaOhIZULDDTq+Q+ItEg2JjkRHoiORSHR0JBIdXelYkUh0dHQkEh0diY6OjrdI3KHjF8otVnR0JBIdiZ9jxa29dC6RSJv0tEB6v1RSuUdiRUdDIpSm3CORSHR0JDoSiUQi0dHRkUgl0ZFIJBKJjkQilURHYlYWJCZ0HBCY7S3KCd+h2Wv2FgTS3owZ73DAghvcoymJxAkd75RFeafM6AgEuk1XFpvEhNVmxoIbLFixelwgsCCwIm260pVE2nSPawg0BCZlUg4I5zoSCwIdgQNWzOhINGVGQyizMqOheUrzXolEIpFIJNKHSd+P8LjwuPR+4WFhE0oo4cMk0vulc4FAIJRAIHw9gUAgEAgfLxAINJ8nlUT6eOn5SS9D2ISSSirdx0t7gUAgEAjfl5kjOgIdgaakEpiQSAQ6AulcKoFEIJVEIhG4QSKVRNqs6OhINCRmJDoCaS+Q9gId6Vx6WCKRSKUjkbhDIpF4hURHoCsNHYmOO3R0JAINq/dLJe0FUkklkPYSqQQ6EokJXWnoNokFHQ2ppE0i0JFIJFLpaEgkGlJJJBKJVBKJVFIJLEjc2DQckQgkutJsbtHtdefSuQWhJMImlFBSaeg2B5yUQKLbNEw4IZDoSigzFizKW6RzicQ9Gk4IJZDo6EoqBwRu0DChYUJTOhpCSSWUriw4YEXgiBkdTZkRmOyF0nyY5kM0fy48Le2lki4rXVY4F4b/L30dgfDrIWzSr6fw4dKXNBMuJ11eKoFEIm0CoQQSgUB6WvgyAunTpL30sgVSCaQSSnpaU7pPtyj3yklZlbTX0G06ur1EYlVSWZTFwxpWzFjRlBmBN2j4AQIHNEwINDSbjsAJDfdouEfDHZrSlEmZlRvloEzKLRILDujOhb0FgRUHHJUVNzjigAUHhNKV5lc1wyMSYRNK2AsfJg1fQyC8XxheorQXCIRP0eyE4VeljxMI7xd+vSXS1xX2wpfXMeEOB7xGw61yUCZlsmloSnculUkJ5Ua5QWCx15VEoqOjo9skEiecsGLFihUd3WbChBvc4ICDp3VlUU7KCSveITAj0JRmk/YaGmblFSbcIHCLhglh09D8Rc3wBYXLSXvpXNqkvfDyhZcrvFzhpZgN/08ikUp4XNoLny58Gamkh6UPE16OdBmrsiodq7Iqq3JSunL04VLpyorEjMWmYbVJZUHghIYjAgc0TAhMaGhoNt3DVqyYsOAWK2abphyUg3KnNJuOxaZjxgmzTSiJsFkxYUVD2mt+VXP1wl7YpE+T3i98mlACYS8NHyMMv25SSaT3aQQS3V5DIpBIJZEIhMelTSLREehoCHthLzGhI5REINEQSJtQ0l4gEAglkQgEwl4glbTXEUiEkkiE0hHoCKQSWDEhbdJeINARSAQSDR2hpE3YCyQCgUQogUAglEAgkPYSiUR3Lp0LJRA2gXAulYb0tEAilFA6Eg0dTekIpBIIhE+3KiflqHQllFACgRs03OIGB0zKhAlpb1FWpaE5FwgEAg0NgcCMWUklbAJpE2hoaJgwYcKMCZMSSldSWZQViaO9CQ03uMEN7jDjFgdM9u7RcVJSaWhoaGho/qJGItAQCCURSiCUQCiJREcibcKmYcKEhkTHgkAglEAgEFjQlYaGUDrSJpRAIJBIdCQSiUDzuECgIdAQCCWQHhcIZUJDQ9isCAQCgbDXkUog0Gw6EmkvEEibRNrr6Eh0JBIdHc0m0NAQaM6FvY6Ojo6ORCKRSCQSqSRWBAKBsAmE0jBjQSqz0m0CE0IJJBLp+xNKKKGE0tAQCAQCgUDYNITHBQLhwyQS6XGBQNhLJBKJroQSSle6sippE2g+XChhLxA2iUQivc/MilS60pEIpJJIJAIdK5q9VFJJdCQ6AonAhFVJdHQkOhIzupJIJZA2iUQikUg0JELp6Eh0JDoSiUQikUgsSqIjEejoSCQSiUQikUglkUogkEgkEmlvRiCVjlQSoTSk0pRUEg2JQEPYJBKBjo5QEoluk0gkQgmEEmhIBBKBriS6kmhKKomOjkQikUgkEl3pWHCHE97hDboSSCWQSB+mKR2BVAITVgQSMxYccMSEBaGEkugIrFidOyGRSI+7t7cqK9K5CQ2hNAQa/m97cJQbR3qdAfTcv6pJyZMxMkBevEzvKs95y268AAexBcQWya7/C4SLoNDpFkVqNJrRqM4ZWrBhYKIwtScMPKHwFqUt2km701ZtsQuCBzzhhCf8gA2rS0HhPe6w4YSBwqqdUFhRdps2UZg+WCmtUChtYuCMYGKitIlohdgFQbBoGwqFgUJhINrQYje1IAgKCwpBUJjaRGlPmBgolGuFoBAUCkFhaBuCiUJ82tCCaEEhGAgmCoXYBcFEMO0GNgRTCyYmgsJEMLFhIgiGXeyC6VrsChNBtGhB7IIgmAiCqQUT0TZMDB83sGHBhonCv+DJrhBEmygMTM+bdrELppcpbWDDig0rzm4b2FBaIa4V4tMKhUKhUCgUCoXCwEAhWjARTEyUl4kWu0JhwcSCicJwrVyKNlyaWLBhuC3+z0rhrAWlbSgMFDYUhlba1AaCaANBYUEhKEwEE1MLYleItiDaGRPBWQuCiSAIggVlF5RWdoWgEK200iYK0YKBYKJQGJh2hYmhbVpcCoJgIhgoFBYtmFqhUC6VXWEgGChs2tSCaLE7I1oQxG5gYmBiIHZnlwpltyAIosVuQ7QgCILgDhve4h8oFB5wwoZyqTAQ3GlxqbQFsZsobcWCOwR32HCHM+5QWLHhHhP3CE74BxZMFAY23GPDCU/aEwqPuMMTzti0J6w4Y8HEtCvcoXBC4Ul7xMAjBh5QmCg8YWBiYKLwgOCEJyx4xB2ecMJ7vMUD7vCAgQf8KyYKhQ1nLHYLzlixYMWq/RN3mDjZLS5N7axNH6z8B35CUChtYsGGgQ3BgiBYMRDXgiAIgmDBGXdaadGCIFq0iWDTVky7IFoQbcPERLQTFkwUgiCYCKY2ERTOeIsNA8GGIAgmgiAIguAeK0qb2LRgQRBE2zAwcI+J4KwNTEQLJoJgorAiWmHTNsQuCILgjBUrgmkXbAgmogVB8FYLgmixixYE0QqrXeyCYOIR91rhET/g3/FOCwoTQfAOjz7fg/bg2/F37W/af6HwEwp/R+G/tXco/BWFv2o/ovBWu8OGO5zxFhtWPOIHnPEGZ6x41DacsOENNqwoTAw8YsF7PKKw4m844YwVTzjhESs27Uk7a/HByp9RCApBIShtYGoDE4W3WLSgXCstmFhQ+CPucHItdoWBiULhDn/EgmjRSotdtMI9fsQ9gkWLFi1aUCgE/4bCQGGxC2IXLJgYWLBiQSE4+bhCENzjJwwUJgaCIHaxe4N7/IBFG9gwPC844Q94o00UCsGiRYtd8CefFm0gCAZO+BGxKy0IhhaUdsaC/8Q7TAQD0YJ3KMTHDUQrTJcWFM5uK6206dIb7UGLVlphunavPbi0aAOFR7eV9hftL9q9Fu1Ri1baop1RKMQuLpU2UFgRLYhLZ7vYlTbsCpsWl0obLpUPVoYW16JNRAuiDfxBC8q1iUWbKO0t3mB6XrBgorSBgWEXlEvB0IJgYMGCDZtWLhUKGwpBsGJohU0ru9IKwcTE1CYGgqHFtaAQTARTO2PVohXKrvA/CN5o0TYsiGuFaA9YcNKCQmFiatHKpSctbhvYUBiYmFjsyrVCaUEhKKzaRBCXFmzYfFpcKi1eJl4mbptuK8THlY8rHxdtel7sgnheIVoQLYhWWlAobC6Va/F68cHKtIsWtxWmFqx25baBICi7RSsfFwzErhAsLpVrhWiFwhMWbFr5uKBQWtkFQbkWlwaCBcHQCvFpA0FphQWFoOxiF6woDK0wsWjlttIGTohdtEJcikvT8yaCwqYVgkI8r1wLCgOxm9rm5WIX1zYvE7e9d1s8773bNm3zvHcuRXvwvGiblyktCDZt83mm14m2uWX4xcVt8WnlEMTvWzl8TUF8XfFbtPrZ4usov47y2xGUb0t5nfI6cfhc8cuL37Lhq4oWX1d8++LXVa7Fz1daoewK5XD4JQ2vVr4/QXxZ5dsVX1b5cuJweKnhmxXEy8WXUyjfh0I5HH6Phq8idvFtK4cP4rclDofXGr6a+Hzl96Mc/r84HH4Nw4tEC0orL1corbxe/DyllTa9XFAoLXbxekEQL1coLT5P2Q2vE5RdIb6MoLSgtOl1yrU4HJ4zHA6Hw3dqOHwH4nA4XBsOv3HlcDj8MobDdySIw+HQhsM3LF4uDofDpeHwOxeHw+G24fANKIfD4csbvoogdoVCeZnSChML4vWCQiEorVAoFAqlFaIVgum2QrlUWqG0QiEoFMpthYnSFsTLDQQDQSGIlynPK5RrhbIrFAqFQmmFiUJQXi920YbD4Tmrb0Lsyq5QWlwqu7hULsXHFYJyW1yLXRAtiFZatLhtIgiCeJ0gWhAE0YafZ7otLgWxi7ZohdLK4fA1rH62eF65Fi0+LSjXCmVXPq7sSisUgnJbUHallVaIVnbltoGhDZRdPK8wMBCtfFowMDAwEASFzfMKQTDtgsLiecOlaNEmCtGCwobN5ymHw0usfnFB2UWL14tWKJQWL1Na2S2eFxSilVYIhl25LSgsWLWBIHZxW7BgwYpohfi0FSuCoQWFk+cNvMUbbHbBwGYXu9IWl8qlgUIwEO2MN14mKNfK4fCc/wWQn0TduldZ/gAAAABJRU5ErkJggg==\";\n\nTestRegister.addTests([\n    {\n        name: \"Split Colour Channels: Default (JPEG)\",\n        input: testCard,\n        expectedOutput: testCardSplit,\n        recipeConfig: [\n            {\n                \"op\": \"From Base64\",\n                \"args\": [\"A-Za-z0-9+/=\", true]\n            },\n            {\n                \"op\": \"Split Colour Channels\",\n                \"args\": []\n            },\n            {\n                \"op\": \"To Base64\",\n                \"args\": [\"A-Za-z0-9+/=\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/StrUtils.mjs",
    "content": "/**\n * StrUtils tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Diff, basic usage\",\n        input: \"testing23\\n\\ntesting123\",\n        expectedOutput: \"testing<ins>1</ins>23\",\n        recipeConfig: [\n            {\n                \"op\": \"Diff\",\n                \"args\": [\"\\\\n\\\\n\", \"Character\", true, true, false, false]\n            }\n        ],\n    },\n    {\n        name: \"Diff added with subtraction, basic usage\",\n        input: \"testing23\\n\\ntesting123\",\n        expectedOutput: \"<ins>1</ins>\",\n        recipeConfig: [\n            {\n                \"op\": \"Diff\",\n                \"args\": [\"\\\\n\\\\n\", \"Character\", true, true, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Diff removed with subtraction, basic usage\",\n        input: \"testing123\\n\\ntesting3\",\n        expectedOutput: \"<del>12</del>\",\n        recipeConfig: [\n            {\n                \"op\": \"Diff\",\n                \"args\": [\"\\\\n\\\\n\", \"Character\", true, true, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Head 0\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", 0]\n            }\n        ],\n    },\n    {\n        name: \"Head 1\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", 1]\n            }\n        ],\n    },\n    {\n        name: \"Head 2\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1, 2].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", 2]\n            }\n        ],\n    },\n    {\n        name: \"Head 6\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", 6]\n            }\n        ],\n    },\n    {\n        name: \"Head big\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", 100]\n            }\n        ],\n    },\n    {\n        name: \"Head all but 1\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1, 2, 3, 4, 5].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", -1]\n            }\n        ],\n    },\n    {\n        name: \"Head all but 2\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1, 2, 3, 4].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", -2]\n            }\n        ],\n    },\n    {\n        name: \"Head all but 6\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", -6]\n            }\n        ],\n    },\n    {\n        name: \"Head all but big\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Head\",\n                \"args\": [\"Line feed\", -100]\n            }\n        ],\n    },\n    {\n        name: \"Tail 0\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", 0]\n            }\n        ],\n    },\n    {\n        name: \"Tail 1\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", 1]\n            }\n        ],\n    },\n    {\n        name: \"Tail 2\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [5, 6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", 2]\n            }\n        ],\n    },\n    {\n        name: \"Tail 6\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", 6]\n            }\n        ],\n    },\n    {\n        name: \"Tail big\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", 100]\n            }\n        ],\n    },\n    {\n        name: \"Tail all but 1\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [2, 3, 4, 5, 6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", -1]\n            }\n        ],\n    },\n    {\n        name: \"Tail all but 2\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [3, 4, 5, 6].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", -2]\n            }\n        ],\n    },\n    {\n        name: \"Tail all but 6\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", -6]\n            }\n        ],\n    },\n    {\n        name: \"Tail all but big\",\n        input: [1, 2, 3, 4, 5, 6].join(\"\\n\"),\n        expectedOutput: [].join(\"\\n\"),\n        recipeConfig: [\n            {\n                \"op\": \"Tail\",\n                \"args\": [\"Line feed\", -100]\n            }\n        ],\n    },\n    {\n        name: \"Escape String: single quotes\",\n        input: \"Escape 'these' quotes.\",\n        expectedOutput: \"Escape \\\\'these\\\\' quotes.\",\n        recipeConfig: [\n            {\n                \"op\": \"Escape string\",\n                \"args\": [\"Special chars\", \"Single\", false, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Escape String: double quotes\",\n        input: \"Hello \\\"World\\\"!\",\n        expectedOutput: \"Hello \\\\\\\"World\\\\\\\"!\",\n        recipeConfig: [\n            {\n                \"op\": \"Escape string\",\n                \"args\": [\"Special chars\", \"Double\", false, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Escape String: special characters\",\n        input: \"Fizz & buzz\\n\\ttabbed newline\\rcarriage returned line\\nbackspace character: \\\"\b\\\" form feed character: \\\"\f\\\"\",\n        expectedOutput: \"Fizz & buzz\\\\n\\\\ttabbed newline\\\\rcarriage returned line\\\\nbackspace character: \\\\\\\"\\\\b\\\\\\\" form feed character: \\\\\\\"\\\\f\\\\\\\"\",\n        recipeConfig: [\n            {\n                \"op\": \"Escape string\",\n                \"args\": [\"Special chars\", \"Double\", false, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Unescape String: quotes\",\n        input: \"Hello \\\\\\\"World\\\\\\\"! Escape \\\\'these\\\\' quotes.\",\n        expectedOutput: \"Hello \\\"World\\\"! Escape 'these' quotes.\",\n        recipeConfig: [\n            {\n                \"op\": \"Unescape string\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Unescape String: special characters\",\n        input: \"Fizz \\x26 buzz\\\\n\\\\ttabbed newline\\\\rcarriage returned line\\\\nbackspace character: \\\\\\\"\\\\b\\\\\\\" form feed character: \\\\\\\"\\\\f\\\\\\\"\",\n        expectedOutput: \"Fizz & buzz\\n\\ttabbed newline\\rcarriage returned line\\nbackspace character: \\\"\b\\\" form feed character: \\\"\f\\\"\",\n        recipeConfig: [\n            {\n                \"op\": \"Unescape string\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Escape String: complex\",\n        input: \"null\\0backspace\\btab\\tnewline\\nverticaltab\\vformfeed\\fcarriagereturn\\rdoublequote\\\"singlequote'hex\\xa9unicode\\u2665codepoint\\u{1D306}\",\n        expectedOutput: \"null\\\\0backspace\\\\btab\\\\tnewline\\\\nverticaltab\\\\x0bformfeed\\\\fcarriagereturn\\\\rdoublequote\\\"singlequote\\\\'hex\\\\xa9unicode\\\\u2665codepoint\\\\u{1d306}\",\n        recipeConfig: [\n            {\n                \"op\": \"Escape string\",\n                \"args\": [\"Special chars\", \"Single\", false, true, false]\n            }\n        ],\n    },\n    {\n        name: \"Unescape String: complex\",\n        input: \"null\\\\0backspace\\\\btab\\\\tnewline\\\\nverticaltab\\\\vformfeed\\\\fcarriagereturn\\\\rdoublequote\\\\\\\"singlequote\\\\'hex\\\\xa9unicode\\\\u2665codepoint\\\\u{1D306}\",\n        expectedOutput: \"null\\0backspace\\btab\\tnewline\\nverticaltab\\vformfeed\\fcarriagereturn\\rdoublequote\\\"singlequote'hex\\xa9unicode\\u2665codepoint\\u{1D306}\",\n        recipeConfig: [\n            {\n                \"op\": \"Unescape string\",\n                \"args\": []\n            }\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/StripIPv4Header.mjs",
    "content": "/**\n * Strip IPv4 header tests.\n *\n * @author c65722 []\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Strip IPv4 header: No options, No payload\",\n        input: \"450000140005400080060000c0a80001c0a80002\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip IPv4 header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip IPv4 header: No options, Payload\",\n        input: \"450000140005400080060000c0a80001c0a80002ffffffffffffffff\",\n        expectedOutput: \"ffffffffffffffff\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip IPv4 header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip IPv4 header: Options, No payload\",\n        input: \"460000140005400080060000c0a80001c0a8000207000000\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip IPv4 header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip IPv4 header: Options, Payload\",\n        input: \"460000140005400080060000c0a80001c0a8000207000000ffffffffffffffff\",\n        expectedOutput: \"ffffffffffffffff\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip IPv4 header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip IPv4 header: Input length lesss than minimum header length\",\n        input: \"450000140005400080060000c0a80001c0a800\",\n        expectedOutput: \"Input length is less than minimum IPv4 header length\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip IPv4 header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip IPv4 header: Input length less than IHL\",\n        input: \"460000140005400080060000c0a80001c0a80000\",\n        expectedOutput: \"Input length is less than IHL\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip IPv4 header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/StripTCPHeader.mjs",
    "content": "/**\n * Strip TCP header tests.\n *\n * @author c65722 []\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Strip TCP header: No options, No payload\",\n        input: \"7f900050000fa4b2000cb2a45010bff100000000\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip TCP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip TCP header: No options, Payload\",\n        input: \"7f900050000fa4b2000cb2a45010bff100000000ffffffffffffffff\",\n        expectedOutput: \"ffffffffffffffff\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip TCP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip TCP header: Options, No payload\",\n        input: \"7f900050000fa4b2000cb2a47010bff100000000020405b404020000\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip TCP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip TCP header: Options, Payload\",\n        input: \"7f900050000fa4b2000cb2a47010bff100000000020405b404020000ffffffffffffffff\",\n        expectedOutput: \"ffffffffffffffff\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip TCP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip TCP header: Input length less than minimum header length\",\n        input: \"7f900050000fa4b2000cb2a45010bff1000000\",\n        expectedOutput: \"Need at least 20 bytes for a TCP Header\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip TCP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip TCP header: Input length less than data offset\",\n        input: \"7f900050000fa4b2000cb2a47010bff100000000\",\n        expectedOutput: \"Input length is less than data offset\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip TCP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/StripUDPHeader.mjs",
    "content": "/**\n * Strip UDP header tests.\n *\n * @author c65722 []\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Strip UDP header: No payload\",\n        input: \"8111003500000000\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip UDP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip UDP header: Payload\",\n        input: \"8111003500080000ffffffffffffffff\",\n        expectedOutput: \"ffffffffffffffff\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip UDP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    },\n    {\n        name: \"Strip UDP header: Input length less than header length\",\n        input: \"81110035000000\",\n        expectedOutput: \"Need 8 bytes for a UDP Header\",\n        recipeConfig: [\n            {\n                op: \"From Hex\",\n                args: [\"None\"]\n            },\n            {\n                op: \"Strip UDP header\",\n                args: [],\n            },\n            {\n                op: \"To Hex\",\n                args: [\"None\", 0]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Subsection.mjs",
    "content": "/**\n * Subsection Tests.\n *\n * @author n1073645 [n1073645@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Subsection: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Subsection\",\n                \"args\": [\"\", true, true, false],\n            },\n        ],\n    },\n    {\n        name: \"Subsection, Full Merge: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Subsection\",\n                \"args\": [\"\", true, true, false],\n            },\n            {\n                \"op\": \"Merge\",\n                \"args\": [true],\n            },\n        ],\n    },\n    {\n        name: \"Subsection, Partial Merge: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                \"op\": \"Subsection\",\n                \"args\": [\"\", true, true, false],\n            },\n            {\n                \"op\": \"Merge\",\n                \"args\": [false],\n            },\n        ],\n    },\n    {\n        name: \"Subsection, Full Merge: Base64 with Hex\",\n        input: \"SGVsbG38675629ybGQ=\",\n        expectedOutput: \"Hello World\",\n        recipeConfig: [\n            {\n                \"op\": \"Subsection\",\n                \"args\": [\"386756\", true, true, false],\n            },\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"],\n            },\n            {\n                \"op\": \"Merge\",\n                \"args\": [true],\n            },\n            {\n                \"op\": \"From Base64\",\n                \"args\": [\"A-Za-z0-9+/=\", true, false],\n            },\n        ],\n    },\n    {\n        name: \"Subsection, Partial Merge: Base64 with Hex surrounded by binary data.\",\n        input: \"000000000SGVsbG38675629ybGQ=0000000000\",\n        expectedOutput: \"000000000Hello World0000000000\",\n        recipeConfig: [\n            {\n                \"op\": \"Subsection\",\n                \"args\": [\"SGVsbG38675629ybGQ=\", true, true, false],\n            },\n            {\n                \"op\": \"Subsection\",\n                \"args\": [\"386756\", true, true, false],\n            },\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"],\n            },\n            {\n                \"op\": \"Merge\",\n                \"args\": [false],\n            },\n            {\n                \"op\": \"From Base64\",\n                \"args\": [\"A-Za-z0-9+/=\", true, false],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SwapCase.mjs",
    "content": "/**\n * @author mikecat\n * @copyright Crown Copyright 2023\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        \"name\": \"Swap Case: basic example\",\n        \"input\": \"Hello, World!\",\n        \"expectedOutput\": \"hELLO, wORLD!\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Swap case\",\n                \"args\": [\n                ],\n            },\n        ],\n    },\n    {\n        \"name\": \"Swap Case: empty input\",\n        \"input\": \"\",\n        \"expectedOutput\": \"\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Swap case\",\n                \"args\": [\n                ],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/SymmetricDifference.mjs",
    "content": "/**\n * Symmetric difference tests.\n *\n * @author d98762625\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Symmetric Difference\",\n        input: \"1 2 3 4 5\\n\\n3 4 5 6 7\",\n        expectedOutput: \"1 2 6 7\",\n        recipeConfig: [\n            {\n                op: \"Symmetric Difference\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Symmetric Difference: wrong sample count\",\n        input: \"1 2\\n\\n3 4 5\\n\\n3 4 5 6 7\",\n        expectedOutput: \"Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?\",\n        recipeConfig: [\n            {\n                op: \"Symmetric Difference\",\n                args: [\"\\n\\n\", \" \"],\n            },\n        ],\n    },\n    {\n        name: \"Symmetric Difference: item delimiter\",\n        input: \"a_b_c_d_e\\n\\nc_d_e_f_g\",\n        expectedOutput: \"a_b_f_g\",\n        recipeConfig: [\n            {\n                op: \"Symmetric Difference\",\n                args: [\"\\n\\n\", \"_\"],\n            },\n        ],\n    },\n    {\n        name: \"Symmetric Difference: sample delimiter\",\n        input: \"a_b_c_d_eAAAAAc_d_e_f_g\",\n        expectedOutput: \"a_b_f_g\",\n        recipeConfig: [\n            {\n                op: \"Symmetric Difference\",\n                args: [\"AAAAA\", \"_\"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/TakeNthBytes.mjs",
    "content": "/**\n * @author Oshawk [oshawk@protonmail.com]\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\n/**\n * Take nth bytes tests\n */\nTestRegister.addTests([\n    {\n        name: \"Take nth bytes: Nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 0, false],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Nothing (apply to each line)\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 0, true],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Basic single line\",\n        input: \"0123456789\",\n        expectedOutput: \"048\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 0, false],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Basic single line (apply to each line)\",\n        input: \"0123456789\",\n        expectedOutput: \"048\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 0, true],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Complex single line\",\n        input: \"0123456789\",\n        expectedOutput: \"59\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 5, false],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Complex single line (apply to each line)\",\n        input: \"0123456789\",\n        expectedOutput: \"59\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 5, true],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Basic multi line\",\n        input: \"01234\\n56789\",\n        expectedOutput: \"047\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 0, false],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Basic multi line (apply to each line)\",\n        input: \"01234\\n56789\",\n        expectedOutput: \"04\\n59\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 0, true],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Complex multi line\",\n        input: \"01234\\n56789\",\n        expectedOutput: \"\\n8\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 5, false],\n            },\n        ],\n    },\n    {\n        name: \"Take nth bytes: Complex multi line (apply to each line)\",\n        input: \"012345\\n6789ab\",\n        expectedOutput: \"5\\nb\",\n        recipeConfig: [\n            {\n                op: \"Take nth bytes\",\n                args: [4, 5, true],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/Template.mjs",
    "content": "/**\n * @author kendallgoto [k@kgo.to]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\nTestRegister.addTests([\n    {\n        \"name\": \"Template: Simple Print\",\n        \"input\": \"{}\",\n        \"expectedOutput\": \"Hello, world!\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Template\",\n                \"args\": [\"Hello, world!\"]\n            }\n        ]\n    },\n    {\n        \"name\": \"Template: Print Basic Variables\",\n        \"input\": \"{\\\"one\\\": 1, \\\"two\\\": 2}\",\n        \"expectedOutput\": \"1 2\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Template\",\n                \"args\": [\"{{ one }} {{ two }}\"]\n            }\n        ]\n    },\n    {\n        \"name\": \"Template: Partials\",\n        \"input\": \"{\\\"users\\\":[{\\\"name\\\":\\\"Someone\\\",\\\"age\\\":25},{\\\"name\\\":\\\"Someone Else\\\",\\\"age\\\":32}]}\",\n        \"expectedOutput\": \"Name: Someone\\nAge: 25\\n\\nName: Someone Else\\nAge: 32\\n\\n\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Template\",\n                \"args\": [\"{{#*inline \\\"user\\\"}}\\nName: {{ name }}\\nAge: {{ age }}\\n{{/inline}}\\n{{#each users}}\\n{{> user}}\\n\\n{{/each}}\"]\n            }\n        ]\n    },\n    {\n        \"name\": \"Template: Disallow XSS\",\n        \"input\": \"{\\\"test\\\": \\\"<script></script>\\\"}\",\n        \"expectedOutput\": \"<script></script>&lt;script&gt;&lt;/script&gt;\",\n        \"recipeConfig\": [\n            {\n                \"op\": \"Template\",\n                \"args\": [\"<script></script>{{ test }}\"]\n            }\n        ]\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/TextEncodingBruteForce.mjs",
    "content": "/**\n * Text Encoding Brute Force tests.\n *\n * @author Cynser\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Text Encoding Brute Force - Encode\",\n        input: \"Р‘СѓР»РєС– РїСЂР°Р· Р»СЏРЅС–РІР° СЃР°Р±Р°РєСѓ.\",\n        expectedMatch: /Windows-1251 Cyrillic \\(1251\\).{1,10}Булкі праз ляніва сабаку\\./,\n        recipeConfig: [\n            {\n                op: \"Text Encoding Brute Force\",\n                args: [\"Encode\"],\n            },\n        ],\n    },\n    {\n        name: \"Text Encoding Brute Force - Decode\",\n        input: \"Áóëê³ ïðàç ëÿí³âà ñàáàêó.\",\n        expectedMatch: /Windows-1251 Cyrillic \\(1251\\).{1,10}Булкі праз ляніва сабаку\\./,\n        recipeConfig: [\n            {\n                op: \"Text Encoding Brute Force\",\n                args: [\"Decode\"],\n            },\n        ],\n    }\n]);\n\n"
  },
  {
    "path": "tests/operations/tests/TextIntegerConverter.mjs",
    "content": "/**\n * Text-Integer Conversion tests.\n *\n * @author p-leriche [philip.leriche@cantab.net]\n *\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Text-Integer Conversion quoted string to decimal\",\n        input: \"\\\"ABC\\\"\",\n        expectedOutput: \"4276803\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion quoted string to hexadecimal\",\n        input: \"\\\"ABC\\\"\",\n        expectedOutput: \"0x414243\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Hexadecimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion single quoted string to decimal\",\n        input: \"'Hello'\",\n        expectedOutput: \"310939249775\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion decimal to string\",\n        input: \"4276803\",\n        expectedOutput: \"ABC\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"String\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion hexadecimal to string\",\n        input: \"0x48656C6C6F\",\n        expectedOutput: \"Hello\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"String\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion round-trip string.decimal.string\",\n        input: \"\\\"Test\\\"\",\n        expectedOutput: \"Test\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"String\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion round-trip string.hex.string\",\n        input: \"\\\"CyberChef\\\"\",\n        expectedOutput: \"CyberChef\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Hexadecimal\"],\n            },\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"String\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion implicit round trip string-string Latin-1\",\n        input: \"U+00FF\",\n        expectedOutput: \"U+00FF\",  // U+00FF (Latin small letter y with diaeresis)\n        recipeConfig: [\n            {\n                op: \"Unescape Unicode Characters\",\n                args: [\"U+\"],\n            },\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"String\"],\n            },\n            {\n                op: \"Escape Unicode Characters\",\n                args: [\"U+\", false, 4, true],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion unquoted text to decimal\",\n        input: \"Hi\",\n        expectedOutput: \"18537\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion single character\",\n        input: \"\\\"A\\\"\",\n        expectedOutput: \"65\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion hex to decimal conversion\",\n        input: \"0xFF\",\n        expectedOutput: \"255\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion decimal to hex conversion\",\n        input: \"255\",\n        expectedOutput: \"0xff\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Hexadecimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion large number to string\",\n        input: \"113091951015816448506195587157728348242683688608116\",\n        expectedOutput: \"Mary had a little cat\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"String\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion whitespace handling (quoted)\",\n        input: \"\\\"  test  \\\"\",\n        expectedOutput: \"2314978187545944096\",\n        recipeConfig: [\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n        ],\n    },\n    {\n        name: \"Text-Integer Conversion non-Latin1 character in input\",\n        input: \"61 ce 93 61\",\n        expectedOutput:\n`Character at position 1 exceeds Latin-1 range (0-255).\nOnly ASCII and Latin-1 characters are supported.`,\n        recipeConfig: [\n            {\n                \"op\": \"From Hex\",\n                \"args\": [\"Auto\"]\n            },\n            {\n                op: \"Text-Integer Conversion\",\n                args: [\"Decimal\"],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/ToFromInsensitiveRegex.mjs",
    "content": "/**\n * To/From Case Insensitive Regex tests.\n *\n * @author masq [github.cyberchef@masq.cc]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"To Case Insensitive Regex: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"From Case Insensitive Regex: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"From Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: simple test\",\n        input: \"S0meth!ng\",\n        expectedOutput: \"[sS]0[mM][eE][tT][hH]![nN][gG]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"From Case Insensitive Regex: simple test\",\n        input: \"[sS]0[mM][eE][tT][hH]![nN][Gg] [wr][On][g]?\",\n        expectedOutput: \"s0meth!nG [wr][On][g]?\",\n        recipeConfig: [\n            {\n                op: \"From Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [A-Z] -> [A-Za-z]\",\n        input: \"[A-Z]\",\n        expectedOutput: \"[A-Za-z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [a-z] -> [A-Za-z]\",\n        input: \"[a-z]\",\n        expectedOutput: \"[A-Za-z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [H-d] -> [A-DH-dh-z]\",\n        input: \"[H-d]\",\n        expectedOutput: \"[A-DH-dh-z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [!-D] -> [!-Da-d]\",\n        input: \"[!-D]\",\n        expectedOutput: \"[!-Da-d]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [%-^] -> [%-^a-z]\",\n        input: \"[%-^]\",\n        expectedOutput: \"[%-^a-z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [K-`] -> [K-`k-z]\",\n        input: \"[K-`]\",\n        expectedOutput: \"[K-`k-z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [[-}] -> [[-}A-Z]\",\n        input: \"[[-}]\",\n        expectedOutput: \"[[-}A-Z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [b-}] -> [b-}B-Z]\",\n        input: \"[b-}]\",\n        expectedOutput: \"[b-}B-Z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [<-j] -> [<-z]\",\n        input: \"[<-j]\",\n        expectedOutput: \"[<-z]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: [^-j] -> [A-J^-j]\",\n        input: \"[^-j]\",\n        expectedOutput: \"[A-J^-j]\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: not simple test\",\n        input: \"Mozilla[A-Z0-9]+[A-Z]Mozilla[0-9whatA-Z][H-d][!-H][a-~](.)+\",\n        expectedOutput: \"[mM][oO][zZ][iI][lL][lL][aA][A-Za-z0-9]+[A-Za-z][mM][oO][zZ][iI][lL][lL][aA][0-9[wW][hH][aA][tT]A-Za-z][A-DH-dh-z][!-Ha-h][a-~A-Z](.)+\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"To Case Insensitive Regex: erroneous test\",\n        input: \"Mozilla[A-Z\",\n        expectedOutput: \"Invalid Regular Expression (Please note this version of node does not support look behinds).\",\n        recipeConfig: [\n            {\n                op: \"To Case Insensitive Regex\",\n                args: [],\n            },\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/TranslateDateTimeFormat.mjs",
    "content": "/**\n * Translate DateTime Format tests.\n *\n * @author Cynser\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Translate DateTime Format\",\n        input: \"01/04/1999 22:33:01\",\n        expectedOutput: \"Thursday 1st April 1999 22:33:01 +00:00 UTC\",\n        recipeConfig: [\n            {\n                op: \"Translate DateTime Format\",\n                args: [\"Standard date and time\", \"DD/MM/YYYY HH:mm:ss\", \"UTC\", \"dddd Do MMMM YYYY HH:mm:ss Z z\", \"UTC\"],\n            },\n        ],\n    },\n    {\n        name: \"Translate DateTime Format: invalid input\",\n        input: \"1234567890\",\n        expectedMatch: /Invalid format./,\n        recipeConfig: [\n            {\n                op: \"Translate DateTime Format\",\n                args: [\"Standard date and time\", \"DD/MM/YYYY HH:mm:ss\", \"UTC\", \"dddd Do MMMM YYYY HH:mm:ss Z z\", \"UTC\"],\n            },\n        ],\n    },\n    {\n        name: \"Translate DateTime Format: timezone conversion\",\n        input: \"01/04/1999 22:33:01\",\n        expectedOutput: \"Thursday 1st April 1999 17:33:01 -05:00 EST\",\n        recipeConfig: [\n            {\n                op: \"Translate DateTime Format\",\n                args: [\"Standard date and time\", \"DD/MM/YYYY HH:mm:ss\", \"UTC\", \"dddd Do MMMM YYYY HH:mm:ss Z z\", \"US/Eastern\"],\n            },\n        ],\n    },\n    {\n        name: \"Translate DateTime Format: automatic input format\",\n        input: \"1999-04-01 22:33:01\",\n        expectedOutput: \"Thursday 1st April 1999 22:33:01 +00:00 UTC\",\n        recipeConfig: [\n            {\n                op: \"Translate DateTime Format\",\n                args: [\"Automatic\", \"\", \"UTC\", \"dddd Do MMMM YYYY HH:mm:ss Z z\", \"UTC\"],\n            },\n        ],\n    }\n]);\n\n"
  },
  {
    "path": "tests/operations/tests/Typex.mjs",
    "content": "/**\n * Typex machine tests.\n * @author s2224834\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        // Unlike Enigma we're not verifying against a real machine here, so this is just a test\n        // to catch inadvertent breakage.\n        name: \"Typex: basic\",\n        input: \"hello world, this is a test message.\",\n        expectedOutput: \"VIXQQ VHLPN UCVLA QDZNZ EAYAT HWC\",\n        recipeConfig: [\n            {\n                \"op\": \"Typex\",\n                \"args\": [\n                    \"MCYLPQUVRXGSAOWNBJEZDTFKHI<BFHNQUW\",\n                    false, \"B\", \"C\",\n                    \"KHWENRCBISXJQGOFMAPVYZDLTU<BFHNQUW\",\n                    false, \"D\", \"E\",\n                    \"BYPDZMGIKQCUSATREHOJNLFWXV<BFHNQUW\",\n                    false, \"F\", \"G\",\n                    \"ZANJCGDLVHIXOBRPMSWQUKFYET<BFHNQUW\",\n                    true, \"H\", \"I\",\n                    \"QXBGUTOVFCZPJIHSWERYNDAMLK<BFHNQUW\",\n                    true, \"J\", \"K\",\n                    \"AN BC FG IE KD LU MH OR TS VZ WQ XJ YP\",\n                    \"EHZTLCVKFRPQSYANBUIWOJXGMD\",\n                    \"None\", true\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Typex: keyboard\",\n        input: \"hello world, this is a test message.\",\n        expectedOutput: \"VIXQQ FDJXT WKLDQ DFQOD CNCSK NULBG JKQDD MVGQ\",\n        recipeConfig: [\n            {\n                \"op\": \"Typex\",\n                \"args\": [\n                    \"MCYLPQUVRXGSAOWNBJEZDTFKHI<BFHNQUW\",\n                    false, \"B\", \"C\",\n                    \"KHWENRCBISXJQGOFMAPVYZDLTU<BFHNQUW\",\n                    false, \"D\", \"E\",\n                    \"BYPDZMGIKQCUSATREHOJNLFWXV<BFHNQUW\",\n                    false, \"F\", \"G\",\n                    \"ZANJCGDLVHIXOBRPMSWQUKFYET<BFHNQUW\",\n                    true, \"H\", \"I\",\n                    \"QXBGUTOVFCZPJIHSWERYNDAMLK<BFHNQUW\",\n                    true, \"J\", \"K\",\n                    \"AN BC FG IE KD LU MH OR TS VZ WQ XJ YP\",\n                    \"EHZTLCVKFRPQSYANBUIWOJXGMD\",\n                    \"Encrypt\", true\n                ]\n            }\n        ]\n    },\n    {\n        name: \"Typex: self-decrypt\",\n        input: \"hello world, this is a test message.\",\n        expectedOutput: \"HELLO WORLD, THIS IS A TEST MESSAGE.\",\n        recipeConfig: [\n            {\n                \"op\": \"Typex\",\n                \"args\": [\n                    \"MCYLPQUVRXGSAOWNBJEZDTFKHI<BFHNQUW\",\n                    false, \"B\", \"C\",\n                    \"KHWENRCBISXJQGOFMAPVYZDLTU<BFHNQUW\",\n                    false, \"D\", \"E\",\n                    \"BYPDZMGIKQCUSATREHOJNLFWXV<BFHNQUW\",\n                    false, \"F\", \"G\",\n                    \"ZANJCGDLVHIXOBRPMSWQUKFYET<BFHNQUW\",\n                    true, \"H\", \"I\",\n                    \"QXBGUTOVFCZPJIHSWERYNDAMLK<BFHNQUW\",\n                    true, \"J\", \"K\",\n                    \"AN BC FG IE KD LU MH OR TS VZ WQ XJ YP\",\n                    \"EHZTLCVKFRPQSYANBUIWOJXGMD\",\n                    \"Encrypt\", true\n                ]\n            },\n            {\n                \"op\": \"Typex\",\n                \"args\": [\n                    \"MCYLPQUVRXGSAOWNBJEZDTFKHI<BFHNQUW\",\n                    false, \"B\", \"C\",\n                    \"KHWENRCBISXJQGOFMAPVYZDLTU<BFHNQUW\",\n                    false, \"D\", \"E\",\n                    \"BYPDZMGIKQCUSATREHOJNLFWXV<BFHNQUW\",\n                    false, \"F\", \"G\",\n                    \"ZANJCGDLVHIXOBRPMSWQUKFYET<BFHNQUW\",\n                    true, \"H\", \"I\",\n                    \"QXBGUTOVFCZPJIHSWERYNDAMLK<BFHNQUW\",\n                    true, \"J\", \"K\",\n                    \"AN BC FG IE KD LU MH OR TS VZ WQ XJ YP\",\n                    \"EHZTLCVKFRPQSYANBUIWOJXGMD\",\n                    \"Decrypt\", true\n                ]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/URLEncodeDecode.mjs",
    "content": "/**\n * URLEncode and URLDecode tests.\n *\n * @author es45411 [135977478+es45411@users.noreply.github.com]\n *\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    // URL Decode\n    {\n        name: \"URLDecode: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"URL Decode\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"URLDecode: spaces without special chars\",\n        input: \"Hello%20world%21\",\n        expectedOutput: \"Hello world!\",\n        recipeConfig: [\n            {\n                op: \"URL Decode\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"URLDecode: spaces with special chars\",\n        input: \"Hello%20world!\",\n        expectedOutput: \"Hello world!\",\n        recipeConfig: [\n            {\n                op: \"URL Decode\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"URLDecode: decode plus as space\",\n        input: \"Hello%20world!\",\n        expectedOutput: \"Hello world!\",\n        recipeConfig: [\n            {\n                op: \"URL Decode\",\n                args: [],\n            },\n        ],\n    },\n    // URL Encode\n    {\n        name: \"URLEncode: nothing\",\n        input: \"\",\n        expectedOutput: \"\",\n        recipeConfig: [\n            {\n                op: \"URL Encode\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"URLEncode: spaces without special chars\",\n        input: \"Hello world!\",\n        expectedOutput: \"Hello%20world!\",\n        recipeConfig: [\n            {\n                op: \"URL Encode\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"URLEncode: spaces with special chars\",\n        input: \"Hello world!\",\n        expectedOutput: \"Hello%20world%21\",\n        recipeConfig: [\n            {\n                op: \"URL Encode\",\n                args: [true],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/UnescapeString.mjs",
    "content": "/**\n * UnescapeString tests.\n *\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"UnescapeString: escape sequences\",\n        input: \"\\\\a\\\\b\\\\f\\\\n\\\\r\\\\t\\\\v\\\\'\\\\\\\"\",\n        expectedOutput: String.fromCharCode(0x07, 0x08, 0x0c, 0x0a, 0x0d, 0x09,\n            0x0b, 0x27, 0x22),\n        recipeConfig: [\n            {\n                op: \"Unescape string\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"UnescapeString: octals\",\n        input: \"\\\\0\\\\01\\\\012\\\\1\\\\12\",\n        expectedOutput: String.fromCharCode(0, 1, 10, 1, 10),\n        recipeConfig: [\n            {\n                op: \"Unescape string\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"UnescapeString: hexadecimals\",\n        input: \"\\\\x00\\\\xAA\\\\xaa\",\n        expectedOutput: String.fromCharCode(0, 170, 170),\n        recipeConfig: [\n            {\n                op: \"Unescape string\",\n                args: [],\n            },\n        ],\n    },\n    {\n        name: \"UnescapeString: unicode\",\n        input: \"\\\\u0061\\\\u{0062}\",\n        expectedOutput: \"ab\",\n        recipeConfig: [\n            {\n                op: \"Unescape string\",\n                args: [],\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/Unicode.mjs",
    "content": "/**\n * Unicode operation tests.\n *\n * @author Matt C [me@mitt.dev]\n * @author Klaxon [klaxon@veyr.com]\n *\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"Unicode Text Format: underline\",\n        input: \"a\",\n        expectedOutput: \"a\\u0332\",\n        recipeConfig: [\n            {\n                \"op\": \"Unicode Text Format\",\n                \"args\": [true, false],\n            }\n        ],\n    },\n    {\n        name: \"Unicode Text Format: strikethrough\",\n        input: \"a\",\n        expectedOutput: \"a\\u0336\",\n        recipeConfig: [\n            {\n                \"op\": \"Unicode Text Format\",\n                \"args\": [false, true],\n            }\n        ],\n    },\n    {\n        name: \"Unicode Text Format: both\",\n        input: \"a\",\n        expectedOutput: \"a\\u0336\\u0332\",\n        recipeConfig: [\n            {\n                \"op\": \"Unicode Text Format\",\n                \"args\": [true, true],\n            }\n        ],\n    },\n    {\n        name: \"Remove Diacritics: text formatting\",\n        input: \"a\",\n        expectedOutput: \"a\",\n        recipeConfig: [\n            {\n                \"op\": \"Unicode Text Format\",\n                \"args\": [true, true],\n            },\n            {\n                \"op\": \"Remove Diacritics\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Remove Diacritics: all diacritical marks one char\",\n        input: \"à̴̵̶̷̸̡̢̧̨̛̖̗̘̙̜̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̱̲̳̹̺̻̼́̂̃̄̅̆̇̈̉̊̋̌̍̎̏̐̑̒̓̔̽̾̿̀́͂̓̈́̕̚͠͡ͅ\", // sorry about this line lol\n        expectedOutput: \"a\",\n        recipeConfig: [\n            {\n                \"op\": \"Remove Diacritics\",\n                \"args\": []\n            }\n        ],\n    },\n    {\n        name: \"Remove Diacritics: default\",\n        input: \"\\xe0, \\xe8, \\xec, \\xf2, \\xf9  \\xc0, \\xc8, \\xcc, \\xd2, \\xd9\\n\\xe1, \\xe9, \\xed, \\xf3, \\xfa, \\xfd \\xc1, \\xc9, \\xcd, \\xd3, \\xda, \\xdd\\n\\xe2, \\xea, \\xee, \\xf4, \\xfb \\xc2, \\xca, \\xce, \\xd4, \\xdb\\n\\xe3, \\xf1, \\xf5 \\xc3, \\xd1, \\xd5\\n\\xe4, \\xeb, \\xef, \\xf6, \\xfc, \\xff \\xc4, \\xcb, \\xcf, \\xd6, \\xdc, \\u0178\\n\\xe5, \\xc5\",\n        expectedOutput: \"a, e, i, o, u  A, E, I, O, U\\na, e, i, o, u, y A, E, I, O, U, Y\\na, e, i, o, u A, E, I, O, U\\na, n, o A, N, O\\na, e, i, o, u, y A, E, I, O, U, Y\\na, A\",\n        recipeConfig: [\n            {\n                \"op\": \"Remove Diacritics\",\n                \"args\": []\n            },\n        ],\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/XORChecksum.mjs",
    "content": "/**\n * Checksum tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2018\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst BASIC_STRING = \"The ships hung in the sky in much the same way that bricks don't.\";\nconst UTF8_STR = \"ნუ პანიკას\";\nconst ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n\nTestRegister.addTests([\n    {\n        name: \"XOR Checksum (1): nothing\",\n        input: \"\",\n        expectedOutput: \"00\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [1]\n            }\n        ]\n    },\n    {\n        name: \"XOR Checksum (1): basic string\",\n        input: BASIC_STRING,\n        expectedOutput: \"08\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [1]\n            }\n        ]\n    },\n    {\n        name: \"XOR Checksum (1): UTF-8\",\n        input: UTF8_STR,\n        expectedOutput: \"df\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [1]\n            }\n        ]\n    },\n    {\n        name: \"XOR Checksum (1): all bytes\",\n        input: ALL_BYTES,\n        expectedOutput: \"00\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [1]\n            }\n        ]\n    },\n    {\n        name: \"XOR Checksum (4): nothing\",\n        input: \"\",\n        expectedOutput: \"00000000\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [4]\n            }\n        ]\n    },\n    {\n        name: \"XOR Checksum (4): basic string\",\n        input: BASIC_STRING,\n        expectedOutput: \"4918421b\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [4]\n            }\n        ]\n    },\n    {\n        name: \"XOR Checksum (4): UTF-8\",\n        input: UTF8_STR,\n        expectedOutput: \"83a424dc\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [4]\n            }\n        ]\n    },\n    {\n        name: \"XOR Checksum (4): all bytes\",\n        input: ALL_BYTES,\n        expectedOutput: \"00000000\",\n        recipeConfig: [\n            {\n                \"op\": \"XOR Checksum\",\n                \"args\": [4]\n            }\n        ]\n    },\n]);\n"
  },
  {
    "path": "tests/operations/tests/XSalsa20.mjs",
    "content": "/**\n * XSalsa20 tests.\n *\n * @author joostrijneveld [joost@joostrijneveld.nl]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\n\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"XSalsa20: no key\",\n        input: \"\",\n        expectedOutput: `Invalid key length: 0 bytes.\n\nXSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"XSalsa20\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"XSalsa20: no nonce\",\n        input: \"\",\n        expectedOutput: `Invalid nonce length: 0 bytes.\n\nXSalsa20 uses a nonce of 24 bytes (192 bits).`,\n        recipeConfig: [\n            {\n                \"op\": \"XSalsa20\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00000000000000000000000000000000\"},\n                    {\"option\": \"Hex\", \"string\": \"\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    },\n    {\n        name: \"XSalsa20 custom vector\",\n        input: \"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\",\n        expectedOutput: \"7c b6 60 af dd 9e c6 46 8f 57 dd 6d 24 33 f9 34 28 fd 82 cd 73 86 c5 47 1a 24 d8 ad 2a 52 5b 6e 5e ff 38 4f c7 ca a2 10 bb 3c 8f 3e 68 8f 4a 97 52 a5 46 df 8c 25 3f ef 17 a2 67 94 55 c7 a1 e1 83 db f5 d5 45 b0 f5 02 b9 8d e0 99 7a 66 ab 43 23 41 68 9f f3 97 dc 4f bc 1f 27 bd 1a 61 97 f5 dc 80 ff 19 05 16 c9 ed 14 f2 81 d1 ca 73 88 82 f6 d3 d2 fb 92 1e 2e f8 99 38 9e 0a 22 3b e7 ae 81 5a 04 86 5f 82 52 68 2f 6a d1 4f 98 ff 5f 08 23 cc 22 9d d2 22 9e 69 9d c2 1a 81 7d c9 54 bb 9b c9 0d ec 3b 9b d3 bf 20 9b 82 da f7 89 34 8a 5e 14 ec 54 2b 6d ee 8b 60 1e 7e 6d e3 c2 8a 2d 57 b6 25 e7 ea b3 43 d8 eb 20 85 b6 f6 82 09 58 99 35 20 44 22 60 60 61 d2 8d e9 8b ea 58 af bf ba ad 70 03 98 19 a0 c3 9a a8 63 94 47 5c d0 61 94 b0 17 ab c4 bb 28 b7 56 6d 3c 66 1c 76 f4 8a d3 a3 a2 9e d3 36 df 1f c6 8b 4f 44 2f 06 a3 58 0b ae c8 06 e2 e6 5d 39 ab 18 28 fe 80 18 12 69 2c 60 34 b5 0b f5 f3 3c 51 fc 0c fb 43 82 1e 3e 92 d6 b8 06 cf 00 16 e3 49 a0 34 83 20 f9 b0 53 7e ad ac 4a c1 36 5f cc fb be e2 ba 5a ad 1d 29 74 07 19 34 61 0e 9d ce 84 60 24 6a e6 8d ed 50 e0 20 44 26 d8 76 6d f2 da 4b 12 72 5a 85 c2 b1 07 04 f5 10 2e 3c 67 1c 5a fc 5b 46 0e 4d fb 39 b6 10 73 22 47 84 10 93 df 5f c8 92 7e 87 c3 0d 24 3a 48 b2 ad c2 56 3d a2 22 e9 02 9c 58 64 c6 d5 a5 f8 c6 54 99 1c 0f 6b f3 db ed 81 16 85 28 17 b0 eb 11 c7 05 9f f9 d8 fc 4a 1c 36 db 16 fd 38 d8 32 34 5b 8c 80 c6 51 21 1d 91 01 c5 8a 60 ad a4 39 33 d5 32 9a c1 f5 b2 ab 20 46 75 db 63 e0 bd d2 97 c0 e9 fc 1c d9 17 4a d1 3a db ea c2 8c 46 22 21 c3 5a bf 6c 1e cf 28 9c 8c 2f b2 0f\",\n        recipeConfig: [\n            {\n                \"op\": \"XSalsa20\",\n                \"args\": [\n                    {\"option\": \"Hex\", \"string\": \"00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F:10:11:12:13:14:15:16:17:18:19:1A:1B:1C:1D:1E:1F\"},\n                    {\"option\": \"Hex\", \"string\": \"00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F:10:11:12:13:14:15:16:17\"},\n                    0, \"20\", \"Hex\", \"Hex\",\n                ]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/XXTEA.mjs",
    "content": "/**\n * XXTEA tests.\n *\n * @author devcydo [devcydo@gmail.com]\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2024\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nTestRegister.addTests([\n    {\n        name: \"XXTEA Encrypt and Decrypt\",\n        input: \"Hello World! 你好，中国！\",\n        expectedOutput: \"Hello World! 你好，中国！\",\n        recipeConfig: [\n            {\n                \"op\": \"XXTEA Encrypt\",\n                \"args\": [{ \"option\": \"UTF8\", \"string\": \"1234567890\" }]\n            },\n            {\n                \"op\": \"XXTEA Decrypt\",\n                \"args\": [{ \"option\": \"UTF8\", \"string\": \"1234567890\" }]\n            }\n        ],\n    },\n    {\n        name: \"XXTEA Encrypt\",\n        input: \"ნუ პანიკას\",\n        expectedOutput: \"3db5a39db1663fc029bb630a38635b8de5bfef62192e52cc4bf83cda8ccbc701\",\n        recipeConfig: [\n            {\n                \"op\": \"XXTEA Encrypt\",\n                \"args\": [{ \"option\": \"UTF8\", \"string\": \"1234567890\" }]\n            },\n            {\n                \"op\": \"To Hex\",\n                \"args\": [\"None\", 0]\n            }\n        ],\n    }\n]);\n"
  },
  {
    "path": "tests/operations/tests/YARA.mjs",
    "content": "/**\n * YARA Rules tests.\n *\n * @author Matt C [matt@artemisbot.uk]\n *\n * @copyright Crown Copyright 2019\n * @license Apache-2.0\n */\nimport TestRegister from \"../../lib/TestRegister.mjs\";\n\nconst CONSOLE_COMPILE_WARNING_RULE = `import \"console\"\nrule a\n{\n  strings:\n    $s=\" \"\n  condition:\n    $s and console.log(\"log rule a\")\n}\nrule b\n{\n  strings:\n    $s=\" \"\n  condition:\n    $s and console.hex(\"log rule b: int8(0)=\", int8(0))\n}`;\n\nTestRegister.addTests([\n    {\n        name: \"YARA Match: simple foobar\",\n        input: \"foobar foobar bar foo foobar\",\n        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\",\n        recipeConfig: [\n            {\n                \"op\": \"YARA Rules\",\n                \"args\": [\"rule foo {strings: $re1 = /foo/ condition: $re1} rule bar {strings: $re1 = /bar/ condition: $re1}\", true, true, true, true],\n            }\n        ],\n    },\n    {\n        name: \"YARA Match: hashing rules\",\n        input: \"Hello World!\",\n        expectedOutput: \"Input matches rule \\\"HelloWorldMD5\\\".\\nInput matches rule \\\"HelloWorldSHA256\\\".\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"YARA Rules\",\n                \"args\": [\n                    `import \"hash\"\n                    rule HelloWorldMD5 {\n                        condition:\n                            hash.md5(0,filesize) == \"ed076287532e86365e841e92bfc50d8c\"\n                    }\n\n                    rule HelloWorldSHA256 {\n                        condition:\n                            hash.sha256(0,filesize) == \"7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069\"\n                    }`,\n                    true, true, true, true, false, false\n                ],\n            }\n        ],\n    },\n    {\n        name: \"YARA Match: compile warnings\",\n        input: \"CyberChef Yara\",\n        expectedOutput: \"Warning on line 5: string \\\"$s\\\" may slow down scanning\\n\" +\n            \"Warning on line 12: string \\\"$s\\\" may slow down scanning\\n\" +\n            \"Input matches rule \\\"a\\\".\\n\" +\n            \"Input matches rule \\\"b\\\".\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"YARA Rules\",\n                \"args\": [CONSOLE_COMPILE_WARNING_RULE, false, false, false, false, true, false],\n            }\n        ],\n    },\n    {\n        name: \"YARA Match: console messages\",\n        input: \"CyberChef Yara\",\n        expectedOutput: \"log rule a\\n\" +\n            \"log rule b: int8(0)=0x43\\n\" +\n            \"Input matches rule \\\"a\\\".\\n\" +\n            \"Input matches rule \\\"b\\\".\\n\",\n        recipeConfig: [\n            {\n                \"op\": \"YARA Rules\",\n                \"args\": [CONSOLE_COMPILE_WARNING_RULE, false, false, false, false, false, true],\n            }\n        ],\n    },\n]);\n\n"
  },
  {
    "path": "tests/samples/Audio.mjs",
    "content": "/**\n * Audio file headers in various formats for use in tests.\n *\n * Each constant contains the minimal bytes needed for container\n * detection and metadata extraction (trimmed from real audio files).\n *\n * @author d0s1nt [d0s1nt@cyberchefaudio]\n * @copyright Crown Copyright 2025\n * @license Apache-2.0\n */\n\n/**\n * MP3 with ID3v2.4 header — title: Galway, artist: Kevin MacLeod\n * 78 bytes: ID3v2 header + TIT2 + TPE1 + TSSE frames\n */\nexport const MP3_HEX = \"4944330400000000004e544954320000000800000347616c77617900545045310000000f0000034b6576696e204d61634c656f6400545353450000000f0000034c61766635362e34302e3130310000000000000000000000\";\n\n/**\n * WAV (RIFF/WAVE) header — 2 channels, 16-bit, 44100 Hz\n * 48 bytes: RIFF header + fmt chunk + data chunk start\n */\nexport const WAV_HEX = \"52494646e69d2a0057415645666d7420100000000100020044ac000010b102000400100064617461489d2a0000000100\";\n\n/**\n * FLAC with streaminfo + Vorbis comment block — title: Galway, artist: Kevin MacLeod\n * 174 bytes: fLaC magic + STREAMINFO (34 bytes) + VORBIS_COMMENT block (86 bytes)\n */\nexport const FLAC_HEX = \"664c6143000000221200120000052e00319f0ac442f0000aa752c925754a50e5f02e117eeb46467e7053040000560d0000004c61766635362e34302e313031030000000c0000007469746c653d47616c776179140000006172746973743d4b6576696e204d61634c656f6415000000656e636f6465723d4c61766635362e34302e313031\";\n\n/**\n * AAC ADTS frame header — MPEG-4, LC profile, 44100 Hz, stereo\n * 32 bytes: ADTS sync + frame header fields\n */\nexport const AAC_HEX = \"fff150800bbffcde02004c61766335382e33342e31303000423590002000001e\";\n\n/**\n * AC3 (Dolby Digital) sync frame header — 44100 Hz, 192 kbps, 2.0 stereo\n * 32 bytes: AC3 sync word + BSI fields\n */\nexport const AC3_HEX = \"0b773968544043e106f575f0d4da1c1ac159850953e549a125736e8d37359d3f\";\n\n/**\n * OGG Vorbis — two OGG pages with identification + comment headers\n * title: Galway, artist: Kevin MacLeod, vendor: Lavf56.40.101\n * 281 bytes\n */\nexport const OGG_HEX = \"4f6767530002000000000000000027a7032a000000002acbc833011e01766f72626973000000000244ac00000000000080b5010000000000b8014f6767530000000000000000000027a7032a010000007e1abea41168ffffffffffffffffffffffffffffff0703766f726269730d0000004c61766635362e34302e313031030000001f000000656e636f6465723d4c61766335362e36302e313030206c6962766f726269730c0000007469746c653d47616c776179140000006172746973743d4b6576696e204d61634c656f64\";\n\n/**\n * Opus — two OGG pages with OpusHead + OpusTags headers\n * title: Galway, artist: Kevin MacLeod, vendor: Lavf58.19.102\n * 233 bytes\n */\nexport const OPUS_HEX = \"4f67675300020000000000000000919a59f200000000f6117eb601134f707573486561640102380180bb00000000004f67675300000000000000000000919a59f201000000b047e56601664f707573546167730d0000004c61766635382e31392e313032030000001d000000656e636f6465723d4c61766335382e33342e313030206c69626f7075730c0000007469746c653d47616c776179140000006172746973743d4b6576696e204d61634c656f64\";\n\n/**\n * WMA/ASF — ASF header with Content Description + Extended Content\n * title: Galway, author: Kevin MacLeod, encoder: Lavf56.40.101\n * 700 bytes: ASF Header Object + all sub-objects\n */\nexport const WMA_HEX = \"3026b2758e66cf11a6d900aa0062ce6c8a02000000000000060000000102a1dcab8c47a9cf118ee400c00c205365680000000000000000000000000000000000000000000000bc3504000000000000803ed5deb19d0156000000000000007040490b00000000b03a7009000000001c0c00000000000002000000800c0000800c000000f40100b503bf5f2ea9cf118ee300c00c2053652e0000000000000011d2d3abbaa9cf118ee600c00c2053650600000000003326b2758e66cf11a6d900aa0062ce6c4c000000000000000e001c00000000000000470061006c0077006100790000004b006500760069006e0020004d00610063004c0065006f006400000040a4d0d207e3d21197f000a0c95ea850b40000000000000003000c007400690074006c006500000000000e00470061006c0077006100790000000e0041007500740068006f007200000000001c004b006500760069006e0020004d00610063004c0065006f0064000000280057004d002f0045006e0063006f00640069006e006700530065007400740069006e0067007300000000001c004c00610076006600350036002e00340030002e0031003000310000009107dcb7b7a9cf118ee600c00c2053657200000000000000409e69f84d5bcf11a8fd00805f5c442b50cdc3bf8f61cf118bb200aa00b4e22000000000000000001c000000080000000100000000006101020044ac0000803e0000e70210000a000000000001000000000001e702e7020100004052d1861d31d011a3a400a0c90348f664000000000000004152d1861d31d011a3a400a0c90348f60100000002001700570069006e0064006f007700730020004d006500640069006100200041007500640069006f0020005600380000000000020061013626b2758e66cf11a6d900aa0062ce6c32330400000000000000000000000000000000000000000056000000000000000101\";\n\n/**\n * M4A (MPEG-4 Audio) — ftyp atom with brand \"M4A \", plus mdat\n * 512 bytes: ftyp + free + mdat start (moov not included in slice)\n */\nexport const M4A_HEX = \"0000001c667479704d344120000002004d34412069736f6d69736f3200000008667265650003e2236d6461742111450014500146fff10a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5de98214b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4bc211a93a09c3e310803595989841e21e02814c4d2f28f925da49e5fe61d4521f0088400d65662610788780a0563ab2a671af1d0cd2fd9997d18be037ff8852d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d\";\n\n/**\n * AIFF (FORM/AIFF) header with NAME chunk = \"Galway\", COMM and SSND chunks\n * 36 bytes: FORM header + NAME chunk + COMM chunk start\n */\nexport const AIFF_HEX = \"464f524d002a9d84414946464e414d450000000647616c776179434f4d4d00000012000200\";\n"
  },
  {
    "path": "tests/samples/Ciphers.mjs",
    "content": "/**\n * Various types of input data for use in tests\n *\n * @author n1474335 [n1474335@gmail.com]\n * @author Matt C [me@mitt.dev]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\nexport const ASCII_TEXT = \"A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.\";\n\nexport const UTF8_TEXT = \"Шанцы на высвятленне таго, што адбываецца на самай справе ў сусвеце настолькі выдаленыя, адзінае, што трэба зрабіць, гэта павесіць пачуццё яго і трымаць сябе занятымі.\";\n\nexport const ALL_BYTES = [\n    \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\",\n    \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\",\n    \"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\",\n    \"\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\",\n    \"\\x40\\x41\\x42\\x43\\x44\\x45\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f\",\n    \"\\x50\\x51\\x52\\x53\\x54\\x55\\x56\\x57\\x58\\x59\\x5a\\x5b\\x5c\\x5d\\x5e\\x5f\",\n    \"\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d\\x6e\\x6f\",\n    \"\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b\\x7c\\x7d\\x7e\\x7f\",\n    \"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\",\n    \"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\",\n    \"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\",\n    \"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\",\n    \"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\",\n    \"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\",\n    \"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\",\n    \"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\",\n].join(\"\");\n"
  },
  {
    "path": "tests/samples/Executables.mjs",
    "content": "/**\n * Executables in various formats for use in tests.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2022\n * @license Apache-2.0\n */\n\n/**\n * ---------------ELF Header---------------\n * 7f454c46         - Magic:            .ELF\n * 01               - Format:           32 bit\n * 01               - Endianness:       Little\n * 01               - ELF Version:      1\n * 00               - Target ABI:       System V\n * 00               - ABI Version:      0\n * 00000000000000   - Padding\n * 0200             - File Type:        Executable File\n * 0300             - ISA:              x86\n * 01000000         - ELF Version:      1\n * 50210608         - Entry Point:      0x8062150\n * 34000000         - PH Offset:        0x34\n * 54000000         - SH Offset:        0x54\n * 00000000         - Flags:            0\n * 3400             - ELF Header Size:  0x34\n * 2000             - PH Header Size:   0x20\n * 0100             - PH Entries:       1\n * 2800             - SH Size:          0x28\n * 0300             - SH Entries:       0x3\n * 0000             - SH Names Offset:  0x0\n *\n * -------------Program Header-------------\n * 06000000         - PH Type:          Program Header Table\n * 34000000         - Segment Offset:   0x34\n * 34800408         - VAddr of segment: 134512692\n * 34800408         - PAddr of segment: 134512692\n * 00010000         - Size of segment:  256\n * 00010000         - Size of segment:  256\n * 05000000         - Flags:            Execute,Read\n * 04000000         - Alignment\n *\n * --------------SH .shstrtab--------------\n * 00000000         - SH Name Offset:   0\n * 03000000         - SH Type:          String Table\n * 00000000         - Flags:\n * 00000000         - VAddr of section: 0\n * cc000000         - Section offset:   204\n * 1c000000         - Section size:     28\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 00000000         - Alignment\n * 00000000         - Entry Size:       0\n *\n * ---------------SH .symtab---------------\n * 09000000         - SH Name Offset:   9\n * 02000000         - SH Type:          Symbol Table\n * 00000000         - Flags:\n * 00000000         - VAddr of section: 0\n * e6000000         - Section offset:   230\n * 10000000         - Section size:     16\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 00000000         - Alignment\n * 10000000         - Entry Size:       16\n *\n * ---------------SH .strtab---------------\n * 11000000         - SH Name Offset:   17\n * 03000000         - SH Type:          String Table\n * 00000000         - Flags:\n * 00000000         - VAddr of section: 0\n * f5000000         - Section offset:   245\n * 04000000         - Section size:     4\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 00000000         - Alignment\n * 00000000         - Entry Size:       0\n *\n * ---------------.shstrtab---------------\n * 2e73687374726162002e73796d746162002e73747274616200 - .shstrab\\0.symtab\\0.strtab\\0\n *\n * ----------------.symtab----------------\n * 00000000         - Name Offset:      0\n * 00000000         - Value:            0\n * 00000000         - Size:             0\n * 00               - Info\n * 00               - other\n * 0000             - shdx\n *\n * ----------------.strtab----------------\n * 74657374         - test\n */\nexport const ELF32_LE = \"7f454c46010101000000000000000000020003000100000050210608340000005400000000000000340020000100280003000000\" +\n                        \"0600000034000000348004083480040800010000000100000500000004000000\" +\n                        \"00000000030000000000000000000000cc0000001c00000000000000000000000000000000000000\" +\n                        \"09000000020000000000000000000000e60000001000000000000000000000000000000010000000\" +\n                        \"11000000030000000000000000000000f50000000400000000000000000000000000000000000000\" +\n                        \"2e73687374726162002e73796d746162002e73747274616200\" +\n                        \"00000000000000000000000000000000\" +\n                        \"74657374\";\n\n/**\n * ---------------ELF Header---------------\n * 7f454c46         - Magic:            .ELF\n * 01               - Format:           32 bit\n * 02               - Endianness:       Big\n * 01               - ELF Version:      1\n * 00               - Target ABI:       System V\n * 00               - ABI Version:      0\n * 00000000000000   - Padding\n * 0002             - File Type:        Executable File\n * 0003             - ISA:              x86\n * 00000001         - ELF Version:      1\n * 08062150         - Entry Point:      0x8062150\n * 00000034         - PH Offset:        0x34\n * 00000054         - SH Offset:        0x54\n * 00000000         - Flags:            0\n * 0034             - ELF Header Size:  0x34\n * 0020             - PH Header Size:   0x20\n * 0001             - PH Entries:       1\n * 0028             - SH Size:          0x28\n * 0003             - SH Entries:       0x3\n * 0000             - SH Names Offset:  0x0\n *\n * -------------Program Header-------------\n * 00000006         - PH Type:          Program Header Table\n * 00000034         - Segment Offset:   0x34\n * 08048034         - VAddr of segment: 134512692\n * 08048034         - PAddr of segment: 134512692\n * 00000100         - Size of segment:  256\n * 00000100         - Size of segment:  256\n * 00000005         - Flags:            Execute,Read\n * 00000004         - Alignment\n *\n * --------------SH .shstrtab--------------\n * 00000000         - SH Name Offset:   0\n * 00000003         - SH Type:          String Table\n * 00000000         - Flags:\n * 00000000         - VAddr of section: 0\n * 000000cc         - Section offset:   204\n * 0000001c         - Section size:     28\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 00000000         - Alignment\n * 00000000         - Entry Size:       0\n *\n * ---------------SH .symtab---------------\n * 00000009         - SH Name Offset:   9\n * 00000002         - SH Type:          Symbol Table\n * 00000000         - Flags:\n * 00000000         - VAddr of section: 0\n * 000000e6         - Section offset:   230\n * 00000010         - Section size:     16\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 00000000         - Alignment\n * 00000010         - Entry Size:       16\n *\n * ---------------SH .strtab---------------\n * 00000011         - SH Name Offset:   17\n * 00000003         - SH Type:          String Table\n * 00000000         - Flags:\n * 00000000         - VAddr of section: 0\n * 000000f5         - Section offset:   245\n * 00000004         - Section size:     4\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 00000000         - Alignment\n * 00000000         - Entry Size:       0\n *\n * ---------------.shstrtab---------------\n * 2e73687374726162002e73796d746162002e73747274616200 - .shstrab\\0.symtab\\0.strtab\\0\n *\n * ----------------.symtab----------------\n * 00000000         - Name Offset:      0\n * 00000000         - Value:            0\n * 00000000         - Size:             0\n * 00               - Info\n * 00               - other\n * 0000             - shdx\n *\n * ----------------.strtab----------------\n * 74657374         - test\n */\nexport const ELF32_BE =     \"7f454c46010201000000000000000000000200030000000108062150000000340000005400000000003400200001002800030000\" +\n                            \"0000000600000034080480340804803400000100000001000000000500000004\" +\n                            \"00000000000000030000000000000000000000cc0000001c00000000000000000000000000000000\" +\n                            \"00000009000000020000000000000000000000e60000001000000000000000000000000000000010\" +\n                            \"00000011000000030000000000000000000000f50000000400000000000000000000000000000000\" +\n                            \"2e73687374726162002e73796d746162002e73747274616200\" +\n                            \"00000000000000000000000000000000\" +\n                            \"74657374\";\n\n/**\n * ---------------ELF Header---------------\n * 7f454c46         - Magic:            .ELF\n * 02               - Format:           64 bit\n * 01               - Endianness:       Little\n * 01               - ELF Version:      1\n * 00               - Target ABI:       System V\n * 00               - ABI Version:      0\n * 00000000000000   - Padding\n * 0200             - File Type:        Executable File\n * 3e00             - ISA:              AMD x86-64\n * 01000000         - ELF Version:      1\n * 5021060800000000 - Entry Point:      0x8062150\n * 4000000000000000 - PH Offset:        0x40\n * 7800000000000000 - SH Offset:        0x78\n * 00000000         - Flags:            0\n * 4000             - ELF Header Size:  0x40\n * 3800             - PH Header Size:   0x38\n * 0100             - PH Entries:       1\n * 4000             - SH Size:          0x40\n * 0300             - SH Entries:       0x3\n * 0000             - SH Names Offset:  0x0\n *\n * -------------Program Header-------------\n * 06000000         - PH Type:          Program Header Table\n * 05000000         - Flags:            Execute,Read\n * 3400000000000000 - Segment Offset:   0x34\n * 3480040800000000 - VAddr of segment: 134512692\n * 3480040800000000 - PAddr of segment: 134512692\n * 0001000000000000 - Size of segment:  256\n * 0001000000000000 - Size of segment:  256\n * 0400000000000000 - Alignment\n *\n * --------------SH .shstrtab--------------\n * 00000000         - SH Name Offset:   0\n * 03000000         - SH Type:          String Table\n * 0000000000000000 - Flags:\n * 0000000000000000 - VAddr of section: 0\n * 3801000000000000 - Section offset:   312\n * 1c00000000000000 - Section size:     28\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 0000000000000000 - Alignment\n * 0000000000000000 - Entry Size:       0\n *\n * ---------------SH .symtab---------------\n * 09000000         - SH Name Offset:   9\n * 02000000         - SH Type:          Symbol Table\n * 0000000000000000 - Flags:\n * 0000000000000000 - VAddr of section: 0\n * 5001000000000000 - Section offset:   336\n * 1000000000000000 - Section size:     16\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 0000000000000000 - Alignment\n * 1800000000000000 - Entry Size:       24\n *\n * ---------------SH .strtab---------------\n * 11000000         - SH Name Offset:   17\n * 03000000         - SH Type:          String Table\n * 0000000000000000 - Flags:\n * 0000000000000000 - VAddr of section: 0\n * 6901000000000000 - Section offset:   361\n * 0400000000000000 - Section size:     4\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 0000000000000000 - Alignment\n * 0000000000000000 - Entry Size:       0\n *\n * ---------------.shstrtab---------------\n * 2e73687374726162002e73796d746162002e73747274616200 - .shstrab\\0.symtab\\0.strtab\\0\n *\n * ----------------.symtab----------------\n * 00000000         - Name Offset:      0\n * 00               - Info\n * 00               - other\n * 0000             - shdx\n * 0000000000000000 - Value:            0\n * 0000000000000000 - Size:             0\n *\n * ----------------.strtab----------------\n * 74657374         - test\n */\nexport const ELF64_LE = \"7f454c4602010100000000000000000002003e000100000050210608000000004000000000000000780000000000000000000000400038000100400003000000\" +\n                        \"0600000005000000340000000000000034800408000000003480040800000000000100000000000000010000000000000400000000000000\" +\n                        \"00000000030000000000000000000000000000000000000038010000000000001c00000000000000000000000000000000000000000000000000000000000000\" +\n                        \"09000000020000000000000000000000000000000000000050010000000000001000000000000000000000000000000000000000000000001800000000000000\" +\n                        \"11000000030000000000000000000000000000000000000069010000000000000400000000000000000000000000000000000000000000000000000000000000\" +\n                        \"2e73687374726162002e73796d746162002e73747274616200\" +\n                        \"000000000000000000000000000000000000000000000000\" +\n                        \"74657374\";\n\n/**\n * ---------------ELF Header---------------\n * 7f454c46         - Magic:            .ELF\n * 02               - Format:           64 bit\n * 02               - Endianness:       Big\n * 01               - ELF Version:      1\n * 00               - Target ABI:       System V\n * 00               - ABI Version:      0\n * 00000000000000   - Padding\n * 0002             - File Type:        Executable File\n * 003e             - ISA:              AMD x86-64\n * 00000001         - ELF Version:      1\n * 0000000008062150 - Entry Point:      0x8062150\n * 0000000000000040 - PH Offset:        0x40\n * 0000000000000078 - SH Offset:        0x78\n * 00000000         - Flags:            0\n * 0040             - ELF Header Size:  0x40\n * 0038             - PH Header Size:   0x38\n * 0001             - PH Entries:       1\n * 0040             - SH Size:          0x40\n * 0003             - SH Entries:       0x3\n * 0000             - SH Names Offset:  0x0\n *\n * -------------Program Header-------------\n * 00000006         - PH Type:          Program Header Table\n * 00000005         - Flags:            Execute,Read\n * 0000000000000034 - Segment Offset:   0x34\n * 0000000008048034 - VAddr of segment: 134512692\n * 0000000008048034 - PAddr of segment: 134512692\n * 0000000000000100 - Size of segment:  256\n * 0000000000000100 - Size of segment:  256\n * 0400000000000000 - Alignment\n *\n * --------------SH .shstrtab--------------\n * 00000000         - SH Name Offset:   0\n * 00000003         - SH Type:          String Table\n * 0000000000000000 - Flags:\n * 0000000000000000 - VAddr of section: 0\n * 0000000000000138 - Section offset:   312\n * 000000000000001c - Section size:     28\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 0000000000000000 - Alignment\n * 0000000000000000 - Entry Size:       0\n *\n * ---------------SH .symtab---------------\n * 00000009         - SH Name Offset:   9\n * 00000002         - SH Type:          Symbol Table\n * 0000000000000000 - Flags:\n * 0000000000000000 - VAddr of section: 0\n * 0000000000000150 - Section offset:   336\n * 0000000000000010 - Section size:     16\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 0000000000000000 - Alignment\n * 0000000000000018 - Entry Size:       24\n *\n * ---------------SH .strtab---------------\n * 00000011         - SH Name Offset:   17\n * 00000003         - SH Type:          String Table\n * 0000000000000000 - Flags:\n * 0000000000000000 - VAddr of section: 0\n * 0000000000000169 - Section offset:   361\n * 0000000000000004 - Section size:     4\n * 00000000         - Associated:       0\n * 00000000         - Extra Info:       0\n * 0000000000000000 - Alignment\n * 0000000000000000 - Entry Size:       0\n *\n * ---------------.shstrtab---------------\n * 2e73687374726162002e73796d746162002e73747274616200 - .shstrab\\0.symtab\\0.strtab\\0\n *\n * ----------------.symtab----------------\n * 00000000         - Name Offset:      0\n * 00               - Info\n * 00               - other\n * 0000             - shdx\n * 0000000000000000 - Value:            0\n * 0000000000000000 - Size:             0\n *\n * ----------------.strtab----------------\n * 74657374         - test\n */\nexport const ELF64_BE =     \"7f454c460202010000000000000000000002003e0000000100000000080621500000000000000040000000000000007800000000004000380001004000030000\" +\n                            \"0000000600000005000000000000003400000000080480340000000008048034000000000000010000000000000001000400000000000000\" +\n                            \"0000000000000003000000000000000000000000000000000000000000000138000000000000001c000000000000000000000000000000000000000000000000\" +\n                            \"00000009000000020000000000000000000000000000000000000000000001500000000000000010000000000000000000000000000000000000000000000018\" +\n                            \"00000011000000030000000000000000000000000000000000000000000001690000000000000004000000000000000000000000000000000000000000000000\" +\n                            \"2e73687374726162002e73796d746162002e73747274616200\" +\n                            \"000000000000000000000000000000000000000000000000\" +\n                            \"74657374\";\n"
  },
  {
    "path": "tests/samples/Images.mjs",
    "content": "/**\n * Images in various formats for use in tests\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2020\n * @license Apache-2.0\n */\n\n/**\n * A small animated gif of a smiley\n * 15x15\n */\nexport const GIF_ANIMATED_HEX = \"4749463839610f000f00b30b00424242ffe700ffef00ffce00000000ffb500ff9c00ffff94ffff10ffffc6ffffefffffff00000000000000000000000021ff0b4e45545343415045322e30030100000021f9040532000b002c000000000f000f0000045a7049096a9d785595ce19170670081204c2600013d09de899aed411108480e229eb9a38194f553998044854725028c6623a14b3727cea7453c0404090790944cdb6abe40e40943317170ca7cff082bb0528d80b2b568662d14f220021f904050a000b002c030009000900010000040530483165040021f904050a000b002c04000a000700010000040530ac3943040021f904050a000b002c040004000700010000040550042943040021f904050a000b002c030004000900020000040a3010228298575c5949040021f904050a000b002c03000400090002000004093008492921415e11010021f9040532000b002c040004000700010000040590904565040021f9040519000b002c030004000800020000040990acb0960c52d41b010021f9040519000b002c030004000900020000040930084264a0128b49220021f904050a000b002c04000400080002000004097005b1ea24b26211010021f904050a000b002c030004000900020000040a3010228298575c5949040021f904050a000b002c03000400090002000004093008492921415e11010021f9040532000b002c040004000700010000040590904565040021f9040519000b002c040003000700030000040a90904525bd54882b42040021f9040519000b002c030003000800020000040990acb0961492da19010021f9040519000b002c030003000900020000040af0044244982408aa71040021f9040519000b002c050003000700030000040a308445c5a098128277040021f904051e000b002c0400040008000200000409902cb1961432d41b010021f904050a000b002c04000a000700010000040590ac3949040021f904050a000b002c030009000900010000040590ac491789003b\";\n\n/**\n * The CyberChef logo\n * 32x32\n */\nexport const PNG_HEX = \"89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000006624b474400ff00ff00ffa0bda793000000097048597300000dd700000dd70142289b78000005184944415458c3c5575d6c145514feeeccecccacdbddb6e096a5dbcdb6d06d80d06090466d6953454ab52ad0a65589840ac1d02a313c989af062820fa66210130d9a68b0363c34610135690b188b7183c13f44506c8115ba535ab6ddd2617f667f66ae0fb41596ddee2eadf13c4de69e7bcf77cff9cecf25b83f613b3b3b975b2c96f25028c47a3c9e1f5a5a5a7e05a0016000d0c9ef9442d23448a60edeb973a769c78e1d077272721a65594620106000505996bf1a1f1f3f67369bebc2e1f0ef6bd7aedd0a409d2d00e2743a1f2929296915046199a66901007aa3d1580600131313da24000000a594124288aaaab72a2b2bed1d1d1d8f8ba2386fc3860d9f25f3c84c0088cbe56a2d2c2cdc4708d12552880770a7288a3228088215003c1ecfd68d1b377e9e488f4b66dde974aeb2dbed498da71251146d538ed1b4e4746092dddee170b4300ca3c32c251c0edfd8bc79f3d164de4e0680110461794a02119292c482202c387efcf86f3d3d3d7b13814816024a2955e62a8b4451b4abaafad8e485d5743ca005028153699c4dd30c83140a857e4c9409c900a0bbbbfbc368343a34a3754a693a1c58b76eddf2dadada5d89002705b07bf7eee13367ce3cab284aff6c482808425e6767e70bc9ea0033d3e6c6c6c65fd6ac5953a1695a3453c3a150c84d295529a59aa669914cd3705adc6eb7926eaca74455d5605555d5c3030303f59224bd525f5f7f30992e87ff40344d5328a5caa64d9bbe4ca5cbe07f1666ae522dae40a5dd8ed30941c8e5727d63341a9f8a5f181a1ac2f0f07022029e02109d2b00bae2e26207cbb2f72cf03c8f9c9c9c441c580c804dc70b330258b6c020beb87ac9abecb59f8b087377b4f4f30a68b6de482549a29224ddb5168bc51cd5d5d54ff6f5f575cfa69633edeb971c78e2d195db055e77cfb6a2eaadb816e5b59ffafb19a7d3095555e3ab64341a8d96f6f6f6fe755f247c69d542abd9c0bd3c70f90a628c30fd5f56542c5c550fc3837600406e6e2eca9e2e433837fcefc0c8b2e079fe7b9fcfe7aba9a9296613c52f55084acc864a027013b28c828a2d30e805bcbe670fac4b5740f5a9285b18c6a0db4da8c180fdc6fdb035d850c555a174a4148410b85cae7293c97442a7d395363434347775757d91b6075a2a6c45d66ce18369258685de644659d96af45ff80345f9f908c932821313c4eff7639b6d1b06838358242c82d96c86288abe582ce6e6797e052184701c9797910796e61976b10c991fff7f7b5313b6373541d5340426d36f747414e5c67294679503a1e90634e6f57adbac56ebb14020f0e9a14387decf84038c8e232b53b45888dc6dec63636389d290c9caca5a3d09a6a2a6a6a628130054d33092a2c52272bbe4515996113f16288ab2c86432bd01001cc72db5582caf651202eaf5473e7e80d7af270409d9cb320c0c66331ca5a5602c1624180d492412392bcbf2db46a3f1394992f665c481b77a2f9f78e719476b5e16ff2e00d31dae8524cb30e8f560390ee72e5e243d7d7d34168bc16030a87575752ccbb20400a2d1e8b7478e1c390ce0f0fd5442fae6d7039f343d643956345f5fcbf1fafd00b219868145afc78d4b97101a1b833a32426d361bcdcfcf87cd6663a7a6649ee70725497a6faede86e4c2c993cf171716eee5753aeb9d0b7f5ebfae5df67a99b86164e8e6cd9badcdcdcdc7d27ae5a6a3f45147c7794dd30e2e59bcf896c0f3851ccbe602c0a8df4fc783413269d8130c06f79d3e7d7a4b5b5bdbd9b45b77c60304c3f0df75752db31714acf8dbe7cbbee2f5fafd7efff9f6f6f6b357af5e8d647ade3fa1780bad734c65970000000049454e44ae426082\";\n\n/**\n * Sunglasses smiley\n * 32x32\n */\nexport const JPG_B64 = \"/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQVFBQUFBQUFP/AABEIACAAIAMBEQACEQEDEQH/xAAbAAACAQUAAAAAAAAAAAAAAAAGBwkAAQIDCP/EAC4QAAIBAwIFAQcFAQAAAAAAAAECAwQFEQYSAAcIITFxIjJBQlJhwQkUFSORE//EABwBAAEFAQEBAAAAAAAAAAAAAAYAAgQFBwgDAf/EADARAAECBAQEBAYDAQAAAAAAAAECEQMEBSEABhIxQVFhoRMicZEUMnKBwdEVUrFC/9oADAMBAAIRAxEAPwCTPU+qDZ3jo6REnuUy71WQ4SJPG98d8Z7ADuT6EgSrlc/jiJaWAVHUHAPypG2pTXZ7AC6i4BDEi3kZETAMWKWQOW5PIfk8PYYDqif9y264XKrrZD5AnaGMeiIQMeuT9+M0jTJjHVOzK4iuiihP2SggN9Wo8ycESEaA0CGlI9AT7qfsw6YqmnFMwe33GropB4BnaaM+qOSP8wfvw6BM+CrVJTC4avqUtP3SsqDemk8iMJadYaPDSoegB90sfd/TBjpjU5u7yUdWiQ3GJd5EZ9iVPG9M98Z7EHuCR5yCdJolc/kSZaZATHSHt8qhtqS92exBukkAkggkdnpES4EWEXQbX3B5H8Hj7jEX/Vf1Xc3eTHUPqakm1HHTaZuGysskcNrp3V6YFoiju6FiyMhBAb5g3beBxT02VpmY/iJxST4mtSVeYuyCUp9BpD8nKuL4kzMSZp5hwQfKACLc7nv2bA5yz/UZ1fe9W2qyXOwUurDcqlKSCOywtR1gduynEkjRP385MQUZJOBwPZgyZLS8pEnJeaMMIDnXdLDqkBQ6WUSbAYnSFYiLiphRIYU9rWPe3cYy5m/qPatseqbrZLVp2k0q9tqWpJ0vkbVlWXXs3sxSLHH38ENKGGCDgjhZfyZLx5WHOTE0YgWHGiyWO11Ak9bJI2wp+sRERFQocPS1r3PYt3OC3pD6sObHO/n3Z6c32kk0xakasu8clrjT+pmWJY43UBgzFye5IxGx77cG6q0tTsufDTUMHxPESlPm4KISt+mk++nESUiTFQ8SEW06STbiLjv2fD16oemzTvO6WosGpoqilnt9VJNb7nQsq1FOshDEKWBBVht3KQQdo8FQRhs1WqpkrME1Dl2YqJ0lylSVEqSeFwDuNi4uHwZiVlqvIwlxN2FxuCLH/MKrp46NNBcnNe19xpbpcNRaqtka7P5IIgpI5lYCWONQM7gJE3kkZWRRghuPLMmdatXpFEGIhMOCs/8ALnUUnYknhYt1BNmxHkaXLSMXxASVDnwxr6jOjHQnN3Xdtu1XdblpzUt4YwN/GxrKlX/yiLGSRCp27URUMmQuTGpyzLl2Wc7VWhyS5eGhMSDDv5nGnUWYF7uS+lifmOwOPk9S5aejeIVFKjy44anS50zac5HVEVj00tVV1Nyqopbhcq5w086xkkA7QFVVBfCgfMckk54fArdTztX5SFMABIUCEpcBKQQpR4lyE7k8hbEn4SWpEjFWjdtzxOw7nHXWt9DQ6qRJ4isNwhG1JG911+lvwfhk8bpnLJ0LM0IRYJCJhAYE7Ef1VxZ9jdr2L4B6XVFSBKFXQe3UYTuqeVUFynhe8Wab93TKyQV9M0kM8KtjcI6iIh1DbVyFYZ2jPgcczR6RXqEtUGLLrAO/l1oLbHZSD0e4fg+DFM3LzI1IWPdj+8W0vyop7dUyy2izVD1s6CKW4VcktRUSICSqvUTMzlQSSFLYGTgcKBSa9XFJgwZdZHDyaEDrslAPc9cJU1Ly3mWse7n94cWiNCxaWR6iVlmuEq7WdfdRfpX8n44HHS+TMmw8swjGjELmFhiRskf1TxZ9zZ7WDYD6pVVT5CE2QO55nH//2Q==\";\n\n/**\n * Meerkat picture with EXIF information\n * 200x150\n */\nexport const EXIF_JPG_HEX = \"ffd8ffe12cd645786966000049492a000800000008000f01020004000000534f4e5910010200060000006e0000001a01050001000000740000001b010500010000007c0000002801030001000000020000003101020011000000840000003201020013000000960000006987040001000000aa000000300200004453432d483546000000010000004600000001000000506963746f6d696f20312e322e33312e3000323031303a30373a30342032333a33313a31330018009a82050001000000d00100009d82050001000000d80100002288030001000000030000002788030001000000c80000000090070004000000303232310390020013000000e00100000490020013000000f401000001920500010000000802000002920500010000001002000004920500010000001802000005920500010000002002000007920300010000000500000008920300010000000a0000000992030001000000100000000a920500010000002802000000a30700010000000300000001a30700010000000100000001a40300010000000000000002a40300010000000100000003a40300010000000100000006a40300010000000000000008a40300010000000000000009a4030001000000000000000aa40300010000000000000000000000010000007d000000250000000a000000323030383a30393a30312031333a32343a343600323030383a30393a30312031333a32343a34360043490d0048e801004b9a390040420f00030000000a0000000300000001000000480000000100000006000301030001000000060000001a010500010000007e0200001b010500010000008602000028010300010000000200000001020400010000008e0200000202040001000000402a00000000000048000000010000004800000001000000ffd8ffee000e41646f626500640000000001ffdb0084000604040405040605050609060506090b080606080b0c0a0a0b0a0a0c100c0c0c0c0c0c100c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c010707070d0c0d18101018140e0e0e14140e0e0e0e14110c0c0c0c0c11110c0c0c0c0c0c110c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0cffc0001108009600c803011100021101031101ffdd00040019ffc401a20000000701010101010000000000000000040503020601000708090a0b0100020203010101010100000000000000010002030405060708090a0b1000020103030204020607030402060273010203110400052112314151061361227181143291a10715b14223c152d1e1331662f0247282f12543345392a2b26373c235442793a3b33617546474c3d2e2082683090a181984944546a4b456d355281af2e3f3c4d4e4f465758595a5b5c5d5e5f566768696a6b6c6d6e6f637475767778797a7b7c7d7e7f738485868788898a8b8c8d8e8f82939495969798999a9b9c9d9e9f92a3a4a5a6a7a8a9aaabacadaeafa110002020102030505040506040803036d0100021103042112314105511361220671819132a1b1f014c1d1e1234215526272f1332434438216925325a263b2c20773d235e2448317549308090a18192636451a2764745537f2a3b3c32829d3e3f38494a4b4c4d4e4f465758595a5b5c5d5e5f5465666768696a6b6c6d6e6f6475767778797a7b7c7d7e7f738485868788898a8b8c8d8e8f839495969798999a9b9c9d9e9f92a3a4a5a6a7a8a9aaabacadaeafaffda000c03010002110311003f00068bb671e5ec1500c895b5400786055e0605b5ea299129b545514c16ab82e2b6b80c516b82e295ca315b5c46050b9062825b0a2b8a82adf55b836ef70b133411b057900a852454571a357d11c62ebaa826f51b50f5af862032979ac5708eb0cad546da094f6ff8adbfe346c953ae8e43827c32feee5f47f47fa2b9e4890ee457c303b0b433dcc209a9c09b506d56d83701b9c3c2bc4be1bf59eb6ccb40ff00ddb1e9c8fec9f66cb211b15fe95c5cf5cf9ff3ff00a51412464b1d3e4f85812f68e7a83dd3018f107439316fe19feb6197fbd745752c442c8ed1988f10c3aa7b1ff23fe23950241a29d2eb4e3f44fe8ff728860797271490ee581d9ab92301541da4e24476fde63284bfb58d11a651d0fef546d556f84ed8217f49ff0035d16a3181c94ed4b346fbd67b71461fefc8bfaad7fe0732f04fbf91630f5c2bf8a1fee3fe3aff00ffd00a99c717af541b9c0abc75c8aaa0029810bc0c8b2545c50bc0df155d4c2ad8a57052b7551df0d2b46788756c095a2fe05069b9c6d0a2faa00d451538f35b0ad6be60d46c64f56df70769216dd1d7f95864a13312d5931c6628a672d85bf9821375e5f6105ea0adce8f25036dd5a13fb43db2e38c4b787fa4718669e2353f547f9fff0014904f1dca318a6a823692361423da9d8e5039b7e58472468f229dfe83b6d4b4d8a6d32491b51863a5cdb486a6523a98cff353f63f97098f1721bffba70f067384f873e5fce48dec4f1e5f687ed53a83e0464625cff1624d214daf097971ad7a65b128914472e2952a30128017dc017f685e3dae6021891d6a3bd7df264dfa87f9ceb357a6b143fad8ff00e23fe250a6637aa250804f1ad24a7ed53c464270120e9b248e4dcfd4b6deec5bd15aaf68db78b467dbfc9caa122369391a3d71c468ef044cc11d0c7b3c32a90ac3a6e3a8396bbac9a686685c7aa4a92b251892ae84c335363edfc460eb4f2e4984bb8fd2ff00ffd10a8338e2f5eaa06f8155141c0aa82806e70522da7b98106ec2be18045287975544601456b92114134abfa45a80f1d8f438d23896bdf4bd875c8af129b4b7527d9db088a0cd62add13bb6f87851c4b9ada50b5663538084095a7b169364b60f73688ba85b955fac06052e2dde9bfd93bc7fe5633c46b8a2767132659835f4cbf87f9934b24b6f4c7ab17ef21e95fda53e0c3f8fd9c8c4b660d40c9b7d331fc2a6e4b0fe188e6e5014a96beac12a4d1318e543c91d0d0823b82325747662636c95751d2bcc016df592b65aa00041aa28a249e0b3affc6f97090c9cfd32fe77f39c4962962de1ea8ff33fe252dbed3f54d16f84572a632778e4527830ecc8c3299c4c4d14ce10d4436e63fd8bae1c5f3996e1fd3bb3ff001f4a29ccf8c8077ff2ff00e0b2b323767fd37fc53ac198c7d19072ff004d14bae24961611dec7e993f62e00fddb7d236fbb2e05be3aa303eaf543f9fff0014a77514b1d07246e42a287b1c85efbb990d5e326add6cd2dbc8b3aa6e36643d194f5072709f0972270138d21af81b2bb5beb6158243fbc4f0af63f3c998806ba179ccf78f271ffa78fe3f9ebef204a0bb87e2b79802c3b0aff03954a025b1e6babd38006487d13ff62820cf6a76abdab1fb3dc1febff12caa3220d16ad26b27825b7d3fcd53d4214172b223036f7c940dd848b4ebe06bc72d98dafb975e2329f1c7e9c9eaff0039ffd2297d4d15c2a8a839c707af34179bf97a05df145b5ebde3123a62c6db617463a16dfdb07328b5392d4800b3127ae4e96d171431f004ee461000624a2e4e2c828294cac9dd0b68b415eb8d3605e84a76d8f7c04b1215912bf10c36c486a9f1d1bece56c805682f27b0ba5b9b37f4e551bf7523c1877192848c4d844f1898a298b2daea8a6eb4c516f7e809b8b0ea187ed345fccbfcd1e4e5112de3f57f35d66a34f28ef7b8fa727fbd9a56d12ca098978cabbbc1fc57c46439b769b5b67827e99a902c69e18097654af00b633d273f0d36f0afbfb6188079b0cbc5c3e9e69b58799ededa3fd0faec667d2a43488b1a98fc1a193b7fa87fd8e64c0edc32dc3a59e7a96ffbb9ff003bf9dfd66f56f2ec9a7c02f6ca517fa44dbc7709bb28fe5900e9feb65797098efce2db908ce3f9b947fb34ae2d45d216b690896ce5eb1b74affc6ad98129cb1ec3787f37fe25d4f1981a28196d2e2de3792dd45d5883592d9b7741f2ff008d866463cf190f2652200b8ef1fe6495f4e8c5f2b0d3642cc054db48431fa2b43970c065f4b6e1cf21f41ff3565f42f1c452e6231edc655208f87c457ba9f8b24227e929cf9f8fea1fd197e3fa29758cb2c32358330742498586ea6bd40f66eb9127ab6766e61be19fd32fa575c46622548ac4db53f81cae71120e36af48714abf84fd28195163a249fbcb673553dc1fe0c3230279170f96c5ffd32211af3dfa8e99c800f5d2e6888622d257af86202248dfabd08276c34d56a72bf13c7be00caad6a51fedf6e986d055a092dc13cba0ed8c9785106970bfbad80db2ba21452d96d98a56b4230025b5aa3fa7d6a063694c6f345d5ec638de45564963128f498390a7beddbdd79603ce8b8b1d46394b86f740a0661d7e9c69c85f50053c31a4b71318d8491b157520a3a9a104770460ded4ee374da2b9b3d68ac53b8b5d5c1fdcdcfd98e66f06a7d893fcafb2f969a9794ffdd3aad568b6b1cbfdc7fc750d3412099ad2ed3eada826df17c2b27f46c8f0dedca4d5a5d7981e0c9fe99012218e42ae0ab0eaa7ae45dd8208b0a8ab585926412c2dd636dc7d1844e8b8f9f4b0ca2a415749d4356d11dae347737362dfef4e9b2fc5f0f7a03d73371e5f8879fd4693260363d51466a0ba6ea369fa53455e11c86975647a2b81f677de371fb20fc2d9467c11ab8fd2d5927e20bfe2ff0075ff001e4aa1b978caba315a6c8e36653dc11fad7355931189b0e3d90a5796915c49eb4245adef50cbb4521f114fb0d96e0d4d6c51f615f6be63ba15b0d52ac07c244c390dfc6bff001219b2f16446c6dcec19f1cbd3947f9e965d5b34523440fc517ef2071dd0ee3fe072a27ab899f19c73a1d3e9ff007a8c82fe09d394e9bb0e32f1e95ff3df2b96c5d80d7f890e198b43dcdb888d0fef20907c2c3bff004619331121b3af9c1fffd42c58b91661d00ce40bd792dc3ca36af6ee31b6063688e72ccb55d80c06458d529143ea02c7a75c2cc2a3aab10577f1f0c1c94058ab116a30d8f7c46e83b23ade35896887639191400a8cacc877c8b30a203a8a75070aa3f4ed52e2d53d0957eb1624d4c0cc54a31fda89c7c51bff00abf0b7ed61e2da8ef171751a5865e7b4bf9c8b92c6ceea269e09c705f89e42a15d3dae117fe4f47f07f938401f0fc7d7ff0014e18cb9706d3f547f9c965eda5c5b4aa93af10e2a8e0d51c78ab0d98602087658b2c662e26d41c88854ee0e45b691a34b6f85e19e19f90078a3153bef41cc283f41c8d8e4e29d6e38cb865e93fd24c56f62bc45d375c0d132505a5f91f1c7e01ff9e2ff0088e59c40ed2e7d24e2eab42320e28295d40d677315b6b508709436f74a6a92276f887da4cb7ad486ff00ee9d760d5cf09e13c9d7bf50a0f4a06891bf95f9a9f913956402f61c2ee716632162424a115bc4afc92565237029be40d8dd8fe66f6215923892ebeb30bac170e384f4da3997f9655fd4dfb39918b383cdd4e6c70e2e286c7f9a83d4adbd190cc8b589fe223afd1b771fcd94e5c5c27fa25c59c6f7412ba940e87d489ba8e841fe0d987970b8c546ea28a5840947a908d9251f6e33e1ff0036fd9c8e2cc6068b13f620156e2df8a337a8aa795b4c375207da4dfecffaa73620c6412096c4c2d66f5177b69c7c60ff0029fe287235628f36fd2e6109fa85c25f57e3fa28d7b365857837c2fd509a8f620e4632a2edf51a08ff0001ff0035ffd501c82ab713b1ed9c7bd700d23a1a03d09ef8548d959d8a6c83e123a60456ca45a80f3ef86d695a368d14721f6b011690e9e307651d7a1c4162559632b1835ebd6b903cd905601ca003607be0b56dd0a05e7dc540ee7082c429cce480476e83109a55b396ecdcc5f530df5a0691fa7f68d7b7cb271049a1cd84c4787d5f4b38b2f2dffa2a8d5648ad965359b4f51ea2127a3a8a8f424f1e0dc73698b424c7d66bfa2e8252109de226946ebf2f744a97fd2ce905392c7c14b807fca27f8627b3203f88b991ed29d7d22d45fc8b1c11b369fa899108afa73a0e276ee41ff8d72acbd9408da5c5fd644b5b1c9b64884a6edae34e7f42e151a3ea6094196023a555beda7cd4e6bce1962347fd2ffc4b18e98fd5825fe6a67a5de6837b6bfa2ef58c56b21ac704edcc46c7f6ade71dbfe2b7cb61c321c3f8ff00364e36a32196d9070cd29d56c2ff00406fab4dfe93a6b9adb5d2f415f7ec7db25663b49c3b3050b6d4f4e423eb711656feee688f1a9f0607a1cb31e3c52e6ccea67de8c924f2f4a2b5b841dca80f8cb4986f9ca29fcc4b9a94b71e5d4b79214be75256b1acd191461b8de9b6f968d3438684b883664d5f10dc6ec6a42f0c9eb440153f6d3f64d7e5d8e6b251e13479380762afc9197d6b7358dbeda36e47f92c3f8e533c36a47721cd54996dbb7f7b036e36dfa7ed2e5512607763cb928ac2970af1c3fb557488fda560370bfccad994725ee9ab1411de5fb8b1bbb57d3ef1bd2b886a6da6f14fe53fea1ff85ccb842131bed273f4ba89c8708dcc5fffd62e2d1a76a8ce429ec02ee49455a6e7f562c51317062431fb382904a9ba2b0a8dab884ba4456403baee298dad2a293c4002a460b4220ab98771be42d2136d2b4bb1d42d1520d4238b56a9a595d8f4a3735dbd397752dfe4b71cb23107aeee364cb281dc7a3f9d14bf55d3356d3ee8c5a84124131fe71b37faac3e161feae40dc79b7639c642e25423579691c6a5a47215146e6a76184032200644888b2cdb4893cb3e54b6336af3ac7a83292ce7720569c540cdfe9b1471c7fa4e8b5396594ff45bb9f32f9635711a5b5d24f2ca0b2321dc53dbe9e99394c35460428db5f69d0235c6a13d225709f113c4ef415fa72066032e1279215ff343c9b15d0b54ba018311c56a7a1e950388c94667a20e33d54dfcc9a2ea704b1c4ad3d93539b0a7250687e1af7195e694251a2d98e3381b0a175a4e8f0dcc56d697ea7eb2bce2827d89a0dc123fa66a351a7e11c4370e59d4898ac91e256867d774b431bc5f59b13b3c47f7d111f8f1fa728c7a9ff383893d2c25fddcbfcc9b4961e57d4987a55d3e67ad6d64de073edfcb43fca73263c24d8d9d7f00068ffa54a2f3cb1e63d297d78e332c0bb928dce9feab0fb43fd60b96891fe20c8e107789ff0034a845e608e54f4eeada398aecd55e2e3e63194011e96b333c8b7789672c6b35922a8414922049047c8ee331e62fd32148901561296ac0fce22551beff00f6c7fc3653026268b4722b9f9831bb0f4e47f8a2957ec3fba9fd6b872e1a163e9654a6ea2570ea7d3b91b823e10c7dbf95b31b80c7972470f5085914cae493e8dd21fb7f64337f95fcad97e3c888488363693fffd7284042904743b67216f6055e40c2441d88d8e218ae50cac5fa8aef816955a930544aa9c15482a8d1180508e5e38a04ada32102aa37187609ab56866924251bbe0a5269d2451d0ab50f8644858c933b1f366b16500b39c26a3a69eb65783d4403fc83f6e3ff00627271c846c7d43fa4d13d3449b1e897f3a299e95a6e89ab5e473e8ef369d78ac19ac660668bad498e61461feac9993a7803306361c6cf39c62632a90fe7241f9afe5ed5db50bbbc1207b168d15054171c7ed514d396fbe6ca397864e098dc764abf2a3c9d2b6a4758b90b15880fc5c218d49216a029a7876cbf2512d512427df985a59d574afaae92448e18cad0024315535fc40ca274241b604d178f693e5dbd97524b60a23bb576352bc7663521c9fe5f0cb6796222c63024bd67c9fe49d52c619d6e6506adeb471a7d9236d8fcf304dccb90642229865d45a8cde7d8da69c036d31892db91a8404f6e9b29cb8802041602746de8769eb0b9e16b7c2dfda5ad01f0233512d0891e8ce5adc331eb8ee9a4a616b4922bf9ad660c391e04a3123ba9ecdf4e2745388da71feacdc0cd2c67e9bf8acb0bbbdb3547d36f44b048392db5c1ad40ea0378ff00ac32a8ea278feb1ff12d3b859a845e5ed5ff00777d6a6c2fcf475f86bee0fd96ccd8668cb741024c6afbcbdaad8b97b63f5c8177e49b4aa3dd7bfd197736a31212f59a3950f5046cebd0823bd33172e3bf7b1e6bad2fa3b626daf944ba7cc6aebfc8dfefc43dbdf23872f0ec79318cab6289d5343b9b51ea467eb36cc392b8a160a7715a75f9e5d9f4a63b8e4db49638499784a68c05165ea76ecd4ea3308e3ea10637ef7fffd00170010be9ee0e723c9eb22e347401b623a60660351875aa1dc1ef85079a2ad8c48c4d7e2191218485a8dc5ecccc5a9551d3084c634d5bcfcfed0fa723216ced13c68448ad80ecc4eedba31fde570246cac9cb6246d879312ca3f2fa48135b01a42924a0aa252a0fb9f0cced1c85f9b83ac04c7c93ed6344b5d7219ac6edaa049cd68487001ec46640209ddc3a23931df37dfcba2e976b63a6dba3adb0e1c2bc78af124763fb54cc939a31e6d51c464c2f43f336a53eb0d2cf115423821614353ef41b7f93951cd122f6e26df048dba3255f2ce9d7f787519a2add2303c81f85c0dc0651e1e3944e61946c724fadeede3bc48cafc007150bb8dbc680e571cbea4ca1b24b3f9322bbf3bbdcc6844660f57d4509512b7c14a12a5bc59466466c91aabab7127b452fd634ebbd22e85beab6efe937f73728483c7fc863f6a9fc8f98128189d9c3be13b8b0b224bdf4fd4b5956eedc9a54ecc3d88f1c0353d241bce28917095fbfea529ae450c732342dd40229f10e85586c1b2c12891b38e491cdd1eb37889c2455bc807db8651b8a7707ae60cf07866e3c981994c6c358b69a9159cc6294fd9b4b92684ffc572f6fa72dc7a9e859c660a8ea02c669e97b0345743666145947fc692ae6613637410121b8b6e1c918f38abf04bdb7f1fe5afbe62ce37b8e6d442eb2d4e7b54f459d82447f77de9e2847f2f8618e690aa2d90c9b51576161a9ab4d69fbaba5152a07eedfdbd9b2e204b97a65fcd6c23a87ffd12f0a562a035e39c8757aeb6a542c10d695c348b6d832fc34f6c78934ac901552f5de9915e2535914c6477aef849400a91491315420815f888eb4ef4c36106d381a3d8ddd4e9b732301d6299417fba3f8bfe49e08c78b93892d54a1b4e3b7f3a2b2f346d52de225a0631af5953e351f32bf67fd971c07198f30db8f538e7c8a0e12c45396d95f15f26eaa4c749d4e7d2af16ea2557e26bc4fdd9762cbc06dab2631314f44b4d42df51b48f50b7014c83e21d3891d41ccbe3bddc094083494eb1a569b79306912aeca049434a657395ac7648c794b4622408b346e2abca4228a2bd57e7951f26de328bd3f4bb1d3c3bc4feacb2ecd23b135c78875606dafaf14708c3f7c0d100ee3da995f8b4764f0a1efa695e213dbb8498305a3350353b577197e5c232e3162da2f18954f92c4f306abf57fab6a56c97f675afa7200e07cabd3fd8f1cc38e2cb8fe926bf9b3fde47fe291934f8663d12dfba4ba3f2f699342752d26692c08204a95f563527f6658dbe30bff000b96649ed790708ff54c7eb87f9dfc5175671189db6217ae9f21602ed63319eae94643ee2bb8ff0054e4b0608cb712e28ff3a2dd1c808a9734bf5bd12d804934d2a6702ac8a405703b7f92dfcb99392a3b5d844b4f62c31a78d665e480abf75e8790fd4c330f361af547938262985a6b933c4b697b0a5e47fb21fe1627fc87fd893fe27870e6de8b38e5bd8ae7b64995a5b0769507f796f200b711fb11d1d7dd7334c2d35dc95dc43ccf25346028e00f0fe65ff9a7318e3218108753756ee67b298c5311f1f1a71703c474e43be5f872744d90ff00ffd22949080c3af13be722367ae905d72dc91590d08eb838b7481b2a890b052db62854329008ec702045608d1d7929dfc3254b6b9762283e9c8952adf0ad1f951874a1debed95d8b5e6c8f40f3079c22751691cf7c8365596269683c0494e43fe0f2cf1a71ebfe99c3cfa7c279d459645656bac0ff0073fe5b16329eb7892c7137cfe1657ff8247c075388fd437fe8ff00c75c1390e3fa27c412db8fcbad3e798a691ae461bbdbdd71661f278cfeb4c94714642e12ff004cdd0d791f5453ef2ef95752d1ada5b6b9659a3958346f112ca187c86d5cc9c3c5f498b4cf52272ba416a86381dc90102ee4fbf8644c69b6d8ab6adb33716925249e26953bf615ca886c545d423112b4dfb9077552286bf2e995982da166bf86e61758e391e36d8c82818d7dc6f8c61ba540daac5a618880de838969522aadef4d8e6c0c4986db3839cf522d0713233016f3c91b9e9139afdc0f5ff639872cd387316e1d425cb6475a4fae5acc2e2dca995450f6e4bdd595b6653e193c7aa89626241468371707d4d1cfd4f50a1693469cd60980ea6d98fd9ff8c5957e5f1c8f1633c12fe83120a5f1f9834c9a530ea103e9d76878bb50940dfe52f55c264794c710fe747eaff4a88e523af0a9eaba33381776a55b97fbb633ca3907b91d1b2fc5115e93c516331691dcc120e42542adfb5dc1fa47ed663e6d3f58b44a2bac2e6396758ae2e3eaf2f486edba57b2c8474ff5ff00e0b2cd366fe19158c935b9b69c03fa4613b7d9bf828c0ffad4fe399b38ed6dc3cd2abbb392302746aa56a9731ee0f71c8663ca1ba4c5ffd32755e124849fa33912f5cac5a2921a5006c48a505a721a0a537182f7485cfc8046a6ddf0a2ddb03b1a5705aaba384fb5be0a410985b798b5bb4b7582d2e7d0856b4e11c61b73ddf8f33f7e3640e7b34cb4f091b22cfc5d3ebfabcfbcda8dc313d47aac07dc0819031059470407f084b26b8f5d88798c87fcb62dface4a806e1103905f193171646e0c0ecca6847d2322931bd8bd0ff2dbcc37d717eb612cd2cecc6bcd8d4220ebbf535f7ccbd21919d5ecea35da611a94470a2bf3157d1b3b8643f0d682869bb6cb5cccc9005c7849e7d149a847468e345e43e295b76f6a023318c29bc1b577b5b8b93598991d507c4c766af811c57e8c81d99029859da2470807a7ed329345246c061ab412a66ee4b6be590bfa90d402ae05180a6cc1b3261c9a642d177ba368fa9ca52cd974fd40d196d6427eaf2d77fddb9de36ff272a911c5c279b8797466b8a3c921b97d5f4ab936f7b1392bfeea9766a78a3f47198d974913b869c59ce33ea1c4a91dd5bdf0e319f897e2e1bacaa4771dff00d92e5021c06c7376909e0cb1f3fe6a2e592d75702db593e8dd20e36fabd29d3a2dc01d47fc59993098c9b1da4eb32e3df7ff004c9330d6fcb77a501e15dd93ed432af8ff002b03954b1989b0784b8a41814e6daeb40d6e2e4b27e8fd400fde44c098be629ba8ff0085ff002732239a276973671225e452fd53cb92467948b40c2825520a381d0861f0b7fc4b232d303b844b12034cbbb9d32f543c9208a9c680fdc190ecc3dd72b8ca78f9221e93bb52df0b6bb926b623eaf29acb6bd0a13e0a7f61bc32dc7949e6ccc8479727ffd42466596d8bd7e2e8df46721d5eba97aa30b4471b9184f34c5561642943b138a0af662001db012a22d33fb7d38a533f2fdb2b34b2cd6d6d7aa365b7b8b892061fe50f4fed035ee725c60731c5f371b513aea61fe6f1a71cae789f43cb163c47ed27ab747fe4e93ff0b9299321b461feedc784a27fcacbfdc25afe62bcb79bd2163656cde1f5440dff000e09ca08efdbfd8b923003d652ff00396bf9a35fe4bc2e5235ff008ae1857f5263c16bf968777db2566f32f98aa07d79b7ff00253fe69c8f004fe5b1f77decaff2e757bfbad6592ee6965a46c56a144751eeaa0d733b45000934e1eb30c6205045f9cafa2b995a009536afeac887a1a8201269db2fc991c68c5848bfe6ecc92701c78d1791dc6d53405572156cea940f195ffd22a217350fc89e247ecd6871e101368998358462e2da43242c49915aad4db6d872f96428f4643cd2c9aebeb91cb2ccb20263aa46e18a923607c48a8cb4028e4c87cb7ac5f32c96b2ba7aa2929f5539ab03e15048a6473eabc281911c510e1ea6040b89a4d751924be8cc3711413c1d9412a41f14e5f60ffaa730476c69e7d785c137d77627a97966e50fa96cacea0d53f9d7e4cb9911c98f27d2449a8c1029aadf5b1f4afd1a4886deb05fde01efd9f2271516633486c7746c52fd66c1a2b722ff004f5a911ad4c90b1ebc01fb23c626ff0063970163f9cbc625ee49eef4d92255bcb462d086a2cab50c8e3f65875561fc8d9464c25a658eb70af61e63d4237da758e66fb6928e56d37b3a7ec31fe75c8c32ca277dd9433743f8feb265eb68baa4861914e95a89a73b5987285891d53b80ddb8ff00c0e665821b051f2280bfd2af2cc832a7c1d119bf79130f05906e3e59038fb9062fffd5298ade308c0f47e99c853d7deeb232d11680f4fd9c20a3aae34014d69e3812ab2042a38b57db10a5750f0e95c4a86a2241a9aedd30154645a85e29f858b01b8e5bfe3d72424438f934b8e7cc27565e74b95845bddc1f588c76722407db8ccb22fddc72d19cd6ee29ecf03e891822d6ff00c85a811f5bb0fa9b9fdb843c06bff3ccc917fc2e3c58cf31c3f8fe8a383550e47c46e4f2ef956715b2d66487c05c46b2affc1c457f15c1e144f2923f94271fae09df923ca93d96b1f5c8f5282784a32708096e60eff102071cbf4f88c4d92c33eb61963406e87d7ee4df6b7a9dbc287d08e2559ae29f0a12df0af4dcb53b6195924f46036018bc1e947f0dada9bb1521e595b82b536341b9f1ed938ee105d6f3d9bcbe92c724570e2bc1a80141debf65b89c05907094c7231a290013c36a96dff6bb7f2fd9c1114c8a5a4348222b2845b49c9f8b66e3bd003ff356106b74d222caf9d6e65588fab216288d21a00ecdc885eff463216082d792161447986559de2bc468d9588631b10cbf30763f76687269803c817591d4989a946331fecbfd3232defeee4606caf3d4ff0021b67fb8f5ff0062d950c1889e5c1272e12d364e44e392bcda9dd8a8bcb24bb03af052b27bf4cccc67247612e21fed9ff14c3369384583c4a1696da2debb5e69d34963729f6d88200f6778f7ff00918999d1c86bd40c3fdcb8421d42b5cc77f67ceee789a51320569e3e32dbcc0741281f0b7b306f5172d80eb7c4194a5e4c7f51b4b19a92db37a6d27dbb77af243e1520735fe56fb5fcd95ce01c79c41e497ccee10417aac427f7721ddd076a1fda4f6ca2371361ab96c51b67adea56038349f58b390746f8c11f4fdafa7e25ccd8c848586e8e521fffd62884b00a877e3df390b7ad28a9e389944a3ed0db05b1050cebc89a8a03bd30d36872ac4803826bdc61e881b96c5d56ab4a60b4f0af8837527aef8690aa0480120ed8d215adda22a796c722420a84f2c319a861bf8ed8d81cd953a34ba9d87d5e29253ff152337fc441c3c24f204fb831240e65eb3e44d256d34c7bd76324cb1105e5478e58f9f543cf8d77f6cc9d348efb174d9c478f9525f7d6732f976ee92f17bab9ac9213bf053422bff054cbe5f4a0736377b259da5cada24fe90e20c4c0f21e3e0c37e9804acec9aea837983b299191258fe2695457e0ad2878ff001c8d9b6c0104ecb13b7350a82a5665342caec7c7a32ef846ccb9a9492a8778e451342e94691762c79029cbb7c4a7638ad2cbb8ace082194d4490721242e7b720439f7a7ed64cb00776437d7e97f1aa6a9a726a36ea8384ab486f107f9130da4ff55f30f2cc035270f369f6bb07fa29249e598ee18c9a0de0b96039369f703d0bc51fea9f864f9a6552c008db775f2c5bff00c520e2f31eafa74a60b842eabb3c1382187b03f687df94c606276ff64c0e4947605151eb1e5bba9565134da56a03a4a3fe6aaf165f67ccd8e7007d3fe95946609df628a8f53b9b776749cd9cd5df50b65e56b283d3eb101aa0aff32e1c79632e5b16e9c48ebfe95ab8d634a7262d72c56091a9c752d3be28181fda684d7fe132ebb69240e63fd2b9bcb6973099f4eba4bdb6fd9303026be0637e9f21f1644e2894880291cf61716f234662a57edc26b1b57c78b6d5f96446120d863c0fffd72bb7bb8c2947143fcd9c8bd690b595cb32ab6c771869545e59540461d3a1c0cc056440103f51df15537953951571092c8348d0f4dbbb54b9bef3058e9cadfee86124b381ef1a014ff82cb630891bce31ff00672713267903421297fb94c7ea5f97168b59757d435393fdf76b6eb6ea7fd94d5387f723ae49fc383fdd3571ea0f28c61fd62a6baf7942d36b0f2d89abd24d42e6498ffc8b4e2983c487480ff929233ff629f0731faa75fd48b67cf5aac3fef058e9b62bdbd0b38f97fc13f3c31d4e41b0e187f52293a289faa5397f9ca727e6079e652ab1eaf325762b12c7181ff02ab88cf94ff14becfd4a745840e5f6c9ea3e5b69e4f2a7d6ee6633cf70cccf3b906bc7c4edcb7ea7326aa2eb881c7405283d92cba1c35dd7d3e649ef5deb8c86ca0bcf6e2d25861904919592688fc40024346d5500f8d3e1a656090d9cd27fac178123a17676e0dfcc40fb40d295a83837b6ca58f44f459872474f816848508dc69f4e125900b3d38feacb2280aca38bc6a2a4bc4db487af20c8324189288b9b759ccb0b9502f362e76a2a1aa9f1e5f695b2d1dcd375bb5797b736b398623fbb00710f1923dfe25cc0d4c883cb670f31225610d77aca5e8482ea088fa7bc722b706047f2b7507e9ca4e527c9878c09f50130aa35a8a58843a9c5fa4add471569c52e14760b3a54b7fcf446c98ca0f369998f4e5fd2ff008a42cda0797ef559b4dd4e3b77ff00963bff00dd9f92c9f61bfe17260c4f56a301d364ae4b2d6b4c2c9196109eaa8c2585bfe049189882c4714792eb6d5ecf68ee53ead216f88b7c56ec0f8803927cf271321cfd413e25f30ad25898e6375a44cd693b529c24aa31f0571f0b8ff25f2c8cef92f0750ad1f9df5081bea7afdaaca8363214e54ff5a33423fd68993fd5cb626f932190f22fffd0202c01a5363b31ce469ebc96a3e71cdc0b547ec9f6c2b4bf9c84988ee3b1380326e19f8068a4fa0e14014be3a7daa54e21495552b20ad28463485c5011b1a634ad53e120b570a0af88803e2dc74c8a944699a7cf7faa5b59db426e249dc208aa541a9fda23a28cb31c788d535e59f0c49ba7d037da7c7a7683058a11c2da3e145145d86f41e199d945507498e5c52252fbb2b35988536e7c13e82301dd9311f37c11c002ab16450637047206bdb6f7c8c8328961822416deaf32ace4142bd449fb4483e206544d378dd4657412b492025437114eabca95217bd695c883bb3ad94212e6628d192dc02ab93b80c6aae69ec007cb22c24173098db321955e783d38a1604072180ea3fcae996823ab510ad3dfc6b04ad13bc8b6af49850b3287028d41fb072bca080e26a06d610a9a8595d2543c5282770ca09feb98c780f30e089169ac2cdf711d3de26ff8d4e446289e492a0fa502a78ca69e1227f4c4e15010ff00a32ee2de250c3c636e3f86d80e028e052992e07c332bf03fefc40e3fe1860e0218f0a1238d2266312aa06356542c8a4fbaee9f864b88a88ab4f7114f088ee237650280ecf4f91d9a996c667b948b7fffd18fc75a7c5fecbe59c93d7aa20844c37e408fbbefc507c95a416d5055be2f0c4a624f550a45c8890d3c0e1648880807a557b62c1104afa678af6d8ed80207343815277a7df926c5d185a354febc894351d431ee3241059dfe4f45a51f372cb773b2ddc68c2c6db8b524908ebc94151c57fdf85732f46071dff17f0bafed132e0a1c9e95a9cba8fd6292c27d025b9355761f7e33bb70b1d56c83d23d4faaabce7fd3787148c7f396346afd9d874df2c8f2f344987f9a05c7a8c01a283f19a0ddc529d77151ff000dc7212671a61b1862f2a82160561ea3ee433545280f4ca0b921467a1b98bd71c4ab398b86e58d771b7d38c52796ca363ea1924e1cc382c108a54c7c450ffc178e4c31921a90064170c1a4480052d5f8cf36f4cb765d8ed96061ee4cbcb5f5b8e56fabf099b98f5c27c0dcf71f75373fb3961e4d19792cf30c9a2bcc56ea2b78a7aeef6e794bfecb81ff00892e6264e1bf375f28c4f321049a5da1b532dbeb4aae378ed9e19cb1f62e102ae44c635b90c0c63d0a8c6fab2d0346ce3b303f0ffc3d300df95b59b1e68c8a4d5006ac558ff6e94a7d3434c27887716dc7c5e6a914b72410d0c8a3c432b2fe2572c893dc8bef0a5726d7fdd8233e21a80ffc29c96dd507c902c34863d4a37f9153fc307a18bfffd9ffdb0084000a07070708070a08080a0f0a080a0f120d0a0a0d1214101012101014110c0c0c0c0c0c110c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c010b0c0c15131522181822140e0e0e14140e0e0e0e14110c0c0c0c0c11110c0c0c0c0c0c110c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0cffc0001108009600c803011100021101031101ffdd00040019ffc401a20000000701010101010000000000000000040503020601000708090a0b0100020203010101010100000000000000010002030405060708090a0b1000020103030204020607030402060273010203110400052112314151061361227181143291a10715b14223c152d1e1331662f0247282f12543345392a2b26373c235442793a3b33617546474c3d2e2082683090a181984944546a4b456d355281af2e3f3c4d4e4f465758595a5b5c5d5e5f566768696a6b6c6d6e6f637475767778797a7b7c7d7e7f738485868788898a8b8c8d8e8f82939495969798999a9b9c9d9e9f92a3a4a5a6a7a8a9aaabacadaeafa110002020102030505040506040803036d0100021103042112314105511361220671819132a1b1f014c1d1e1234215526272f1332434438216925325a263b2c20773d235e2448317549308090a18192636451a2764745537f2a3b3c32829d3e3f38494a4b4c4d4e4f465758595a5b5c5d5e5f5465666768696a6b6c6d6e6f6475767778797a7b7c7d7e7f738485868788898a8b8c8d8e8f839495969798999a9b9c9d9e9f92a3a4a5a6a7a8a9aaabacadaeafaffda000c03010002110311003f004146684bbe5c06452bc6056c0c50b80c8a57818ab6062b6dd3156c0c55b03155d4c55b51814ba9be155e6293d2694213129e2ce06c09fe6c68d5a3885d2c5dfc29deb8292568215846e6a8768643ff00269ffe65be4a9c412f0a5c27fba97f77fd0fe836cc8bf3f0c0e5a934b1d7738ada91bc881e23738d2daf8ee4484c2c281bec13d2a7f65bfc97c9445ecd393bff00e567f510ca849faa3fc2412d6ec7a83fb51e2458759286fc1fe7609ffbcfc7f1b92674a0762863340c3aa7b1ff008aff00e2190068d15c3a8e1f4cbe8ff70aa41ad5b67ea4d7635c3c3d1cd94481b7ef71285cc48aa641d8fef074d9be16db046fe92eb32c40e4d4352adbd6587a8fe78ffaa57fe032fc72ff004b2447d51fe963ff00a67ff1c7ffd0457340ef970eb81578c0ab80c0ab86055c3155c3156e98ab862adede38ab46441df156beb318c554daf003b0c56d522d4aead9bd487becf19fb2c3f95c64a32312c2711214514d6f16a89eb6947d2b95159b4e7a03eef6edfb6bfe7f065a6025bc7fd2348c9286d2f547fd512e916504a49f2743b1f91f039486d9c44e347e9923ff0047c5776b1c966ced791a52685f73211f69a223f6e9feebfe4c3c3c5cb9ff00bbfc7f31c6c790e33c124bcdbfc2187c43bd3b7b30c8872b8c5d289878bd695c9852556b45dc6280ba51f5a80b2ff7f11049effeb57fcac91ef70f3e2bd87fc92ffaa5ff0010a2643723d40b495451e9de9fcc3fcff932128821d7ca467bff0013514c22146ab5b9fa4a1f6ff2321135b16dc1a830d8fd0aae1594a1a346e080c3a6fdd4e58ec6586392363f892f572b4249565262929d7dbfe365c1d69d2d989fe6bfffd1457340ef978c0abc0c0ab87be34869a58d46e7052549ef1548a0ae48045aff00ad1a03c7ae0a45b4d70fe18136b4bccdd30808325a04c4f5c348b6cc4e16a4ee7010a0a609676e2d9a78145d4141eb72056685a9f17d83bc5fe5e19436b8b8f29caebe99ff00d2bc88568b80e71fc7174af704fecb8ff8dbec3e441678f289edf4cff98b5aa47f0c5bdb883232c8a4aba9aab29a107c4361bae4822d36fad5a6a748b5122def76115fa8f85fc16e57fe667fc432d044f9fa66e3984a1bc7d51fe6212e2daef4fb8114ea518eeac0fc2d4e8f1b8ffaeb2a9031e699463963b372b7d658c93370b8ff00968029c8f8caabfb5ff167fc1e44c8dd97084cc7d33fe1ff004f042caef1304b95e04fd9980f81beef87fe0726da3318fd5eb87faa2d99244a0aab545450f6c8db903342eadd11785c4aabb8d994f420f553928ca8b6ca2242946e2b6d38ba83789b665f9fec9ff5b2446ffd175392e12e2ff959f8ff006d5d3c6bb4f1ef0c82ac3b0aff005c818dec573e202a71feee6a00b43d3e2818fd9ee0ff00cd5ff13c84655b161833cb19dbe95b728a260ea47a374b40dd83ad3fe36e192977ff0035753465c51fa727fbb7ffd204d76a18003ae681df16feb0fd00df145bbd49c9a74c516e226e1b9dfdb145ad6868072249c349b57444e209ed8589577a328a64156fc34df1a6417292bdb638da297aad7718db1a753e2a374c832017a4d25b4cb3db3709146e7a823f9597f6972519107644a02428a2888af019ac944376a0996d3a861fb4d07f3a7f3c1933112de3f57f31c3cb888def7fe0cbff0055507c448098c51c6ed17fc6d1ff0032e41b316a2cf0cbd3916024d3c305b96ab1884c9fbdfb34dbc2bfe57b6102d8ceeb645dbeab1409fa3f534f56c1cfeec13529e0d6f2fecffc636ff6197c4ed52f53af965a3bfeef22ebcd31ed23facdbc9f5ad3e4fb132f55ff00265fe5ff005f213c7c3fd56722328fe6e5ff00a68848ee996330b1125b3f543d2bff001a3e62994a1b0fa1c0e2e128678258519e1027b406af0b6eca3e5ff1bae5b1c80864686e3e9ff5355b55fac822c9cb102a607218ff00b1ad0e583193c99e3c847d27fcc5b708510acc8536a48a6a3e1fe65e5dd0fc58403c8a7264e2e63fae85b7778ddad1983024fa646ea7c47faaff006b237d59e9320df1cbe993722fa6684551b6a7fc6a723216d39f0981afe143baaa7c2df1c2db83dc1fe0f8227a38efffd32e0a396fdba6684077a5511097af5f0c690511e96f538582d73c4d300654d2fc5f6be8c282be368aa6bd076c056954d251fbbe8302869e2256b5a1180166d1e417c40c09454f637b6caa5c2b074120f4d8390a7f6b6fd9ff002d39a62451a6819604d7f121d6ac3f8e2dcbaa29f2c695ca78fc4a68ca6aac0d0823a1046054624b0ea2424ac21bf07f773fd9491bfcba7f7537f97f624cb7eafe8e470b369f6db97fb8ff008e29ba3fa860b85f46ed76f8b657f9ff002be42af6fe261875263e99ff00a7433028c55810c3a83d703b1042f51fbb2aea2488f543d308953564c3198dd7595c5ee9acd369cc66b63fdf5949bededfcd99109fc9d565c12c7fd282bdc2da5d402f74d1c637349ed8f40e3f637de1947ec72f81f2bcb8c558fa18ca5c7bff0017fbbffab8838e52a55d188a7d971b329fe5a7fc4933065020d869e4b2782399bd44220b9ea0aed1b9f11fefa6c9e3cb4aba1d56615b5bea903622415a7cebff00135ccce32473e27271e489db20ff003d07342518c75f8a3f8e27f14ea3fe032bbeae3e48184abf9a888ee6395794abf6b6929d2b913b396353c71a90e252963f4f63f1c4ff0065bc7fe6975c9100b8b28bffd40812a4b0f0cd0bbeb7475435fbf1b6242af2790546c0624a1671f88127e78a57b052411f4e05a580256846d854ec898d44628a7639128a5c412a77c0c8298e40788c2a89b6ba9615f4987ab6b5afa4495e2dfefc8241f14327fa9f6ff6f089743ea8b465c319ff005959a0826432c72fc2bbb48568cbff00317127fd4443fbbff230d0fc7fbf71c4e78f69faa3fce42dc432c32059578f2155606aac3f991c7c2eb80821cd84c48582a6df00a9df22cd5fea676292452f21f655a84d7c3d4081bfd8e0d9a0ea200d1f44913eba4e16cf53aa32ed05d11f1c7fe4c95fef20ff0088659c40ec7fd3b4e6d3898e28ad951ade6483528c371a7a33835574ff005d7edc792ebeafa9c4c79a58f63c9b9feadb7089a353fcafcd7fd8b1c84aaf61c2ec2132458319a924481aaae41ea36df2076478dde1502a2cdebc6c229987194f44957f9264ff008dff00632e865ef706708ddc542ee231b9914551b7a75fa0d3f687f3ff00cdf95ce1c27fa0d247543861c4329e71b751efff001abe63cf1b4ac9911e3024f8e21b2483eda1f0ff009b3ec608643134843012c34527905358241f64d3768ffc8ff8c6d9940890505dea0824e6bbc128f881fe5ff9aa26c157b1e6d987208cbd5fddcbeb44340550056f85baa9dc7b303801a2e7e5d30fe12fffd50d5001a743db342ef69a0cbdf152a8c78ecbd0e05a595a57977c36b4a8a5140a8eb815d2283b0efd0e1085e14851ef91290a946e20740705ab6ca540e5dfa789c6d0b24627a7d03084af85e6f56336fcbeb00d1387daf9648593b7363202b7fa5905be927d102f5e3855cd64b351cd093feec400afd566fe6f45fd3ccd8e9cd7a9d592232bc7f4ac9bcb36352df5f658a9508554b01feb13ff001ae0fca447f13903552ee536f2e88958da5e7243bf0954713feb15ff009a3233d182363fe99075025b4e2829ccb68de8ca14a7530c80c909ed546fef22ff005a3cc430943628184fd588ff00988bb39b4d9e1fa8dc131dbbee90cadc8237f3da5d0ff935265b1e1229a32cb8b698e19a0af2dee74a3e8cdfbdb36358671eff00e57ecb7f938d91b168b315915ddaa91f588cb03f6258cd2a7c194f46c946302cbc592bbb696fbfef9478a80c313871f7ca29f14ac6974a589e35bb652455048845186e3e2a7c3be583146b6971329e7b1cbd494b728dbd48e943f693f64d7e5fb2d984451a2e31d8aa5548f5613543f694ef4ff21c6572c6aa7b825e1ff9e9136e36dfa7ed2640131e68e5c94d635943245fb5f12c67aab01b85fe647cb8cbaa6af92274c92dae2036970dc268f7824f15fe53ff0018cffc266408c6437fa9c8c3964457f31fffd60b545f7cd0bbe6f92d00a6f8a15938924376c696d632823c2b812e750540ee31b452e04d00037c55548631efd7236a8bb3b582e61558ee912ffb5b5c0f4d1b7f87d39f7566ff008adf2c1107facd53998f31e843de5b5e5acc52ee378a53d9fbff00aadf65ff00d8640d8e6ce244b929a866a2a02cec40551b9a9d8623734c8edbb21b26d234388cba84aa978c092dd4815a704033698a0203fa4eb32ce533fd06e6d5347bf08b14cb2bb82ca54ee29fe4f5eff672464180895386e2d63532dd4bf0060bb9d89ad057e9c89932a513e6ed0526102cc0106945af8f881c708920c1636aba7de44e91832da9a7223ed01d7e1af86466624516511289b0a7358d8c72a4105d29f5d794714c37341bf2236ff85cc0cb8a858fa5bfc6121538f12f49352b1528d1996d4ecd19fde4647fc371ff006595c32ff9cd12c313f41ff9273692df46bc61e9ff00a248fd6ddf785e9fcbfcbc4ff2365c29c5a1745053e93aad88f5550bc63af13ce9feac83ed2ffaea993b3d53e1dee1626a8b22f19a14969b1a8a38fa30188236626479174eb03a2c96aa1428a3c752411fea9f886552df63e94101046b1b7242429fbffeba5ff87cae248d8b5f26db90284fc2edf146ebf65bde33ff00124c33875fe14ac601d832fc13762360c7fc9fe47ca784c7faab5dca2c3d46a9fddcea7ed7d904ff0095fc8f96c64c6248363ea7ffd702010a4781cd0bbe2aad50cbe0462ae00825ba8c50a869251576c0ab8a18f6ebe38a2da2c7a8c3c95512467254e0a5269cf1ad0ab50d7052414541abdf5bc5f5770b75667adadc0f513fd81fb717fb06c9899e5f545aa5841dc7a25fd045595a58deceb269ed25a5c0209b69419631bffbaae168dfec66cbb144195869c9290152f525fe72d32f9aeae2e0306b564555151c871fb54434e59942745c6ab083f26686ed79fa4250a96a3951829452484f8550f1f0cb25bb006931f32da7d7ac8416043b54c862ad095535ff0086a6572e6d913b305b1d32e24bb5840093ab135229d773ea13fc9fcb9394c00c44492cdb43f2fde5aa4ab3383f17a8889d3e9f9e631f516db005241325cc9e645692500c3298d61aee1013dbfc94cb0d089620d1b651087137182e843febd4007c0ae609d3f177333a8c721ea08c90c6d03c777241203bfc278b123ba9fe7ff6589d3ca236947fa991c5c86079712db69ae2dc2b595c892371c96098f503ed717ff9ad72b19a50fa870ffb86b5b72ba5df9e17501b4bbec57e127dd7f61f326330774100a5371a65edb31684fd6235deabb4807894ff009a72cab60410865911d4d09dbed0ee0fcb31e714370dc243586e97d4b394d5d7f95bfdfd19edfe5638e75b1403dead79a74d07c4a7d6808a86142c01dc72a7fc4b2793098ee19a0db8c838b9a301412753feabff00366398f722adffd00d2d36e3d0e689de076cca2bb11d30320d2721f0f518a0ab45c1589afc43016245ac927909269503a61480d45272eb80864ad4e8c0e047373293f1d715545af71b6284dfcb2d12ea20331577042ad2a0fbff0093993808b71b3dd2657da7c3a94725adc36dcf98a1a3000f6cb41b71ea92cd72e1f4eb382dace25658471e35e345e24afecb7edf1cb8cc0e6c040963fa7ead772dfb492c6429f850b0a1a9f901f0ff0091903901eee267e191ee4dff00445b5d4e6ee58c99d483507e1603701957af1cae520c85a631ce52e156955028a06e36f1db2027ba98ec80974259fcc4d3229e062f53980bb48dfbba71629cbf99a35cb6721cbf9cd12e484bdb69ac6610dfc4dc4ff752a9a1a7fc54e7edf1ff007d49ff000998d28907671eeb98722dc84e70482e212695e8c3fc961fcd83c6e85b4c011712a724a374914c66b5008a7c43f695c6caf920411b3513dee5be9d5383aadc43fb514837dbf94f5cc6963e0de3c91c48ab6be8a4a476f27a6e7ecdbce4d2bff14cff00b3fecb2c865ef4892cb9fabc92d2e6229703a9145940ff0093770999077e682025b245c6aa4f28ebf0c9daa7f9bf9397f9594485ee3ea604376f77240be99660a87e0a6f4f18c8fe5c23248726719ed4554fd5af41920fddce057881f03fb7f92f93a07fa326443fffd10a010941db342ef5cea4853d31a5b71a8db0daaa2c741cbbd322b6b43028477c36a172321a2d36ef4eb85051df51b79ea6ca67603fdd72282df743fbcff9218816e39ce63f547fce82d9ecaee1425a23c17ac8bf1a8ff5997ec7fcf4e180c08e6d91cb19722a119a8d8d4642edb2a91567772d8ceb3a286e3bd0fdd96427c2c251e2d994c37515dc097708a73ea3a508ea32fbeae298d6c82beb2b49e40ccb566501e8694c84a56a364b868b60438512a30aad5cec057aa7fad912cf88abdad9db5af268dbd491fed3b126b82c3177d60ab0523f7a3651e23da990e3ae49a51b86774f52260b206a518d0353b57a65b3c6271dc35dc41f52d1a8defa5e8de42b756f5af0701c0f97fcdbc331c4270fa4fa7f9b93f7b044b16390f49ddb4d32ce443796123da114122d7d44527f62689ff78abfecb8659296d731c23fd5717ef21ff5520e11810578b67a8170a9c0f565a143feaf2f897fd46c71e312dc1e28ff003db04c72285d434f88716b220cbdd41a0603b7f92ffc9974aa2838ac584a5944ab55146eebd0d47fc45d731e78eb71f4b8c42261d424745b7b98d6e13f6437c249ff008ae41fdd4dff002730e3c9decc4ef62b8c42405ed59a451f6e1701674f665fb32a7f94b992636b4839103fc4bd40a3507fc497fe69ca0c29890a40cd0b196da429211f115fb2e3e5f6792fed65b8e49ba7ffd204ac6847f29cd1077a5b94d5415342305a405e1890396d8a17972011d8e05a5a1558541fa3254ad8dba0c89415cc5168c5803db7a1c8dee94d74dd535c461f5612dd20d943c6d2fdd2ff0078bff2332c139071f262c679fa53a4b68b50ff008ea68df5673d6e52448cfccf17497fe0e39309cb0fe2718c8c3e99712125f2bc124852c3525e5de19c02c3fe7a43ff0054b1108c85c4b6475247d4133d2b47bbd3a2786722449086468eaca08fa36cb217ca9ae59448da85d958d8d68a01a9f9e448a66929bdd8b156690d4d0d2a773f645720432541729c0349fbbaee01d8d7e5f6723c29507b98e58d8223b236c5c50135ff00294f2c44775533084b329407d261252a402a478d3e16cca22e2e3643d7ea5042a4d2191d1fb46db9fa01fb5fec731ce49479b4544f24442fa8c3209a123d451427a120f54746f85d0ff2e4e19c141890aebeb486ba7ffa35dd096d365358a403ed1b476fb3ff0018321e142478a1e89ffb5fe3d6c774326a7652394bb89ace753466009507fcb5fb4b8d9e531c7fd3ff008e289d7f456ded8b301710953cbfdd886b1bfcc8fb2f96406de9f5458cb74ba58dbe2122904f5ee0fd23f6b2ac98ba86b21bb79564915269bd293a45707a57f96561f67fe327fc1e4f0cfa151246cd14a056f63229d2ee2dc1a7f3d3fe36cc8316c41cf03a7ef54d56b559d3707baf35ca8c5487ffd3020717627eecd13be5f5464a537c6a94398d63a771815b6ad14d36c2aed81d8d305aaa2b71ebbe2c4a261d5351b788456f3fa510aeca881bfe0f87a9ff000d878880c258a24d90e9351bd97fbcbb9893d6b2301f703903ba46388e8829248e4243c9c8ff0094d5ff00891c9726c0172b2250ab0520ec41a11f7645245b28f2a6a93cd742d1a57998ef52410a83aeff006be2ff002b2fc26465e4e06a7081b81c288f340f4ede62a7e1edbd376d96b97ca36d512c611ee53e2455151bc8db9f6e208ca886db55304b39ac95770a3e26dc1ff558705ff63913b2844c10848c03d3b904d1491b2af7c54ad33b4370afc8bc75a156028694f85d5fe7fecffd865d16b22d52e2c6c6ee431c0cb69766856ddc9f464aeffb990fc513ff00919135745c79e03563e94ba57bdb398c3731b161feeb9366a7f34727d9917289e00d70c862771c4bd6586e8704dc8df86eb2291fb4bff35a65423c27673632c531e6acef0df7eeb512639d4521d4694e9d12f147ecff00c5d97c6427b7f13873877a008d4348b82a3e027765fb51c8a7fe15d5b2128906c7a64d2418a6114da66a29557faa5d81f1c6d531fe1f122ffc27fc57968c80f3fa990a285bcd2dd7775a06140e08e0dee1d7f76fff0012c89c37c906086b39a5b2b9157709d3634f9738cecebfe52e4019439223b1ddcf70219de480810bff0079074e04f8237fbadbf97270993cd9d81c9fffd42f243c25abbf439a177ab829f41586e461485e854aef8a96c9d80ed809400e27db14a2b4e843191de286e00d96296678587f94be97db5dff0069b2424073f534659575e0ff00378d1aab3807d2d1ad8a8eebea5c9fc266ff0088e489be51835c4c4ff94ff78856d4e58e4f4c5bdac0de1f5740dff25393656dc3103d67fe99b3abea808e33845ff223897f54782ad7c18af6d5b56a81f5b6fb97fe69c8d2f830ee4ebcaf7d773df95b89649288c4542f0ff8255079664e9e2372e3e7801c95b5cb849a43105a981bd4753d0d415562dfe4e59293500c7c5c962591f88e34f8791dc6d56e0a51707364a678c8dfbefeed8d43f226847ec06e2dff0012c14955706d544d0b978c925c354d36daaabcbfd5c1ee48423cbf59491e50fba5551c31534db97f332f25c98084d34bbeb960f048cbea6d21f517929af872183266e08f11f545a32c48dc232e99ee53d39922962eca095a1fe64e43f76dfea36637e7b14baf0b8dba4b75a44cbf1c2a5941aaff003afc9d32d8ca33fa489b03143adf5c43f05d2974ff007e01f18ff5bf664c1c1bb2190a211bd5b5290d2eecd7708b5e7113d7d30df614ff00be1fe1ff007de58058fe72d8297cd6ac8ab7103168eb45916a0ab0fd86fda4917fdf6fff000f954b1b5ca3d42adb6a972adb4ab1cadf69641ca097da58ff00dd6fff00162e08e431e698e4ef45f3d3ef58c6ca6c2f36e56f28e51313dd3f69797ecf0ff80cc8b0596c50d7367716e47a8bf0f442dfbc8c8f04947c4bfeae44c1487fffd5049120520fed74cd0bbe5aa4a5623d3b61b43668003f7e04af60081c4d710abb7e3d3150d275a9edd312aaeb7130e87901b8e5bff6e104b54f0c25cc23e1d7a6f4fd19e332a56a43307afb70b959d3fe078658329ead074a07d265055171e5aba3fbfb5fab37668c345bff00cf26960ff828f0f140f4470e68f5f11b6d2f48937b6d45d07fc5c8241ff23202b4ff0064991e089e457f3528fd514c7cbda34b6f7ff595bc8648f895e309e5c81fe6e4070cb7140c4f3639351198a0a7a8cdf5ad46f618d7f76a8ab24d4d9096f857a7c5cf8fece277b3fc2c46c93c5c176820370370f248dc54d3ad07c47f9b0852e8a4819f82aba4ce3ecb100151fb5cbfbb7e0d894b83f0726828013c76049a1fdbfd91fb1f6300494290cc232240a2de527e2d9b8efc457e8fdbc3cb74aadbdc309a458fe37e4514bec033372f87bffb1c4ee2984a361486a6dea324ea55949078310cbec47d9ff85cd64b0d1e5193863311b111c9f8fe7a222b89dc836d71cffc96d9bee6fb5fec1b2bf0b1df2e0937c4e19f596292a3dddc0a8b8b65b8a75e20ac9ff0b9911e38f29717fc358e4c15b8f5a943169f704dd59c8f6b327da720803da49a1dd7fe7bc7993191fe21c1fee1c7e1ee54985cdbf2b8951984aa14cc9c6486503ecace07c127f92cafeb2659115d78a2991f24b2ea1b6928f0b712ff006a16af253e15217d54fe493edff3e42510d5200a1a42c144372090a3e063bba8f6fe78bfc9ca85c4d860af05fdddafc25fd5b77eb5f8811fecbedffb2f8d332232b161b04cbfffd6031d47153bd33436ef4ab48a8407ee303105498549a8a57b61661c15147204fb8c2bcdc26ea302697257ae142f0180241db02aa4450835eb80a14a5923435e4057c4d30a5a42d291e8ab48dff1582dff0010ae1e1250481cd9b7972c961b56ba625e5084739119245e5d636f538f2f8b2ec479ecebf201c5c90b7104834ab8a49c5a79eaee4feca9e2c397d0dc72c3c9039a5770f6f6f32c0b2fa60806320f214ebe0fd7ece0b4d28349c88e6cab226ed22d0fc15e343c3e7fb591b64021d8842792855ea25534e4acc7c7ecbc7f1e1e4c963b80ccb20f52265ddc6c58f2529cff006793237c2d8aacb84b78e14735e71060f1b1edcb9090ff00b1fdbc9310534bab817400bdb45bc8828e2e0fa772a3fe2b9d7e197fd4972894c5d49c79e2dad2e3a4accc5b4b9fd7602ad6930f4ae947fa9f626ff5a3c89c40f2714c1463d56f6d5cc5282c17668a50430f60df6972a11ae4c7888d9596fb499dc49ea4b61783a4a3fe6baf075ff8c99903250e5fe919095f3565ba961667594daca4ef796eb5b792bff2d76a7947f17f3c7861901e5cd9c85352ded993e9ea76a2263bade597c51303fb4d6e797fc93cb2c16b3b736ce94268ccb6732dcc1d8c441dfc1a193a7faabf1e44c014f08297496f244e50a509fb711ac6d5f1e126dcbfd5c8f8647263c2ff00ffd70714cb4e2c287c7342ef08688624a83b1dc6492a6cee0056fbf032a54550072eddf155ace2b403154c2cec6d668565b9d52dad14ff00badb9c928ff5a28d47fc4f2c1189e72e16896420d08ce48af43cb10afc77d7778dfcb6f0ac2bff000571cb0feec759cffd830e2ca7946305a350d161ff0079b480fe0f773bcbf7c49c23c1c71e911fe7fad3e1e43ce5fe922dfe9ebd8ffde5b7b3b5f030db203ff0527a988cd21cb861fd48a9d38ea672ff00396bf98fcc52502ea128af5081100ff8045c7c599ea57f2f8c74ff0074ccb49323e8debcd2196491896918d6bc7c5bf6f7cb79071081c5b297a024d3a2a9aaf00f53debf157091b24318785e38dc321579233520026b1b5517e85f838e42e992044a5a354a162edc5bf9881b3a9a53972e5819ad242fa4cc2aacb5414242853c69fecbf6b092a16f051007501594717402a79c44d263fceaf1affc3e1082ab3442432c4c540b9d8b9da8a9f60ff373fb6af930d76eb8b896194c687e0005032123fe0d331b2920b44f9a8cf7e974162b88a3609ba386e2411fcac7ecff00c1657c67faac7c4dfd438d7fd7d5d047789f5d8945019852651e0b7517267ff9ed149931901e6d72ae9cbfa6a0fa76997009b3bd485bfe59ef3f767e4b2ff76fff00099214587084235bea365554e4233d42912447fe00b2e020140b8f25d0df5bec92afa0e4fc45be28181fe651f1c792163fa49e25ef6bc24f5ec2436f29e9c5eaa7d9255f8245ff0021f2519772f0f72a2f982e633f56d5610ea362c52b4ff5a13c78ff00ad6ef0ff00a99602912e85ffd02c247d07639a2a77d6e4e492712d51d8fb62abb93d781dc7638a5c9271aa3fd18a80b969d69be2abc10e2b4c690d9507be34aea6d4270a1b4a0ebbe04aa5b5b4973770c10c6667958288ebc6b5fe66fd95c9c059a613342de9d716cb69a6c76ab4e30a70a0141b0ecbfcb99193675b0366d0d391241e9aedcb8afd046259247ae46b180a09640383822a0d7f676ff2b22424243c07a3ea722acc415a6c43fed1f8bf994640ecd8163b28919dea579536eabca9c9957f6b953960b674a719632712956e202b13b80c6b1cbb7f2fc2b264c302e2243095e61a58824709068c4305a725ff002fece4ac302a92dca2c6ec8eccb03d24ea5806038bfc3feea6c8ceda328dad456ea09d6a1d2407af200ffcdd949e1ee71812d1b681ba253de36ff8d5b0080292a66cea3690d3c1d7fa63e1ad297d5264de350478a1a7f4c1e19470ac904c3e1903713fefc50dff001218f090b4a2aa884940aa18fc416aa0fcd3e28ffe170d9452a492a4b184951881b0e8f4ff0057ecb532624921ffd12c4ad37fa7e59a277cb97d3120ee298a151c4551c4efe18a45a97c153cfefc29568e95e9518b154a8e1b0f91c5429537eb4c5936a050d4e255cb5af88c21059179212cceb8b24f2309954fd5a1a3519c83bf25f87e04e5fdee5f82affa4e26aaf87c9975d3dcfaa03c7fbaab72351d3efc079b44690f63ea7a0ad37fbd3c6813fca25a8dfc9f0e4c72f34148b5712f3201d81f88d06ec38f1fb5d392ff00c3fa780b20912024c8010b1023d46dc866057ec86fb35ca8b7053937993d51420b98c2f526bf12ff00c4b10bd165bf2e4fc79730582914a94a0f8bfe0f2410548888151390ceb10a13fb5f1c9e8f3e8abb1c9b145693eba487d1e323721eaf0f85b9fc43dfe1e3f137fbaf24d53e4b7547d3da42b7090a4d5dda124c9fecf81ff89ae532a71881d4c50eb670984c916a0aa46e90b472927dbd454555c8d0ea58103a158ad7a281958f8107e1ff00929c700f26255d1af00354aa7ed5294fa78b53137fd1670e2fe92f47988358dc0f10415ff86e39304adf7ac9bd11f6f81f10680ffc2e1d9050a7ea27c54ff93538fa50ff00ffd9\";\n\n/**\n * Duck Matrix avatar with no EXIF information\n * 88x88\n */\nexport const NO_EXIF_JPG_HEX = \"ffd8ffdb008400080606070605080707070909080a0c140d0c0b0b0c1912130f141d1a1f1e1d1a1c1c20242e2720222c231c1c2837292c30313434341f27393d38323c2e333432010909090c0b0c180d0d1832211c213232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232ffc00011080058005803012200021101031101ffc401a20000010501010101010100000000000000000102030405060708090a0b100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9fa0100030101010101010101010000000000000102030405060708090a0b1100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00f39d2237f3223f398e3765047cb8c95ee0f1d6b73c66037852c2786479049772073d7e652ca09ef9da1067bedef597e15b382ffcd6b89248e08d1a57208c95040c73c67381fad6f6a176ff00f08d491adbdb5bc12cd3b24580f85405b71cf392463230319ae09de334cf16ab71aa9f6397b2bb824d34690d641e6ba789d67de4ec55dc08db8e739ffc77a77ad6f14c7e4e8f3a12c156ed157e5c06468f7af18e0ae48fc4d72d2cc069ab2c4e1255941054004647b723fce2afbea4b35bc534c91ce63456944a5fe7cf1cfcdea2b4941b69f6379d36e4a48c270c08638e4e79c1a78cb49c138dfcae00ae96d239b5dba8ec2db4eb37915c6c2a879382704e7a7afa558d6f4fb7d1960b696dedbce89dbed3322ee5dc73b541ce7181d7dcfb55f3f4ea5fb7d795ad4e6e20de747f7940751b94e0823dfd7393f8557946f676ddb882492ddfd4d74497da4b3208e21bd54b380a4ab10383c9fef13dbd2b2afedada1b62f1caccce0328ddca8c904118f507d288b77d8709b6f556330f2d96ce3a015bda8d9a4766080b1b9c30dc39202038f6ebfad610daad8dd93d2ba0d5ee20480c250b398c6370230c55727f43c7b83454bdd58755be68a464a320310192cbcb7bf3c7d2ad79c3fe790ffbec5500c1ee03e554f650381cf4ab7e637aaffdf34e511ca26ef871e38f4c2b72ec90dcc7246c40e87820f1fed0157ee58c1e1f8e1701fcfb7bb73701b2782c17278e4e31f8d67787525bab3b6b5891371903296c839049233d8607a7f2a97c617d6443450cafbc41e4f979cedc480e4e3f88e1f3e84d6325cd3b1c938f355e5f3ff339b1103a0ceed8c89230b93cf20f6ff3d2b4edec74a78205b9bc811c4232584ae33d7b0c743eb546403fe11e90855199a2048ebf75bfcfe745a45e749045bbefe130173d87f3ade5b5cea95da7adb5fd0f45d3751d3f4ad16eff00b25650af84fb43a005c842c4a8c7ca33dbdfd79a5b1d60ae9aaef70ce59fe645c640c77eb8f5ed54b50b29345f0d258c36acf700abf9dbb2a176307e339c92c3f2ac981e5b8d26178623b3cdd8caa48f9b1d40fcff004af1e718d6bcaf7573c274a15ef34ee9b23d62cac6fa39aeac24115c451ab02bf2994606e040ee3afe159fae5d68973046d60eff00687b687cc528c01942fef0e48ee467d39ae834ad2ede7d3cdc4c678eecec4822519121dca1b771c0c67b8fc78ae28c6ff69103e4183cc420f452303f9d77e167bc2f7b1e9611ef0bdec5265c3b1381c9ad2d5a4f308da98071839cf1b4553995448e064904d68eaa85d62906d0b21200ce4f0060febfa57549ea8ed93f7a2674118f35493d4f738ef5a1e5c7ea2a8daa05ba0092464638f715b388ff00baff0097ff005aa66f514e5a973c2d7690de69ee085114d997cb3c84dc324e781c1c0fcbbd67f8b12d12ee21668c525432f98c319dc49c019ec49ebcd47a6ca122910a9632945dc075f99491fa51ae4cb75696770a71b435bb26dc7cc9b4023eaa53f106a52b4ccd46d56e432b29f0dedd8438953e6f6c356cf83a289b534bc755cc4b84c91f78a63fa1ac09a50da394c6312263fef8357bc3b7820bd87cc70a98663ce390b91fd696222dd29242c44252a334ba9deea69fdadaed858c7ff1eed3624562402157241f6e2ae8f045b2ea4f7104b2a83f398a33f2ff00fab9e94786d22bbf10ea17330c24312c28ad93b778dcdd47a103f3adbbb92ce0b416af6eec8b242d12a9daa02306c75f415f3739384a34a32b7f57ff0023e5a729539468c25cbdfe7aff0091c668ccb040efd337b2246a4fdd507a7f21f8572dacc022d55e751b7ed11173df2df2e7fc7f1aded5266b5d64c31102d64b93329c1ea71b81ff003deb06eaf61943b48c15a3b726204fdf62ca08c7d39fcabd3c2425ed1545d51ebe0e12f69ed57da4624aaf966e0f3cfeb52dd2954419184381f2f3d05432c81d9f039dd525e3670dcfcc7238ed815eb3bdd1ec6ba1108df7a32fca4fcd9f6ab3be7ff9eaf4e28c90db3908721bd39edf851bff00d95ffbed6a1b6c4e4c8d09f29b24001b8c724d47733a3e9f1c25b748269246c0e06768fcfe53fa540642c140e0f5a85f00c9c9232467f1ab51d4d1435279189d373dbcdc0ffbe6b5bc3da59d46e115c95b645dccfbb1db381ee6b24465f4c8c2ae59a7238ea785c0fd6bd074cb78b4ed36ce348b6489bdda403efb6d1819faf4ed58e2eafb38596ece6c6d6f654da8eecdfb4bcd1747b4bab97b88fcc91cccf1abee21b0011d4ff7455af0f78874bf107da229191368f944bfc5e983eb9ae07c4897579abb2b4437f92dd0e78cb1e4f6ea063f0ad34b77b6d2e196097c989275dcc0fcb8cf07df91d3fc6bc6960a125cf277933c196029b5ed24ef264fabe971b6a0f34d231637281610782bb179cfae4f35c36b9118278d55711053b4eece7393f875aeb2d01934fb58be4dd34b82fdf3c6093ebcd50f12e9d35a68c84aab2994124804ed2a0a9cf6aedc34dc26a127e477e12aba7523093f238f0c431ee2ac5c1dc839c76e9ec2aae3939e2accdf740230700f1f415eabdd1ed496a89d9898d12304b1183c74e9f9537ecf71fdc6ff00bee9f02949c646723af07d2aef3ea3fef81fe358b958c5cb976326509bd4c671c66a13cc9202d9249008e879a9a28fcc915482d8c74f4f4fd29b2c47cc28000ccc7a718e6b54eda1b276d0d9d374c59921921bdda613e7b0d9feace40e4fe031f515d6a5fdc5c4762218484077361b710c1704e3fc79fc2b8101ed8c76e18b1620b2a92703d0d7576d7d7b234652297ca8711ab20c053c33738ea1413ea33cd7257829ee79f8aa7cf66f52cebe5a59b0a479e20e7202600c7279e7e503afb7b55b8b4d17761299278d48995941e8c4f619e9c8ed9eb5951debb089dd415bd690c8ef92480aa38c7beeff0022b46db599ed34d82285329f69655ce0b1c8c8cfbe4e7a0e9dfb72ba524924724a9ce315189b6be1f96c7c356b7df6a19494c8230be8aa7f9f1f8573b7c50e9f3aa157064390e3214f039ad0bbd4ef648058cb18110060f32318071cf5efc05ebea69935846da7cb6f05d7cdbbcc65765f987ca4120fa8dbd339352e9fbe9a308c1a92933ce6f23733190a0505413b5703a75a7dc6d65520a9278f93d38c1ae8d3435d56e21820956dd6e6421372ee0ad8395c819eddbd6b2752d352c228e179775cabb2b2e38e081c1eff00e7d89f4a3513b23db8568c9a8f51212f13a606ed8a7273db231f5ea2adfdaffd95fcab3e26de09dc49200faf200a93ca7f43fe7f1a9924dea4ca2ba9bda07832e6f744fedb9e4f2ac62c8760a4b37254e077e78fc863ad564d08ac575a82c53f90a5b0cc46460f53ff00d6f41eb5e91e1eff009230bff5d07fe94d611ff9136f3fedb7f4acab4dc76ea7155c4ce327eb6fc4c4b1d19934d6d42e1dff0079033e32b9298c76c93ce39c63247d6a16d1ee12de44123652356711b10199c16230072428231ffeaaddff0099661ffb0537fe8694f8fef5cffd744ffd024acf99dd99fb69ddbfeb4665da5919e7b7d309002c6b3b4c80b1562776d23db38ff0a8a08becd10bcf3e5024b860d2956dc0a052ac08e33b891dfaf6ad2d0ffe461baffaf71ffb2d55b9ff009142dffebea6ff00da7426f99af41a9be7b7a7e25f8352fece85a2f3ae64dea920287a000aed6f4ce0023dbad56b6be83c889bcd966b52de5323aab051d53195279c9c71fe150c9d67ff00ae6bff00a35ea869bff2094ffaf8b7ff00d0452b68d87b34937e65775bb8c491492b8b4998491859182ab60a838fa8c671e9cd634d1bca249b73b4a8db999c92d9cf27fc6ba3bfff008f3b3ffae51ffe8c35889d6fbfdd6ffd0ab7a6fa9d9464dab95ac7f7bbe4567f300195c71d6ae66e7fb8ff0098ff001aaba37df9ff000ad7aaa9a4ac6951da563fffd9\";\n\n/**\n * Jelly beans\n * 50x50\n */\nexport const JPG_RAW = \"\\xff\\xd8\\xff\\xe0\\x00\\x10\\x4a\\x46\\x49\\x46\\x00\\x01\\x01\\x01\\x00\\x48\\x00\\x48\\x00\\x00\\xff\\xdb\\x00\\x43\\x00\\x03\\x02\\x02\\x03\\x02\\x02\\x03\\x03\\x03\\x03\\x04\\x03\\x03\\x04\\x05\\x08\\x05\\x05\\x04\\x04\\x05\\x0a\\x07\\x07\\x06\\x08\\x0c\\x0a\\x0c\\x0c\\x0b\\x0a\\x0b\\x0b\\x0d\\x0e\\x12\\x10\\x0d\\x0e\\x11\\x0e\\x0b\\x0b\\x10\\x16\\x10\\x11\\x13\\x14\\x15\\x15\\x15\\x0c\\x0f\\x17\\x18\\x16\\x14\\x18\\x12\\x14\\x15\\x14\\xff\\xdb\\x00\\x43\\x01\\x03\\x04\\x04\\x05\\x04\\x05\\x09\\x05\\x05\\x09\\x14\\x0d\\x0b\\x0d\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\x14\\xff\\xc2\\x00\\x11\\x08\\x00\\x32\\x00\\x32\\x03\\x01\\x11\\x00\\x02\\x11\\x01\\x03\\x11\\x01\\xff\\xc4\\x00\\x1c\\x00\\x00\\x02\\x02\\x03\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x06\\x07\\x04\\x05\\x00\\x02\\x03\\x01\\x08\\xff\\xc4\\x00\\x1a\\x01\\x00\\x03\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x04\\x05\\x06\\x03\\x02\\x01\\x07\\xff\\xda\\x00\\x0c\\x03\\x01\\x00\\x02\\x10\\x03\\x10\\x00\\x00\\x01\\xe7\\x14\\xd4\\x6e\\x92\\x6e\\xe0\\x67\\xd3\\x01\\x2e\\xe2\\x61\\x94\\xcc\\xb8\\xa2\\x6a\\x9e\\xa0\\x8a\\x71\\xfa\\xde\\xdc\\x52\\xd3\\xd5\\x89\\x16\\xe1\\xbd\\x57\\x6b\\x4c\\xc9\\xc9\\x02\\xad\\xcd\\x3f\\x3d\\xe3\\x0e\\xfc\\xcd\\x61\\x95\\xe9\\x08\\xa8\\x74\\x05\\x4e\\xc3\\x14\\x07\\x5d\\x62\\xc5\\x22\\x57\\xb8\\x0f\\xbd\\x49\\xd9\\x50\\xd2\\x6b\\x46\\x3c\\xe3\\x8f\\xa1\\xd5\\x83\\x2d\\x27\\xa5\\xbd\\xf3\\x24\\x99\\x82\\x11\\xf5\\xd5\\xf3\\xf0\\x05\\x8f\\x34\\xbc\\x16\\xa5\\x83\\x00\\x74\\xaa\\x86\\x66\\xb4\\x8e\\xf2\\x89\\x13\\x88\\xd9\\xe2\\x16\\x56\\xe5\\xb3\\x4a\\x8c\\xc9\\xc9\\x61\\xa1\\x95\\xa5\\x1d\\x7e\\xb8\\xa8\\x67\\x65\\xf3\\xbd\\xa4\\xa7\\xff\\xc4\\x00\\x21\\x10\\x00\\x02\\x03\\x00\\x02\\x02\\x02\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x04\\x01\\x02\\x05\\x00\\x06\\x11\\x12\\x13\\x14\\x16\\x21\\x24\\xff\\xda\\x00\\x08\\x01\\x01\\x00\\x01\\x05\\x02\\xd9\\xa5\\x49\\x5a\\x1d\\x6a\\x39\\xf8\\xfa\\xd5\\x03\\x98\\xe6\\x61\\x44\\x16\\xa2\\xc9\\x82\\xb4\\xbc\\x97\\xae\\x9e\\xe4\\x63\\xec\\x33\\xd6\\x24\\x4b\\x29\\xa4\\x98\\xad\\x5c\\x4a\\x68\\xae\\x4e\\x5e\\x41\\x3c\\x3f\\xc8\\x20\\x0c\\x4b\\xc8\\xd4\\x42\\x34\\xb8\\x7c\\x2f\\x3a\\xd1\\xea\\x3a\\x36\\x19\\xf4\\x4d\\x42\\xd9\\x41\\xe9\\x02\\xc5\\x8d\\xbc\\xd1\\x45\\xeb\\xf5\\x12\\xcc\\xb3\\x9b\\xc0\\x8b\\x17\\x23\\x99\\x8e\\xad\\x9a\\x67\\xb7\\x46\\x65\\x35\\xd6\\xa5\\x9c\\x81\\xf8\\x8b\\x5e\\x9f\\x2e\\x83\\x4c\\x35\\xcd\\xb3\\x83\\x45\\x1c\\xe1\\xd9\\x85\\x35\\xf3\\x1c\\xd2\\x73\\xad\\x3a\\x1a\\xbd\\x67\\x87\\xed\\x89\\x96\\x96\\x79\\xb6\\x11\\x45\\x9c\\x9e\\xb3\\x03\\x9d\\x0c\\x77\\x06\\xf2\\xb8\\x9b\\x9f\\x0b\\x5a\\x42\\xaa\\x4b\\x58\\xa4\\xf6\\x14\\x45\\xf4\\x3b\\x1d\\x2a\\x4c\\x6c\\xe5\\x82\\xba\\x7d\\x8f\\xf9\\x91\\xcf\\xa5\\x46\\xa6\\xbf\\xeb\\x12\\x0d\\x7f\\x1f\\xff\\xc4\\x00\\x2f\\x11\\x00\\x01\\x04\\x01\\x02\\x03\\x06\\x04\\x07\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x02\\x03\\x04\\x11\\x12\\x31\\x05\\x13\\x21\\x14\\x22\\x41\\x51\\xa1\\xf0\\x32\\x42\\x71\\x81\\x23\\x24\\x52\\x53\\x61\\x91\\xf1\\xff\\xda\\x00\\x08\\x01\\x03\\x01\\x01\\x3f\\x01\\xbf\\x0e\\x98\\xc3\\xc0\\xfb\\xa1\\x19\\x71\\xca\\xa8\\x5b\\x5d\\xed\\x73\\xd3\\x38\\xe3\\x98\\xf7\\x35\\xad\\x1a\\x73\\xfc\\xa7\\xcf\\xda\\x7b\\xce\\xf1\\x4d\\xb0\\x21\\x69\\x0d\\x0b\\x4b\\xcf\\xce\\x54\\x5f\\x98\\xac\\x1b\\x28\\xdf\\x1f\\xd6\\x56\\x98\\x23\\x9a\\x37\\x39\\xbd\\x4e\\xde\\xfd\\x17\\x16\\xae\\xd7\\x5a\\xdf\\xa1\\xea\\x9d\\x0c\\x6d\\x6e\\x85\\x2b\\x66\\x2d\\x0d\\x8c\\x74\\x5c\\x2a\\x06\\x13\\x87\\x0c\\xe1\\x3d\\xa0\\x38\\x85\\x74\\x4d\\x5a\\x00\\xe8\\xc6\\xdf\\x4f\\xa2\\xb1\\x62\\x6b\\x04\\x03\\xd7\\xc9\\x43\\x34\\x92\\x3f\\x4b\\xdd\\x9c\\xa6\\x70\\xfa\\xad\\xf9\\x7b\\xde\\x6a\\xd5\\xc8\\xe0\\x7e\\x83\\xb8\\x50\\xdb\\x9f\\x99\\xad\\x87\\x01\\x3a\\xce\\xb7\\x17\\x13\\xba\\x3a\\x2d\\xf0\\xf1\\x8d\\xb4\\xfa\\xff\\x00\\xaa\\x0a\\xd6\\x2a\\x1e\\x7e\\x94\\x6a\\x56\\xbc\\xe6\\xcf\\x19\\xeb\\xef\\xd5\\x5d\\x12\\x4b\\x16\\x23\\x2a\\xd5\\x52\\x23\\x2e\\x27\\x38\\x5c\\x3e\\x28\\xa5\\x92\\x31\\x27\\xc3\\xe2\\xb1\\x18\\xe8\\x1a\\x14\\x1c\\x4a\\x0a\\xf0\\xb7\\x4e\\xe0\\x6c\\xac\\xf1\\x37\\xd8\\xee\\xe3\\xba\\x9c\\x49\\x87\\x9b\\x0e\\xe3\\x65\\xda\\x6c\\xb2\\x42\\xf2\\xed\\xf7\\x54\\xa4\\x80\\xc4\\xd9\\x2c\\x63\\x27\\xcf\\x65\\xc4\\xed\\x40\\xf7\\x35\\x91\\xbb\\xa8\\x59\\x72\\xbf\\x15\\x53\\x8e\\x46\\xfe\\xfe\\xcb\\x46\\x32\\x15\\x2c\\xb4\\x38\\x65\\x5c\\x8f\\x97\\x28\\x78\\xf1\\xea\\xa4\\xa3\\xda\\x86\\xac\\xe1\\x4b\\xc3\\xa1\\x90\\x08\\x88\\xc1\\xf3\\x43\\x81\\xd2\\xfd\\xc3\\xe8\\x9d\\xf1\\x39\\x40\\x01\\x99\\x80\\xf9\\x85\\x60\\x72\\xec\\x38\\x33\\xa7\\x54\\xd3\\xf8\\x79\\x55\\x09\\x70\\x76\\xa5\\x6f\\x67\\x95\\xce\\x93\\xf5\\x15\\xff\\xc4\\x00\\x2f\\x11\\x00\\x01\\x03\\x02\\x03\\x06\\x05\\x03\\x05\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x02\\x03\\x04\\x21\\x11\\x13\\x31\\x05\\x12\\x22\\x41\\x51\\x61\\x71\\xa1\\xb1\\xc1\\xf0\\x23\\x32\\xd1\\x33\\x81\\x91\\xe1\\xf1\\xff\\xda\\x00\\x08\\x01\\x02\\x01\\x01\\x3f\\x01\\xd9\\x04\\xef\\x90\\x2c\\x3d\\x4e\\x1e\\xca\\xb2\\x67\\xc2\\xc0\\x19\\x70\\xa4\\x95\\xc6\\x1e\\x23\\x74\\xd9\\xef\\x8b\\xee\\x70\\xbe\\x9a\\x76\\xfc\\x2a\\x9a\\x82\\xe9\\x4e\\x04\\x80\\xb3\\x09\\xb3\\x8e\\x3e\\x3e\\x48\\x6d\\x9d\\xdb\\x64\\xb7\\xf8\\x51\\xc2\\xd8\\x2b\\xfe\\x96\\x83\\x1f\\x45\\x0b\\xf8\\xae\\xab\\x33\\x62\\x95\\xed\\x6d\\xef\\xeb\\xa7\\x9d\\x94\\x59\\xd2\\xcb\\x8b\\xec\\x42\\x73\\x23\\x67\\x2f\\x9c\\x96\\xc9\\xa5\\x8a\\x79\\x1c\\x65\\x18\\xf0\\xd9\\x09\\x48\\x18\\x0f\\x45\\x43\\x4d\\x9b\\x2b\\xa4\\x71\\xd7\\x45\\x2e\\xfc\\x2e\\x31\\xcc\\x6c\\x54\\xad\\x8a\\x30\\x66\\x2d\\xd1\\x06\\xc4\\x00\\x99\\xfa\\x15\\x56\\x59\\x39\\x73\\xe0\\x1c\\x3e\\xea\\x16\\xcf\\x01\\xce\\x88\\x1b\\xe9\\xd1\\x3f\\x67\\x4a\\xf7\\x17\\x11\\xaa\\x86\\x4d\\xe3\\xbc\\x3a\\xa9\\xaa\\x29\\x6a\\x1c\\x23\\x79\\xfd\\xd5\\x7d\\x29\\x7c\\x6e\\x11\\x1c\\x07\\x44\\xfa\\x82\\xfd\\xd1\\x26\\x16\\xeb\\xa2\\x64\\x9c\\x43\\x28\\x6e\\xe2\\xa1\\x9c\\xc4\\xe9\\x1b\\x25\\xc0\\xbf\\x6d\\x7c\\xb5\\x42\\x68\\xb0\\xfb\\xbd\\x11\\x95\\xad\\xa7\\xc8\\xe6\\xa9\\xa9\\x44\\xd8\\x1c\\x78\\x8f\\x92\\xa7\\x7e\\x55\\x66\\x4c\\xda\\x1d\\x7a\\x76\\xf9\\xe2\\xa4\\xa3\\x8a\\x46\\x6e\\x39\\xbf\\x31\\x54\\xf1\\x06\\xcd\\x23\\x59\\x89\\x23\\x9f\\x8f\\xf7\\x8e\\x2a\\xbd\\x8d\\x8f\\x8d\\xd6\\xd2\\xc8\\xe4\\x63\\xa9\\x55\\x55\\x70\\xc8\\x04\\xad\\x07\\xba\\xa6\\xaa\\x19\\x8d\\x7b\\x6c\\xb6\\xa3\\x9b\\x23\\x98\\xe0\\xdf\\x9e\\xfa\\xaa\\x08\\xc1\\xa7\\x11\\xbd\\x45\\x3c\\x9b\\x29\\xfb\\xae\\x18\\xa3\\x2b\\xaa\\x5f\\xbe\\xe3\\xa9\\xd3\\xb7\\xfa\\x8e\\xcd\\x18\\xd9\\x3b\\x90\\xef\\xf9\\x54\\xc4\\x87\\x38\\x8e\\x40\\xa9\\x1e\\xf7\\x81\\xbc\\x71\\xb7\\xb2\\xd9\\xd2\\xbc\\xd8\\xb9\\x48\\xf7\\x1a\\x9b\\x9e\\xbe\\xaa\\x8f\\xf5\\x23\\xf1\\xf7\\x47\\x55\\xff\\xc4\\x00\\x2f\\x10\\x00\\x02\\x01\\x03\\x02\\x03\\x07\\x02\\x07\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x02\\x03\\x00\\x04\\x11\\x12\\x21\\x13\\x22\\x31\\x23\\x32\\x41\\x51\\x61\\x71\\x91\\x33\\xf0\\x05\\x15\\x42\\x52\\x72\\x81\\xb1\\xa1\\xff\\xda\\x00\\x08\\x01\\x01\\x00\\x06\\x3f\\x02\\x4d\\x71\\xf2\\xc8\\x74\\x97\\x23\\x97\\xd0\\x1f\\x9a\\x4b\\x62\\x0e\\x95\\x50\\x4c\\x63\\x65\\xd2\\x69\\xa5\\xe0\\x17\\x94\\x12\\xe3\\x9b\\x53\\x1f\\x21\\x4b\\x24\\x7a\\x56\\xed\\x54\\x1d\\x12\\xf4\\xff\\x00\\x95\\x1c\\x57\\x3c\\x27\\x98\\x13\\x92\\x17\\x6c\\xe6\\x99\\x63\\x0a\\x90\\xb0\\xea\\xa3\\x07\\x39\\xde\\x9d\\xbf\\x36\\xbb\\x19\\x39\\xc6\\x05\\x42\\x67\\x71\\x3c\\x93\\x91\\xac\\x28\\xdb\\x3a\\xaa\\x07\\xe1\\x2c\\x4c\\xd9\\x8f\\x5f\\x4e\\xa3\\x1b\\x8a\\x33\\x63\\xe8\\xe7\\x58\\xf4\\xf3\\xf8\\xa8\\x73\\x20\\x3a\\xfa\\x11\\xb8\\xac\\xb3\\xa9\\x45\\xc9\\x5e\\x7d\\xab\\x07\\x94\\xe0\\xb0\\x22\\x97\\x55\\xc2\\x83\\x8d\\xc6\\xa1\\x56\\xb6\\x82\\xec\\xdb\\x88\\x70\\x57\\x4c\\x7f\\x53\\x02\\xb8\\xd7\\x40\\xf6\\x03\\x66\\x3d\\xd6\\xf5\\xac\\x22\\x95\\x4e\\xb8\\x5e\\x95\\xa9\\x53\\x97\\x57\\xd3\\x45\\xf5\\xa6\\x78\\xad\\x9c\\xc5\\xfb\\x54\\xe9\\xfe\\xfd\\x29\\xa0\\x96\\xe9\\x4d\\xc4\\x6b\\xd3\\xc4\\x7b\\xd0\\x4f\\xdb\\xb6\\xf4\\x62\\x90\\x88\\xe5\\x83\\x1d\\x5b\\x1c\\xc3\\xd6\\x83\\x7e\\x22\\xdc\\x18\\x50\\x03\\x11\\x55\\xc3\\xb1\\xf3\\x7f\\xb1\\x53\\x76\\x41\\xb3\\xe6\\xdd\\x3c\\x32\\x2a\\x49\\xa6\\xb7\\x6b\\xce\\x1a\\xe1\\x44\\x60\\x64\\x93\\xef\\x5d\\x94\\x26\\xd2\\x72\\x46\\x1c\\x91\\x81\\xbf\\xd8\\xa5\\x62\\x07\\x1e\\xe0\\xe0\\xe8\\x18\\xd7\\x81\\x9a\\xe8\\xc3\\xd3\\x02\\x96\\x6e\\x49\\x84\\x8e\\x18\\x37\\x9f\\xf7\\x49\\xca\\xca\\x9a\\x87\\x67\\xe0\\x7d\\xea\\xde\\xe4\\x8d\\x06\\x16\\xd5\\x86\\x3d\\xdf\\x03\\x51\\x49\\x12\\x34\\x90\\xe7\\x02\\x42\\xa7\\x07\\xcf\\x7a\\x71\\x15\\xbb\\x5c\\x5a\\xa0\\x05\\x94\\x0c\\x8c\\xef\\xb9\\xfb\\xf0\\xa2\\xef\\x6c\\xd2\\x28\\xe5\\x17\\x19\\xc2\\xc4\\xd8\\xe9\\x83\\xd7\\x3b\\x6e\\x28\\xf6\\xac\\x3d\\xa8\\x8b\\xbb\\x6e\\xcc\\x6e\\x22\\xea\\x99\\xf3\\xa7\\x9a\\x2b\\x45\\x8d\\x8a\\xe5\\x4a\\xae\\x9c\\x7c\\x55\\xd6\\x26\\x26\\x44\\x8b\\x05\\x41\\xca\\x82\\x4f\\xfb\\xb0\\xa9\\x97\\xaa\\x89\\x0c\\x40\\x8d\\xb4\\x8f\\xbc\\xd4\\xb1\\xc2\\x99\\xfd\\x32\\x06\\x3f\\x18\\xab\\xbb\\x9b\\x7d\\x1c\\x69\\x4e\\xa7\\x8c\\xf7\\x4e\\xfb\\xd1\\xc4\\x7b\\x7b\\x1a\\x80\\x1d\\xc7\\x1b\\xa1\\xfe\\x55\\x74\\xac\\x03\\x29\\x2a\\x30\\x7f\\x98\\xa6\\x68\\xa2\\x48\\xd8\\xb9\\xc9\\x45\\xc7\\xeb\\xa9\\x38\\x5d\\x96\\x48\\xee\\x6d\\xe1\\x50\\xba\\xa8\\x57\\x6d\\x1a\\x98\\x0d\\xcf\\x66\\xb5\\x70\\x06\\xc3\\x82\\xdf\\xe5\\x77\\xdb\\xe6\\xbf\\xff\\xc4\\x00\\x22\\x10\\x01\\x00\\x02\\x02\\x02\\x03\\x00\\x03\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x11\\x21\\x00\\x31\\x41\\x51\\x61\\x71\\x81\\x91\\xa1\\xb1\\xc1\\xf0\\xff\\xda\\x00\\x08\\x01\\x01\\x00\\x01\\x3f\\x21\\x10\\x32\\xcf\\xff\\x00\\x55\\x6d\\x07\\xd7\\x39\\x55\\x4c\\x4f\\x10\\x53\\x39\\x7d\\x17\\x25\\x21\\xb4\\x98\\xf1\\x0e\\xb7\\xde\\x3e\\x30\\xd6\\x2b\\xcd\\xeb\\x1f\\x75\\x91\\x1d\\x35\\x56\\x4e\\xce\\x9c\\x8a\\x38\\xb5\\xaa\\x29\\xa9\\xa2\\xf0\\x19\\x37\\xe0\\xef\\x5a\\xc4\\x09\\x6c\\x1f\\x40\\xf9\\x13\\xf7\\x26\\x8e\\x52\\xf0\\x50\\x51\\xb9\\x8b\\xc0\\xb0\\x23\\xe9\\x2f\\xda\\xdc\\x6f\\x1d\\x93\\x08\\x40\\xa5\\xdc\\xf5\\x94\\xb2\\x20\\x4b\\x48\\xee\\x3b\\xc1\\x3a\\x59\\x1a\\x13\\x5c\\xfa\\xe3\\x1c\\xd5\\x99\\x0a\\x1c\\x9a\\xc2\\xb0\\x09\\x24\\xdd\\x77\\xee\\x30\\x2c\\xeb\\x99\\x13\\x50\\x0e\\x89\\xdc\\x34\\x67\\x1f\\x94\\xb8\\xf6\\x4e\\xb2\\x5e\\x0d\\x8c\\x21\\xf0\\x0f\\xb5\\x8e\\x6c\\xa2\\x01\\x86\\x9e\\xd8\\x24\\xc4\\x93\\x28\\xd0\\xa3\\x9a\\x7f\\x78\\x4d\\x28\\xc6\\x04\\xd6\\x21\\xd1\\x28\\x94\\x1c\\xcf\\xd1\\x33\\xcd\\x63\\x8b\\x38\\x94\\xea\\xce\\x0a\\x88\\x2c\\xde\\xae\\x12\\x19\\x8c\\x3c\\x38\\x17\\x61\\xeb\\x23\\x79\\x0d\\x51\\x5c\\x40\\xfa\\xb8\\x72\\x01\\xe3\\x03\\x0b\\xee\\x78\\x79\\x3c\\x60\\x5b\\xb8\\xe4\\x38\\xb7\\xbd\\x18\\x20\\x57\\x3c\\x3c\\x4c\\xa8\\x62\\xd5\\xeb\\x97\\x00\\x63\\xc0\\x83\\xd1\\xdd\\xa8\\x5b\\xd7\\xdc\\x74\\x8c\\xc8\\x4f\\x0a\\x9b\\x89\\x3f\\xe7\\x2b\\xbc\\x16\\x68\\x95\\x23\\x86\\xce\\x67\\x22\\x49\\x35\\xad\\xc1\\xf7\\x5f\\xe3\\x2a\\x6b\\xe4\\x21\\x26\\x44\\xc1\\xa1\\x5e\\x9c\\xa6\\x12\\x75\\x08\\x32\\xad\\xfb\\xe2\\x54\\x02\\xdd\\x3a\\xc8\\x0c\\x7c\\x9e\\x25\\xe5\\x1b\\x3e\\x62\\xb8\\x63\\x6a\\x26\\x6d\\x04\\x7e\\x26\\x74\\x61\\xae\\x21\\x26\\xfb\\xa2\\x3b\\x76\\x99\\xeb\\x0a\\xb8\\x2d\\x22\\xad\\x2e\\x77\\xfd\\xcd\\x46\\x8c\\xa9\\x59\\x47\\x71\\x36\\x73\\x18\\x9b\\x39\\xd7\\x56\\x08\\x63\\x91\\xb0\\x4a\\xe3\\x17\\x08\\x72\\x26\\x98\\xc1\\x6b\\xe0\\x89\\x88\\x12\\x98\\x99\\x5d\\x87\\x2e\\xdc\\x64\\xf7\\xad\\x05\\x6f\\x6f\\x36\\xbf\\x9c\\x4b\\x0e\\x0c\\xf6\\xc4\\x8f\\xe8\\xcf\\xff\\xda\\x00\\x0c\\x03\\x01\\x00\\x02\\x00\\x03\\x00\\x00\\x00\\x10\\x89\\x4a\\x5d\\xe3\\xb4\\xf3\\x74\\xe6\\x50\\xee\\x11\\x27\\xec\\x65\\x8f\\xfc\\x21\\x26\\x5f\\xff\\xc4\\x00\\x23\\x11\\x01\\x00\\x02\\x02\\x02\\x02\\x02\\x03\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x11\\x21\\x00\\x31\\x41\\x51\\x61\\x71\\x81\\x91\\xa1\\xb1\\xc1\\xd1\\xf0\\xff\\xda\\x00\\x08\\x01\\x03\\x01\\x01\\x3f\\x10\\x69\\xa6\\x38\\x70\\x4d\\x4f\\x85\\x62\\x78\\xd6\\x40\\x85\\xbf\\x37\\xff\\x00\\x7f\\x98\\x74\\x08\\x6d\\x78\\x9d\\xa4\\xd4\\x9c\\x71\\x3f\\x78\\xb9\\x0e\\x8e\\xdb\\xda\\xcd\\xc9\\xb8\\x0f\\x9c\\x59\\x47\\x22\\xce\\x2b\\x5b\\x9e\\xb9\\xe6\\xfc\\x61\\x66\\x9d\\x91\\x11\\x3a\\x67\\xc6\\xb4\\x78\\xf3\\x8b\\x25\\xfb\\x1c\\x04\\xb8\\xd8\\x95\\x60\\xbf\\x47\\xcd\\x6b\\x13\\xa0\\x74\\x44\\x1a\\x0a\\xaa\\x46\\x4b\\x32\\x48\\x69\\xc7\\x11\\x1e\\xa7\\x7b\\xf9\\x91\\xc1\\x43\\x4d\\xf9\\x9e\\xfe\\x71\\xf1\\x4b\\x70\\x7f\\x7a\\xc4\\x00\\xa0\\xb2\\x77\\x32\\xfe\\x0f\\x8f\\x38\\x0c\\x26\\x17\\x9f\\x39\\x11\\xc2\\x33\\x74\\x22\\x12\\x6d\\x99\\xd1\\xad\\xae\\x2d\\x92\\x28\\x07\\x6f\\x07\\x6b\\xf2\\xd7\\x46\\x18\\x56\\x89\\x6d\\x9e\\xa5\\xb8\\xfd\\x7a\\xc1\\x79\\x02\\x64\\xb1\\x31\\xc8\\xc1\\x1d\\x46\\xfe\\x63\\x08\\x5a\\x57\\x05\\x5d\\x84\\xce\\xef\\x67\\x67\\xa3\\xca\\x0d\\xd4\\x24\\xcc\\x5d\\xfa\\xe9\\x86\\x9c\\x2b\\x91\\x2e\\xbb\\xbc\\x14\\x35\\x5d\\x9a\\x01\\x2a\\xbd\\xc3\\x9f\\x9a\\x9c\\xa6\\x10\\x0c\\x8b\\x69\\xb7\\xd3\\x06\\xb7\\xba\\xee\\x00\\x9c\\xd8\\xd9\\xd3\\x14\\x03\\xef\\xd3\\x38\\xdd\\x42\\xb2\\x93\\xc1\\xc1\\xd4\\xee\\x28\\xfc\\x61\\xee\\x70\\x9e\\xcf\\x9c\\xae\\x44\\xa4\\x98\\xb8\\x5d\\x9c\\x4c\\x4e\\x98\\xe7\\x00\\xd3\\x9a\\xde\\xbe\\xb0\\xa2\\x44\\x04\\x09\\x69\\x0b\\x36\\x6e\\xe6\\x65\\x83\\x58\\x26\\x9e\\x64\\xca\\xdf\\x2d\\x57\\x8f\\xb9\\x6f\\x24\\x6c\\x04\\xa2\\x93\\x84\\xfd\\xf8\\xd4\\x60\\x9a\\x8c\\x38\\x43\\x14\\x49\\xe0\\xc7\\x60\\x40\\x02\\x25\\x0b\\x71\\xea\\x12\\x64\\x24\\xe6\\xf0\\xa4\\x59\\x61\\x51\\xc5\\xf7\\xf6\\x79\\x19\\xc2\\x03\\x58\\xc2\\x81\\x3b\\x01\\x38\\xe4\\x88\\x3d\\x44\\x6f\\x1b\\x98\\xc9\\x92\\x92\\x7a\\x3b\\xf4\\xd1\\x8c\\x8a\\xc0\\x2b\\xf1\\xee\\xa7\\x8f\\xee\\x18\\xa2\\x0d\\x31\\x76\\x0c\\x47\\xf2\\x69\\x36\\xe4\\x0e\\x88\\x0d\\x1d\\x93\\x5e\\xa7\\x7c\\xc4\\x39\\x04\\x91\\x7f\\xe7\\x59\\xb3\\xe3\\xf8\\x61\\x29\\x23\\x21\\xdd\\xf3\\x84\\x1a\\x1c\\x0a\\x3f\\x18\\x88\\x6d\\x85\\x3c\\x9c\\x56\\x4a\\xe9\\xa3\\x77\\xd6\\x10\\x36\\xe1\\xfd\\x64\\xbf\\xe8\\xe7\\xff\\xc4\\x00\\x24\\x11\\x01\\x01\\x00\\x02\\x02\\x02\\x01\\x04\\x03\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x11\\x21\\x31\\x00\\x41\\x51\\x61\\x71\\x81\\x91\\xb1\\xc1\\xa1\\xd1\\xf1\\xe1\\xff\\xda\\x00\\x08\\x01\\x02\\x01\\x01\\x3f\\x10\\xd7\\xa7\\x57\\x3d\\x84\\x75\\x41\\x50\\xae\\x6b\\xe7\\x8e\\x43\\x43\\xe5\\x3b\\xff\\x00\\x9f\\x7e\\x68\\xbc\\x16\\x48\\xbe\\xe6\\xaf\\x67\\x7d\\x78\\xe2\\x89\\x90\\xa0\\x53\\x16\\x3a\\x80\\x1b\\x59\\xc2\\xdd\\x06\\x5b\\x21\\x0b\\xa2\\x02\\xa1\\x8c\\xef\\x1d\\x6f\\xcb\\x98\\x8a\\x0b\\x93\\x8c\\x3d\\x9c\\xb5\\xdb\\x0d\\xda\\xc2\\x20\\xe0\\xc7\\x03\\x46\\x8d\\x02\\xdc\\x88\\xef\\x76\\xbf\\x4c\\x57\\x9e\\xd8\\x3c\\xf5\\x44\\xbe\\xa7\\xfb\\xc0\\x0b\\x48\\x65\\xf0\\xfe\\x19\\x6c\\x4c\\x75\\xc1\\x70\\xe0\\x22\\x42\\x6e\\x47\\x53\\xbf\\x1d\\xf8\\x40\\x99\\xb1\\x95\\x73\\x83\\xcf\\xdd\\xc1\\x98\\x0e\\x39\\x17\\xcc\\x83\\xa6\\xbb\\x9a\\x67\\xee\\xf8\\xe0\\x44\\x00\\xeb\\xfd\\x73\\x0a\\x4d\\xa9\\x17\\x2b\\x69\\xd7\\xdf\\x63\\x21\\x78\\x88\\xe4\\x17\\x1b\\x2e\\x3a\\xa6\\x97\\xb3\\x13\\xbe\\x04\\x46\\x2d\\xe8\\xf1\\x83\\x67\\xae\\xf1\\xee\\xa3\\xb2\\x0b\\x64\\xcf\\xbf\\xae\\x2f\\xc7\\x1a\\xb3\\x19\\x60\\xe1\\x73\\x82\\x4d\\x1d\\x84\\xcf\\x28\\x89\\x91\\x03\\x95\\x14\\xf8\\x83\\x77\\xf7\\xc8\\x99\\xea\\x57\\x33\\x2e\\x74\\x10\\xf8\\x38\\x62\\x58\\xe9\\xeb\\x52\\x75\\x8a\\x43\\xe3\\x61\\xc7\\xce\\xa2\\x40\\x58\\xac\\x2b\\xe2\\xc2\\x98\\xa9\\x9a\\x50\\xbb\\xca\\xb0\\x4c\\x56\\x9a\\x8d\\x77\\x93\\x4c\\xa7\\x10\\x14\\x6c\\xec\\xf6\\x91\\xd4\\x4e\\xf3\\x31\\xc6\\x1d\\xc2\\xdd\\xc2\\x76\\xa4\\xea\\x77\\xae\\xf8\\xb7\\xd2\\x04\\xc0\\xa0\\xa1\\xe5\\xb7\\x5b\\xc7\\x7c\\x70\\x7f\\x3e\\x60\\x54\\xef\\x48\\xb9\\xcd\\x7d\\xe6\\x9e\\xf5\\xc3\\x46\\x51\\x2b\\xac\\x1c\\x86\\x72\\x4d\\xfd\\x08\\x71\\x1c\\xed\\x00\\xe5\\x62\\xaa\\xe7\\xa3\\xda\\xa9\\x3a\\xe6\\x63\\xa2\\xc9\\x66\\x6b\\x0f\\xe9\\xf7\\xcb\\x80\\x6b\\x83\\x80\\x98\\x3a\\xb1\\x82\\x36\\x9a\\x67\\x14\\xa4\\x25\\x13\\x2e\\x58\\xe0\\xd0\\xd6\\x2f\\x56\\x50\\xa2\\xd3\\xee\\x1f\\xd7\\x03\\x71\\xcc\\x66\\x78\\x1a\\xec\\xcc\\x62\\xf5\\x71\\xc9\\x6f\\x24\\x75\\x07\\x66\\x8c\\x7e\\x3e\\xb7\\x84\\x88\\xc3\\x7c\\xa9\\x02\\x47\\x02\\xb7\\xe4\\x9b\\x78\\x02\\xe1\\x11\\x2f\\x4a\\xfe\\x9d\\x7d\\x39\\x8c\\x7b\\xef\\x60\\xb1\\xbd\\x4e\\xf1\\x1b\\xa3\\x7c\\xa8\\xa9\\x24\\x14\\xab\\x37\\x8c\\x46\\x44\\x42\\xa6\\x6b\\xcb\\xd4\\xce\\x3c\\x75\\x2b\\xf9\\x6b\\xef\\xc4\\x0a\\x28\\x1e\\xcc\\x3a\\xf1\\xc5\\x67\\x46\\x15\\x59\\x72\\x97\\x5c\\x96\\xe4\\xb2\\x56\\x4f\\x1c\\x39\\x56\\x31\\x97\\x19\\x9f\\x8c\\x71\\xa9\\xaf\\xf9\\xc3\\x98\\x20\\xe7\\xff\\xc4\\x00\\x1f\\x10\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x02\\x03\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x11\\x21\\x00\\x31\\x41\\x51\\x61\\x71\\x81\\xd1\\x91\\xff\\xda\\x00\\x08\\x01\\x01\\x00\\x01\\x3f\\x10\\xca\\x95\\xf8\\x23\\xa1\\xff\\x00\\xa4\\x51\\xaa\\x3a\\xfa\\xba\\x8a\\x07\\x12\\x09\\xa8\\x42\\x89\\x66\\x0b\\x78\\x98\\x94\\xbb\\x7a\\x81\\x41\\xf3\\x68\\x54\\xa4\\x3f\\x3f\\x99\\xc1\\xac\\x14\\x9f\\xe5\\xc9\\xa2\\x63\\xd4\\x20\\x1b\\x60\\x03\\x7e\\x78\\x61\\xc5\\x9d\\xc9\\x6c\\xce\\x48\\x04\\x84\\xb7\\x5f\\x9a\\x7d\\x70\\x8f\\xbd\\x3f\\xd3\\x67\\x03\\x79\\x27\\x33\\x40\\x32\\x80\\x81\\x2e\\x33\\xcb\\xb5\\xa4\\xcd\\x43\\x4a\\x92\\x3e\\x10\\x97\\xe1\\xaa\\xf3\\x20\\x83\\x66\\x0b\\x16\\x9a\\x80\\x3d\\xec\\x0c\\x91\\xb3\\x44\\xe8\\x5b\\x0b\\x8d\\x1f\\x15\\xe0\\x07\\xc3\\x51\\x49\\x28\\x68\\x37\\x84\\xfc\\xfd\\x79\\x70\\x08\\x93\\xb5\\x3c\\x31\\x4b\\xf5\\x4c\\xf5\\xe7\\x5a\\x4a\\x54\\x8a\\x27\\xc4\\x7e\\x72\\x15\\xef\\x43\\x85\\x70\\x3f\\x25\\x2a\\x81\\x64\\x77\\x7f\\xa5\\x88\\xd3\\x25\\xd2\\x8a\\x07\\x81\\xcf\\xe8\\x65\\xa6\\xd2\\x16\\x0a\\x8e\\x3f\\x5f\\x97\\x84\\xd5\\x81\\x42\\x90\\x9f\\x1c\\xac\\x10\\x3c\\x87\\x7a\\x81\\xe8\\x2e\\x58\\x00\\x97\\xc1\\xbb\\x76\\xf0\\x0e\\xf9\\x41\\x10\\x80\\x53\\x24\\x8d\\xf8\\x8f\\x36\\x34\\x8d\\x9c\\x21\\x6b\\x6e\\x7d\\xde\\x42\\x2f\\x58\\x58\\x0d\\x08\\x88\\x44\\x6b\\xc5\\x38\\x44\\x28\\x0a\\x56\\xb5\\x1d\\xdb\\x2a\\xe0\\x71\\x40\\xe6\\xd7\\x40\\x8d\\xc4\\xa0\\x02\\xe2\\x8a\\xa0\\xf9\\x1b\\x8f\\x96\\x33\\x41\\x0a\\x26\\xbc\\x2a\\xfb\\x8e\\x25\\x08\\xf0\\xbf\\xb3\\xed\\xca\\x89\\x8f\\x41\\xa8\\x18\\xda\\x07\\xd8\\x1a\\x88\\xe6\\x60\\x11\\x2f\\xd3\\x5b\\x9f\\xbd\\xe6\\x98\\x60\\x8c\\xc5\\x3e\\x18\\x03\\x63\\x3d\\x87\\x04\\x50\\x82\\x8a\\xc5\\x70\\x53\\x01\\x29\\x9e\\x1d\\xec\\xd3\\x64\\x68\\xc0\\x80\\x91\\x5f\\xc0\\xe0\\x04\\xab\\x86\\x18\\x84\\x04\\x92\\xfa\\x1a\\x8f\\xa4\\xe9\\xbb\\x42\\xa6\\xa8\\xfe\\xd0\\x1a\\x15\\x21\\x73\\x6e\\xd0\\x53\\x3c\\x60\\x00\\x28\\x86\\x5a\\xf3\\x95\\xfb\\x8c\\x82\\xf8\\x33\\xce\\x7e\\xee\\x61\\xdf\\x91\\x84\\x0f\\x45\\xf4\\xbe\\x5e\\x97\\xcc\\x8b\\x06\\xca\\xc1\\x34\\xa5\\x65\\xe9\\x16\\xd6\\x05\\xda\\x00\\xc4\\x11\\x49\\x27\\x0c\\x3a\\xaa\\x84\\x8a\\x5a\\x11\\x70\\x81\\xf5\\x84\\xe5\\x4c\\x1d\\x0d\\x86\\x1e\\x12\\x8b\\x82\\x3e\\x1c\\xe9\\xe7\\x22\\x03\\x5b\\x0e\\x59\\x08\\x31\\x4e\\x73\\x89\\x22\\x5d\\x2d\\x66\\xdf\\xc7\\x01\\x35\\x22\\x85\\x04\\x7d\\x13\\x27\\xe3\\x88\\x8b\\x69\\x72\\x89\\x62\\x7e\\xb8\\xcf\\x34\\xdf\\x40\\x00\\xa0\\x28\\x5f\\x05\\x38\\x49\\x00\\x4b\\xf4\\xa5\\xc5\\xd5\\x7f\\x95\\x7a\\x26\\x52\\x46\\x28\\x4b\\x69\\x1b\\xf5\\x3f\\x79\\xdc\\x04\\x5a\\x04\\x8e\\x7e\\xb9\\xad\\x75\\x05\\x5f\\xf6\\xef\\xff\\xd9\";\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const webpack = require(\"webpack\");\nconst MiniCssExtractPlugin = require(\"mini-css-extract-plugin\");\nconst CompressionPlugin = require(\"compression-webpack-plugin\");\nconst CopyWebpackPlugin = require(\"copy-webpack-plugin\");\nconst { ModifySourcePlugin, ReplaceOperation } = require(\"modify-source-webpack-plugin\");\nconst path = require(\"path\");\nconst zlib = require(\"zlib\");\n\n/**\n * Webpack configuration details for use with Grunt.\n *\n * @author n1474335 [n1474335@gmail.com]\n * @copyright Crown Copyright 2017\n * @license Apache-2.0\n */\n\nconst d = new Date();\nconst banner = `/**\n * CyberChef - The Cyber Swiss Army Knife\n *\n * @copyright Crown Copyright 2016-${d.getUTCFullYear()}\n * @license Apache-2.0\n *\n *   Copyright 2016-${d.getUTCFullYear()} Crown Copyright\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */`;\n\n\nmodule.exports = {\n    output: {\n        publicPath: \"\",\n        globalObject: \"this\",\n        assetModuleFilename: \"assets/[hash][ext][query]\"\n    },\n    plugins: [\n        new webpack.ProvidePlugin({\n            $: \"jquery\",\n            jQuery: \"jquery\",\n            log: \"loglevel\",\n            // process and Buffer are no longer polyfilled in webpack 5 but\n            // many of our dependencies expect them, so it is easiest to just\n            // provide them everywhere as was the case in webpack 4-\n            process: \"process\",\n            Buffer: [\"buffer\", \"Buffer\"]\n        }),\n        new webpack.BannerPlugin({\n            banner: banner,\n            raw: true,\n            entryOnly: true\n        }),\n        new webpack.DefinePlugin({\n            // Required by Jimp to improve loading speed in browsers\n            \"process.browser\": \"true\"\n        }),\n        new MiniCssExtractPlugin({\n            filename: \"assets/[name].css\"\n        }),\n        new CompressionPlugin({\n            filename: \"[path][base].gz\",\n            algorithm: \"gzip\",\n            test: /\\.(js|css|html)$/,\n        }),\n        new CompressionPlugin({\n            filename: \"[path][base].br\",\n            algorithm: \"brotliCompress\",\n            test: /\\.(js|css|html)$/,\n            compressionOptions: {\n                params: {\n                    [zlib.constants.BROTLI_PARAM_QUALITY]: 11,\n                },\n            },\n        }),\n        new CopyWebpackPlugin({\n            patterns: [\n                {\n                    context: \"src/core/vendor/\",\n                    from: \"tesseract/**/*\",\n                    to: \"assets/\"\n                }, {\n                    context: \"node_modules/tesseract.js/\",\n                    from: \"dist/worker.min.js\",\n                    to: \"assets/tesseract\"\n                }, {\n                    context: \"node_modules/tesseract.js-core/\",\n                    from: \"tesseract-core.wasm.js\",\n                    to: \"assets/tesseract\"\n                }, {\n                    context: \"node_modules/node-forge/dist\",\n                    from: \"prime.worker.min.js\",\n                    to: \"assets/forge/\"\n                }\n            ]\n        }),\n        new ModifySourcePlugin({\n            rules: [\n                {\n                    // Fix toSpare(0) bug in Split.js by avoiding gutter accomodation\n                    test: /split\\.es\\.js$/,\n                    operations: [\n                        new ReplaceOperation(\"once\", \"if (pixelSize < elementMinSize)\", \"if (false)\")\n                    ]\n                }\n            ]\n        })\n    ],\n    resolve: {\n        extensions: [\".mjs\", \".js\", \".json\"], // Allows importing files without extensions\n        alias: {\n            jquery: \"jquery/src/jquery\",\n        },\n        fallback: {\n            \"assert\": require.resolve(\"assert/\"),\n            \"buffer\": require.resolve(\"buffer/\"),\n            \"child_process\": false,\n            \"crypto\": require.resolve(\"crypto-browserify\"),\n            \"events\": require.resolve(\"events/\"),\n            \"fs\": false,\n            \"net\": false,\n            \"path\": require.resolve(\"path/\"),\n            \"process\": false,\n            \"stream\": require.resolve(\"stream-browserify\"),\n            \"tls\": false,\n            \"url\": require.resolve(\"url/\"),\n            \"vm\": false,\n            \"zlib\": require.resolve(\"browserify-zlib\")\n        }\n    },\n    module: {\n        // argon2-browser loads argon2.wasm by itself, so Webpack should not load it\n        noParse: /argon2\\.wasm$/,\n        rules: [\n            {\n                test: /\\.m?js$/,\n                exclude: /node_modules\\/(?!crypto-api|bootstrap)/,\n                options: {\n                    configFile: path.resolve(__dirname, \"babel.config.js\"),\n                    cacheDirectory: true,\n                    compact: false\n                },\n                type: \"javascript/auto\",\n                loader: \"babel-loader\"\n            },\n            {\n                test: /node-forge/,\n                loader: \"imports-loader\",\n                options: {\n                    additionalCode: \"var jQuery = false;\"\n                }\n            },\n            {\n                // Load argon2.wasm as base64-encoded binary file expected by argon2-browser\n                test: /argon2\\.wasm$/,\n                loader: \"base64-loader\",\n                type: \"javascript/auto\"\n            },\n            {\n                test: /prime.worker.min.js$/,\n                type: \"asset/source\"\n            },\n            {\n                test: /bootstrap-material-design/,\n                loader: \"imports-loader\",\n                options: {\n                    imports: \"default popper.js/dist/umd/popper.js Popper\"\n                }\n            },\n            {\n                test: /blueimp-load-image/,\n                loader: \"imports-loader\",\n                options: {\n                    type: \"commonjs\",\n                    imports: \"single min-document document\"\n                }\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: MiniCssExtractPlugin.loader,\n                        options: {\n                            publicPath: \"../\"\n                        }\n                    },\n                    \"css-loader\",\n                    \"postcss-loader\",\n                ]\n            },\n            {\n                test: /\\.(ico|eot|ttf|woff|woff2)$/,\n                type: \"asset/resource\",\n            },\n            {\n                test: /\\.svg$/,\n                type: \"asset/inline\",\n            },\n            { // Store font .fnt and .png files in a separate fonts folder\n                test: /(\\.fnt$|bmfonts\\/.+\\.png$)/,\n                type: \"asset/resource\",\n                generator: {\n                    filename: \"assets/fonts/[name][ext]\"\n                }\n            },\n            { // First party images are saved as files to be cached\n                test: /\\.(png|jpg|gif)$/,\n                exclude: /(node_modules|bmfonts)/,\n                type: \"asset/resource\",\n                generator: {\n                    filename: \"images/[name][ext]\"\n                }\n            },\n            { // Third party images are inlined\n                test: /\\.(png|jpg|gif)$/,\n                exclude: /web\\/static/,\n                type: \"asset/inline\",\n            },\n        ]\n    },\n    stats: {\n        children: false,\n        chunks: false,\n        modules: false,\n        entrypoints: false\n    },\n    ignoreWarnings: [\n        /source-map/,\n        /source map/,\n        /dependency is an expression/,\n        /export 'default'/,\n        /Can't resolve 'sodium'/\n    ],\n    performance: {\n        hints: false\n    }\n};\n"
  }
]